如何构建自己的XML解析器
如何构建自己的XML解析器
最近,我需要编写一个脚本来解析XML文件并提取各种信息。我确定Perl有很多优秀的XML模块,但是我不想经历麻烦,因为它必须找到并安装它(以及它的依赖树)。此外,我确定自己正在处理格式良好的XML,而我所做的只是提取字段,因此不需要进行错误检查,XSLT,XInput和所有其他花哨的东西。我只是将自己的XML解析器滚动了大约100行。这不是幻想。它做出了各种假设,会导致它在转转环境中出现乱码,但是我想我会展示它是如何构建的。
外环
“<”和“>”是保留字符,只能出现在标签的开头和结尾。如果您习惯于使用lex风格的解析器,那么您会想到“啊,所以我应该阅读直到看到“<”或“>”。但是在Perl中,您可以阅读输入,直到看到“>”为止。”,那么您至少知道有一个标签。因此,解析器的轮廓为:
$/=“>”;
而(<>)
{
…
}
注意使用$/设置输入记录分隔符。这意味着如果我们的输入文件是
<?xmlversion=“1.0”吗?>
<地址>
<friend/>
<name>John<nickname>Spike</nickname>Smith</name>
<streetAddress>枫树大道123号。</streetAddress>
</地址>
那么$_的连续值将是
“<?xmlversion=”1.0“?>”
“<地址>”
“<friend/>”
“<名称>”
“约翰<昵称>”
“穗</nickname>”
“史密斯</name>”
“<streetAddress>”
“123MapleAve。</streetAddress>”
“</address>”
换句话说,$_始终以完整的XML标记结尾,该标记可能以其他文本开头。因此,首要任务是将标记与它前面的文本分开:
我的$text;
我的$tag;
m{^(。*)<(。*?)>$}s;
$text=$1;
$tag=$2;
(对于那些不记得的人,m{pattern}等效于/pattern/。)使用“s”修饰符可以使点匹配任何内容,包括换行符。
数据表示
既然我们已经有了一些内容,那么考虑如何表示XML文件中的数据将是一个好主意。XML是一组嵌套的元素,每个元素都有一个名称,可选属性和内容(介于<foo>和</foo>之间的东西)。内容可以是文本数据或其他元素。我选择了一个相当简单的数据结构来保存元素的数据,形式为“((名称,属性,内容……)”。因此,以上示例中的streetAddress元素将变为:
(“streetAddress”,“”,“123MapleAve.”)
该名称元素包含三个项目:字符串“约翰”和“史密斯”,和<昵称>元素。它可以表示为:
(“名称”,
“”,
“百度”,
[“昵称”,
“”,
“长钉”,
],
“树叶”,
)
最终,我们希望将整个文件存储在像这样的树形列表中。
语境
当我们看到像<foo>这样的打开标记时,我们将开始对其进行解析。我们读取的所有内容都会放入该<foo>元素内,直到看到结束标记为止,这时我们已经完成了<foo>标记,并应返回到我们之前处理过的任何元素(包含<foo>元素)。自然地,这暗示了上下文堆栈。
在我的代码中,我做了一些不好的事情。我使用了两个具有相同名称的变量。@context是上下文堆栈(堆栈自然使用数组表示),而$context是引用数组的引用,它引用了我们当前正在查看的任何元素。
在任何给定时刻,$上下文是在到数组的引用(名称,属性,内容...)上述格式和@context是这样的引用到阵列堆叠,描述在其内部的当前元素为元素嵌入式。
也就是说,在某个时候,$context将是
[“昵称”,“”]
那时,@context将是
(
[“地址”,””],
[“name”,“”,“John”]
)
在主循环的这一节中,我们将标记与之前的文本分开了。现在我们看到应该将文本部分附加到$context指向的数组后面(现在不用担心$context的设置方式;我们稍后会担心):
我们的@tree=(“xml”,“”);#解析的XML树
我们的$context=\@tree;#上下文堆栈
我们的@context=();#当前上下文
$/=“>”;
而(<>)
{
最后,如果$_eq“”;#文件结尾
#XML元素前面可能有文本。
我的$text;
我的$tag;
m{^(。*)<(。*?)>$}s;
$text=$1;
$tag=$2;
如果($textne“”)
{
推送@{$context},$text;
}
…
}
解析标签
现在我们只需要处理$tag中的文本。我们需要关注三种类型:
<foo>:一个开始标记
</foo>:结束标记
<foo/>:单例标签
开头标签和单例标签还可以在标签名称之后具有属性:
<名称添加=“2007-07-19”>
<addressformat=“us-postal”category=“personal”>
在我的脚本中,我不必担心属性,因此我选择只将它们存储为未解析的原始字符串。如果它们对您很重要,我建议将它们表示为将每个属性的名称映射为其值的散列。
我们可以使用正则表达式(还有什么?)来解析标签。这很复杂,使用“x”标志是个好主意,它允许我们在正则表达式中嵌入空格(包括换行符)和注释:
如果($tag=〜m{
^(/?)#结束标签
(\S+)#标记名称
#可选属性
(
#<space><attr-name>=“<attr-value>”
(?:\s+
[\w:]+#属性名称
=
\“[^\”]*\“#属性值
)*
)
\s*
(/?)#可选的单斜杠斜杠
$
}xs)
{
#这是常规的<foo>,</foo>或<foo/>标记。
我的$closing=($1ne“”);#这是结束标签吗?
我的$name=$2;#标签名
我的$attrs=$3;#属性分配
我的$singleton=($4ne“”);#这是一个单例标签吗?
…
}
现在,$name是标签的名称,$attrs是(未解析的)属性字符串,$closing和$singleton是布尔型标志,分别告诉我们这是关闭标签还是单例标签。
关闭标签最容易处理。我们已经完成了元素的解析,解析后的数据位于$context中,因此我们要做的就是将其附加到其父元素并弹出@context堆栈以返回到该元素:
如果($结束)
{
#当前上下文的结尾
#将$context附加到@context中的最后一项
推送@{$context[$#context]},$context;
#从堆栈中还原先前的上下文
$context=pop@context;
下一个;
}
这给我们留下了开始标签和单例标签。像<foo/>这样的单例元素等效于<foo></foo>。也就是说,单身人士没有孩子。但是,无论哪种情况,我们都需要启动一个新的上下文:
#将旧的上下文保存到上下文堆栈中
推送@context,$context;
#开始一个新的上下文
$context=[$name,$attrs];
当然,如果我们正在查看一个单例标记,那么我们已经知道它没有内容,应该立即关闭。并且由于我们已经完成了关闭标签的工作,因此我们已经知道如何关闭标签:
如果($singleton)
{
推送@{$context[$#context]},$context;
$context=pop@context;
}
最后,我们可以解决三种标签类型中最困难的一种:打开标签。
对于这些,我们需要将旧的上下文保存到堆栈中并启动一个新的上下文(我们已经完成),然后……实际上,这是我们在此阶段需要做的所有事情。我们无法添加元素的内容,因为我们尚未从输入文件中读取它们。
还记得在顶部,当我们将标签与标签之前的文本分开,并将其附加到@{$context}之后吗?现在,我们了解$context的设置方式:看到开始标记时,我们创建了一个新的匿名数组,以便循环的后续迭代中可以放置其文本。
就是这样!您可以阅读完整的脚本,其中包括用于打印已解析树的&dumptree函数和用于查找元素的&lookup函数。
得到教训
输入记录不必以换行符结尾。如果有更方便的记录终止符或分隔符,请使用$/以使您的生活更轻松的方式读取输入。
Perl的正则表达式功能强大。不必象lex那样费力地尝试先读取“<”,然后读取标记名称,然后再读取属性,然后再读取“>”。只需阅读整个内容,然后在正则表达式内用括号括起来的表达式提取有趣的内容即可。
如果您要解决一个难题,请尝试将其分解为不那么困难的问题,然后将其分解为更简单的子问题。首先要照顾容易的部分。这使您可以进行简化的假设(如果我们知道我们不在看一个结束标记,那么我们就需要保存旧的上下文并开始一个新的上下文)。一旦处理了足够多的简单操作,您可能会发现难题已经简化为一点,您根本不需要做任何事情。
最近,我需要编写一个脚本来解析XML文件并提取各种信息。我确定Perl有很多优秀的XML模块,但是我不想经历麻烦,因为它必须找到并安装它(以及它的依赖树)。此外,我确定自己正在处理格式良好的XML,而我所做的只是提取字段,因此不需要进行错误检查,XSLT,XInput和所有其他花哨的东西。我只是将自己的XML解析器滚动了大约100行。这不是幻想。它做出了各种假设,会导致它在转转环境中出现乱码,但是我想我会展示它是如何构建的。
外环
“<”和“>”是保留字符,只能出现在标签的开头和结尾。如果您习惯于使用lex风格的解析器,那么您会想到“啊,所以我应该阅读直到看到“<”或“>”。但是在Perl中,您可以阅读输入,直到看到“>”为止。”,那么您至少知道有一个标签。因此,解析器的轮廓为:
$/=“>”;
而(<>)
{
…
}
注意使用$/设置输入记录分隔符。这意味着如果我们的输入文件是
<?xmlversion=“1.0”吗?>
<地址>
<friend/>
<name>John<nickname>Spike</nickname>Smith</name>
<streetAddress>枫树大道123号。</streetAddress>
</地址>
那么$_的连续值将是
“<?xmlversion=”1.0“?>”
“<地址>”
“<friend/>”
“<名称>”
“约翰<昵称>”
“穗</nickname>”
“史密斯</name>”
“<streetAddress>”
“123MapleAve。</streetAddress>”
“</address>”
换句话说,$_始终以完整的XML标记结尾,该标记可能以其他文本开头。因此,首要任务是将标记与它前面的文本分开:
我的$text;
我的$tag;
m{^(。*)<(。*?)>$}s;
$text=$1;
$tag=$2;
(对于那些不记得的人,m{pattern}等效于/pattern/。)使用“s”修饰符可以使点匹配任何内容,包括换行符。
数据表示
既然我们已经有了一些内容,那么考虑如何表示XML文件中的数据将是一个好主意。XML是一组嵌套的元素,每个元素都有一个名称,可选属性和内容(介于<foo>和</foo>之间的东西)。内容可以是文本数据或其他元素。我选择了一个相当简单的数据结构来保存元素的数据,形式为“((名称,属性,内容……)”。因此,以上示例中的streetAddress元素将变为:
(“streetAddress”,“”,“123MapleAve.”)
该名称元素包含三个项目:字符串“约翰”和“史密斯”,和<昵称>元素。它可以表示为:
(“名称”,
“”,
“百度”,
[“昵称”,
“”,
“长钉”,
],
“树叶”,
)
最终,我们希望将整个文件存储在像这样的树形列表中。
语境
当我们看到像<foo>这样的打开标记时,我们将开始对其进行解析。我们读取的所有内容都会放入该<foo>元素内,直到看到结束标记为止,这时我们已经完成了<foo>标记,并应返回到我们之前处理过的任何元素(包含<foo>元素)。自然地,这暗示了上下文堆栈。
在我的代码中,我做了一些不好的事情。我使用了两个具有相同名称的变量。@context是上下文堆栈(堆栈自然使用数组表示),而$context是引用数组的引用,它引用了我们当前正在查看的任何元素。
在任何给定时刻,$上下文是在到数组的引用(名称,属性,内容...)上述格式和@context是这样的引用到阵列堆叠,描述在其内部的当前元素为元素嵌入式。
也就是说,在某个时候,$context将是
[“昵称”,“”]
那时,@context将是
(
[“地址”,””],
[“name”,“”,“John”]
)
在主循环的这一节中,我们将标记与之前的文本分开了。现在我们看到应该将文本部分附加到$context指向的数组后面(现在不用担心$context的设置方式;我们稍后会担心):
我们的@tree=(“xml”,“”);#解析的XML树
我们的$context=\@tree;#上下文堆栈
我们的@context=();#当前上下文
$/=“>”;
而(<>)
{
最后,如果$_eq“”;#文件结尾
#XML元素前面可能有文本。
我的$text;
我的$tag;
m{^(。*)<(。*?)>$}s;
$text=$1;
$tag=$2;
如果($textne“”)
{
推送@{$context},$text;
}
…
}
解析标签
现在我们只需要处理$tag中的文本。我们需要关注三种类型:
<foo>:一个开始标记
</foo>:结束标记
<foo/>:单例标签
开头标签和单例标签还可以在标签名称之后具有属性:
<名称添加=“2007-07-19”>
<addressformat=“us-postal”category=“personal”>
在我的脚本中,我不必担心属性,因此我选择只将它们存储为未解析的原始字符串。如果它们对您很重要,我建议将它们表示为将每个属性的名称映射为其值的散列。
我们可以使用正则表达式(还有什么?)来解析标签。这很复杂,使用“x”标志是个好主意,它允许我们在正则表达式中嵌入空格(包括换行符)和注释:
如果($tag=〜m{
^(/?)#结束标签
(\S+)#标记名称
#可选属性
(
#<space><attr-name>=“<attr-value>”
(?:\s+
[\w:]+#属性名称
=
\“[^\”]*\“#属性值
)*
)
\s*
(/?)#可选的单斜杠斜杠
$
}xs)
{
#这是常规的<foo>,</foo>或<foo/>标记。
我的$closing=($1ne“”);#这是结束标签吗?
我的$name=$2;#标签名
我的$attrs=$3;#属性分配
我的$singleton=($4ne“”);#这是一个单例标签吗?
…
}
现在,$name是标签的名称,$attrs是(未解析的)属性字符串,$closing和$singleton是布尔型标志,分别告诉我们这是关闭标签还是单例标签。
关闭标签最容易处理。我们已经完成了元素的解析,解析后的数据位于$context中,因此我们要做的就是将其附加到其父元素并弹出@context堆栈以返回到该元素:
如果($结束)
{
#当前上下文的结尾
#将$context附加到@context中的最后一项
推送@{$context[$#context]},$context;
#从堆栈中还原先前的上下文
$context=pop@context;
下一个;
}
这给我们留下了开始标签和单例标签。像<foo/>这样的单例元素等效于<foo></foo>。也就是说,单身人士没有孩子。但是,无论哪种情况,我们都需要启动一个新的上下文:
#将旧的上下文保存到上下文堆栈中
推送@context,$context;
#开始一个新的上下文
$context=[$name,$attrs];
当然,如果我们正在查看一个单例标记,那么我们已经知道它没有内容,应该立即关闭。并且由于我们已经完成了关闭标签的工作,因此我们已经知道如何关闭标签:
如果($singleton)
{
推送@{$context[$#context]},$context;
$context=pop@context;
}
最后,我们可以解决三种标签类型中最困难的一种:打开标签。
对于这些,我们需要将旧的上下文保存到堆栈中并启动一个新的上下文(我们已经完成),然后……实际上,这是我们在此阶段需要做的所有事情。我们无法添加元素的内容,因为我们尚未从输入文件中读取它们。
还记得在顶部,当我们将标签与标签之前的文本分开,并将其附加到@{$context}之后吗?现在,我们了解$context的设置方式:看到开始标记时,我们创建了一个新的匿名数组,以便循环的后续迭代中可以放置其文本。
就是这样!您可以阅读完整的脚本,其中包括用于打印已解析树的&dumptree函数和用于查找元素的&lookup函数。
得到教训
输入记录不必以换行符结尾。如果有更方便的记录终止符或分隔符,请使用$/以使您的生活更轻松的方式读取输入。
Perl的正则表达式功能强大。不必象lex那样费力地尝试先读取“<”,然后读取标记名称,然后再读取属性,然后再读取“>”。只需阅读整个内容,然后在正则表达式内用括号括起来的表达式提取有趣的内容即可。
如果您要解决一个难题,请尝试将其分解为不那么困难的问题,然后将其分解为更简单的子问题。首先要照顾容易的部分。这使您可以进行简化的假设(如果我们知道我们不在看一个结束标记,那么我们就需要保存旧的上下文并开始一个新的上下文)。一旦处理了足够多的简单操作,您可能会发现难题已经简化为一点,您根本不需要做任何事情。
【版权与免责声明】如发现内容存在版权问题,烦请提供相关信息发邮件至 1439028666@qq.com ,我们将及时沟通进行删除处理。
本站内容除了 98link( http://www.98link.com/ )特别标记的原创外,其它均为网友转载内容,涉及言论、版权与本站无关。
最新文章
-
1
如何提高抖音粉丝数量(一位达到10000粉丝的抖音博主分享的经验)
-
2
从零开始开启抖音橱窗,如何吸引更多粉丝关注(没有1000粉丝)
-
3
如何开通抖音橱窗的商品分享权限(教你详细步骤)
-
4
如何在抖音开通小黄车直播(抖音小黄车直播教程)
-
5
正视SEO优化(让企业营销更高效,SEO优化的6大好处)
-
6
提高网站排名及权重的终极优化方案(SEO小白还是专业人士)
-
7
营销型网站建设的核心要素是什么(掌握这些要素)
-
8
营销型网站SEO优化之关键策略(15个段落教你如何提高营销型网站SEO排名)
-
9
企业SEO优化营销(如何提升网站排名)
-
10
营口seo优化的窍门知识(营口seo攻略秘诀)