当前位置:首页> 社会热点 > 如何构建自己的XML解析器

如何构建自己的XML解析器

2021-04-17 22:20:06 来源: 网络   编辑: 佚名   浏览(514)人   
0
如何构建自己的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那样费力地尝试先读取“<”,然后读取标记名称,然后再读取属性,然后再读取“>”。只需阅读整个内容,然后在正则表达式内用括号括起来的表达式提取有趣的内容即可。  
如果您要解决一个难题,请尝试将其分解为不那么困难的问题,然后将其分解为更简单的子问题。首先要照顾容易的部分。这使您可以进行简化的假设(如果我们知道我们不在看一个结束标记,那么我们就需要保存旧的上下文并开始一个新的上下文)。一旦处理了足够多的简单操作,您可能会发现难题已经简化为一点,您根本不需要做任何事情。
【版权与免责声明】如发现内容存在版权问题,烦请提供相关信息发邮件至 1439028666@qq.com ,我们将及时沟通进行删除处理。 本站内容除了 98link( http://www.98link.com/ )特别标记的原创外,其它均为网友转载内容,涉及言论、版权与本站无关。