查看源代码 Xmerl
简介
特性
Xmerl XML 解析器能够根据 XML 1.0 标准解析 XML 文档。默认情况下,它执行格式良好的解析(语法检查和格式良好约束的检查)。可选地,也可以使用 Xmerl 作为验证解析器(根据引用的 DTD 和验证约束进行验证)。通过例如 xmerl_xs 模块,可以将解析后的结果转换为其他格式,例如文本、HTML、XML 等。
概述
本文档不介绍 XML。有很多书籍从不同的角度描述了 XML。在 www.W3.org 站点,您将找到 XML 1.0 规范 和其他相关规范。您可以在 ZVON.org 站点找到关于 XML 和相关规范的教程。
但是,在这里您将找到一些关于如何使用以及您可以使用 Xmerl 做什么的示例。用户界面的详细描述可以在参考手册中找到。
Xmerl 有两个已知的缺点
- 它无法通过 URL 引用检索 Internet 上的外部实体,只能检索本地文件系统中的资源。
- Xmerl 可以解析 Unicode 编码的数据。但是,它在标记名称、属性名称和其他编码为 Unicode 字符(未映射到 ASCII)的标记名称上会失败。
通过解析 XML 文档,您将获得一个记录,显示文档的结构,作为返回值。该记录还保存着文档的数据。Xmerl 方便在以下场景中使用
您需要从 XML 文档中检索数据。您的 Erlang 软件可以通过从解析接收到的数据结构中提取数据来处理来自 XML 文档的信息。
还可以使用 Xmerl 对解析后的 XML 进行进一步处理。如果您想将 XML 文档的格式更改为例如 HTML、文本或其他 XML 格式,您可以对其进行转换。Xmerl 支持这种转换。
也可以将任意数据转换为 XML。因此,例如,很容易让它对人类可读。在这种情况下,您首先从您的数据中创建 Xmerl 数据结构,然后将其转换为 XML。
您可以在下面找到这三个使用示例。
Xmerl 用户界面数据结构
Xmerl 用于保存解析数据的以下记录在 xmerl.hrl
中定义
成功解析的结果是一个元组 {DataStructure,M}
。M
是 XML 产品 Misc,它是文档元素之后的标记。它按原样返回。DataStructure
是一个 #xmlElement{}
记录,其中包含字段 name
、parents
、attributes
和 content
,例如
#xmlElement{name=Name,
...
parents=Parents,
...
attributes=Attrs,
content=Content,
...}
元素的名称在 name
字段中找到。在 parents
字段中保存父元素的名称。Parents 是一个元组列表,其中每个元组中的第一个元素是父元素的名称。该列表是反向的。
记录 #xmlAttribute{}
在 name
和 value
字段中保存属性的名称和值。元素的所有属性都是 #xmlAttribute{}
的列表,位于 #xmlElement{}
记录的 attributes
字段中。
顶层元素的 content
字段是一个记录列表,显示文档的结构和数据。如果它是一个简单的文档,例如
<?xml version="1.0"?>
<dog>
Grand Danois
</dog>
解析结果将是
#xmlElement{name = dog,
...
parents = [],
...
attributes = [],
content = [{xmlText,[{dog,1}],1,[],"\
Grand Danois\
",text}],
...
}
其中顶层元素的内容是:[{xmlText,[{dog,1}],1,[],"\ Grand Danois\ ",text}]
。文本将在 xmlText
记录中返回。不过,通常文档更复杂,顶层元素的内容在这种情况下将是一个嵌套结构,其中包含 #xmlElement{}
记录,而这些记录又可能具有复杂的内容。所有这些都反映了 XML 文档的结构。
标记之间的空格字符,如 space
、tab
和 line feed
,将被规范化并作为 xmlText 记录返回。
错误
不成功的解析会导致错误,该错误可能是元组 {error,Reason}
或退出:{'EXIT',Reason}
。根据 XML 1.0 标准,存在 fatal error
和 error
情况。符合规范的解析器必须检测到致命错误,而可能检测到错误。这两个类别的错误都由此版本的 Xmerl 报告为致命错误,通常以退出的形式报告。
入门
在以下示例中,我们使用 XML 文件 "motorcycles.xml" 和相应的 DTD "motorcycles.dtd"。motorcycles.xml 如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE motorcycles SYSTEM "motorcycles.dtd">
<motorcycles>
<bike year="2000" color="black">
<name>
<manufacturer>Suzuki</manufacturer>
<brandName>Suzuki VL 1500</brandName>
<additionalName>Intruder</additionalName>
</name>
<engine>V-engine, 2-cylinders, 1500 cc</engine>
<kind>custom</kind>
<drive>cardan</drive>
<accessories>Sissy bar, luggage carrier,V&H exhaust pipes</accessories>
</bike>
<date>2004.08.25</date>
<bike year="1983" color="read pearl">
<name>
<manufacturer>Yamaha</manufacturer>
<brandName>XJ 400</brandName>
</name>
<engine>4 cylinder, 400 cc</engine>
<kind>alround</kind>
<drive>chain</drive>
<comment>Good shape!</comment>
</bike>
</motorcycles>
motorcycles.dtd 如下所示
<?xml version="1.0" encoding="utf-8" ?>
<!ELEMENT motorcycles (bike,date?)+ >
<!ELEMENT bike (name,engine,kind,drive, accessories?,comment?) >
<!ELEMENT name (manufacturer,brandName,additionalName?) >
<!ELEMENT manufacturer (#PCDATA)>
<!ELEMENT brandName (#PCDATA)>
<!ELEMENT additionalName (#PCDATA)>
<!ELEMENT engine (#PCDATA)>
<!ELEMENT kind (#PCDATA)>
<!ELEMENT drive (#PCDATA)>
<!ELEMENT comment (#PCDATA)>
<!ELEMENT accessories (#PCDATA)>
<!-- Date of the format yyyy.mm.dd -->
<!ELEMENT date (#PCDATA)>
<!ATTLIST bike year NMTOKEN #REQUIRED
color NMTOKENS #REQUIRED
condition (useless | bad | serviceable | moderate | good |
excellent | new | outstanding) "excellent" >
如果要解析 XML 文件 motorcycles.xml,可以在 Erlang shell 中运行它,如下所示
3> {ParseResult,Misc}=xmerl_scan:file("motorcycles.xml").
{{xmlElement,motorcycles,
motorcycles,
[],
{xmlNamespace,[],[]},
[],
1,
[],
[{xmlText,[{motorcycles,1}],1,[],"\
",text},
{xmlElement,bike,
bike,
[],
{xmlNamespace,[],[]},
[{motorcycles,1}],
2,
[{xmlAttribute,year,[],[],[],[]|...},
{xmlAttribute,color,[],[],[]|...}],
[{xmlText,[{bike,2},{motorcycles|...}],
1,
[]|...},
{xmlElement,name,name,[]|...},
{xmlText,[{...}|...],3|...},
{xmlElement,engine|...},
{xmlText|...},
{...}|...],
[],
".",
undeclared},
...
],
[],
".",
undeclared},
[]}
4>
如果您改为接收 XML 文档作为字符串,则可以通过 xmerl_scan:string/1
解析它。file/2 和 string/2 都存在,其中第二个参数是解析器的选项列表,请参阅 参考手册。
示例:从 XML 内容中提取数据
在此示例中,考虑您想要检查 XML 文件中特定数据的情况。例如,您想检查每辆摩托车已记录多长时间。
查看 DTD 并观察到符合此 DTD 的 XML 文档的结构必须有一个 motorcycles 元素(根元素)。motorcycles 元素必须至少有一个 bike 元素。在每个 bike 元素之后,可以有一个 date 元素。date 元素的内容是 #PCDATA(Parsed Character DATA),即原始文本。请注意,如果 #PCDATA 必须具有 "<"
或 "&"
字符,则必须分别写为 "<"
和 "&"
。还存在类似于 HTML 和 SGML 中的其他字符实体。
如果您成功解析 XML 文件并启用验证,如:xmerl_scan:file('motorcycles.xml',[{validation,true}])
,您就知道该 XML 文档是有效的,并且具有根据 DTD 的结构。
因此,了解允许的结构,很容易编写一个程序来遍历数据结构并提取名称为 date 的 #xmlElements{}
记录中的信息。
请注意,空格:标记之间的每个空格、制表符或换行符都会导致 #xmlText{}
记录。
示例:从任意数据创建 XML
对于此任务,有不止一种方法可以实现。“蛮力”方法是创建您需要的记录,并将您的数据馈送到相应元素的 content 和 attribute 字段中。
Xmerl 通过“简单形式”格式对此提供支持。您可以将您的数据放入简单形式的数据结构中,并将其馈送到 xmerl:export_simple(Content,Callback,RootAttributes)
中。Content 可以是简单形式和 Xmerl 记录(如 #xmlElement{}
和 #xmlText{}
)的混合。
类型为
- Content = [Element]
- Callback = atom()
- RootAttributes = [Attributes]
Element 是以下任何一种
- {Tag, Attributes, Content}
- {Tag, Content}
- Tag
- IOString
- #xmlText{}
- #xmlElement{}
- #xmlPI{}
- #xmlComment{}
- #xmlDecl{}
简单形式结构是 {Tag, Attributes, Content}
、{Tag, Content}
或 Tag
中的任何一种,其中
- Tag = atom()
- Attributes = [{Name, Value}| #xmlAttribute{}]
- Name = atom()
Value = IOString | atom() | integer()
另请参阅 xmerl 的参考手册
如果您想在 motorcycles.xml 文档中添加有关 2003 年出厂的黑色 Harley Davidsson 1200 cc Sportster 摩托车的信息,且该摩托车状态良好,则可以将数据放入如下的简单形式的数据结构中
Data =
{bike,
[{year,"2003"},{color,"black"},{condition,"new"}],
[{name,
[{manufacturer,["Harley Davidsson"]},
{brandName,["XL1200C"]},
{additionalName,["Sportster"]}]},
{engine,
["V-engine, 2-cylinders, 1200 cc"]},
{kind,["custom"]},
{drive,["belt"]}]}
为了将此数据追加到 motorcycles.xml 文档的末尾,您必须解析该文件并将 Data 添加到根元素内容的末尾。
{RootEl,Misc}=xmerl_scan:file('motorcycles.xml'),
#xmlElement{content=Content} = RootEl,
NewContent=Content++lists:flatten([Data]),
NewRootEl=RootEl#xmlElement{content=NewContent},
然后,您可以通过 export_simple/2 函数运行它
{ok,IOF}=file:open('new_motorcycles.xml',[write]),
Export=xmerl:export_simple([NewRootEl],xmerl_xml),
io:format(IOF,"~s~n",[lists:flatten(Export)]),
结果将是
<?xml version="1.0"?><motorcycles>
<bike year="2000" color="black">
<name>
<manufacturer>Suzuki</manufacturer>
<brandName>Suzuki VL 1500</brandName>
<additionalName>Intruder</additionalName>
</name>
<engine>V-engine, 2-cylinders, 1500 cc</engine>
<kind>custom</kind>
<drive>cardan</drive>
<accessories>Sissy bar, luggage carrier,V&H exhaust pipes</accessories>
</bike>
<date>2004.08.25</date>
<bike year="1983" color="read pearl">
<name>
<manufacturer>Yamaha</manufacturer>
<brandName>XJ 400</brandName>
</name>
<engine>4 cylinder, 400 cc</engine>
<kind>alround</kind>
<drive>chain</drive>
<comment>Good shape!</comment>
</bike>
<bike year="2003" color="black" condition="new"><name><manufacturer>Harley Davidsson</manufacturer><brandName>XL1200C</brandName><additionalName>Sportster</additionalName></name><engine>V-engine, 2-cylinders, 1200 cc</engine><kind>custom</kind><drive>belt</drive></bike></motorcycles>
如果获取与原始文档中类似的缩进和换行符很重要,则必须在适当的位置添加带有空格和换行符值的 #xmlText{}
记录。可能还需要保留引用 DTD 的原始序言。如果需要,可以将 RootAttribute {prolog,Value}
传递给 export_simple/3
。以下示例代码修复了上一个示例中的这些更改
Data =
[#xmlText{value=" "},
{bike,[{year,"2003"},{color,"black"},{condition,"new"}],
[#xmlText{value="\
"},
{name,[#xmlText{value="\
"},
{manufacturer,["Harley Davidsson"]},
#xmlText{value="\
"},
{brandName,["XL1200C"]},
#xmlText{value="\
"},
{additionalName,["Sportster"]},
#xmlText{value="\
"}]},
{engine,["V-engine, 2-cylinders, 1200 cc"]},
#xmlText{value="\
"},
{kind,["custom"]},
#xmlText{value="\
"},
{drive,["belt"]},
#xmlText{value="\
"}]},
#xmlText{value="\
"}],
...
NewContent=Content++lists:flatten([Data]),
NewRootEl=RootEl#xmlElement{content=NewContent},
...
Prolog = ["<?xml version=\\"1.0\\" encoding=\\"utf-8\\" ?>
<!DOCTYPE motorcycles SYSTEM \\"motorcycles.dtd\\">\
"],
Export=xmerl:export_simple([NewRootEl],xmerl_xml,[{prolog,Prolog}]),
...
结果将是
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE motorcycles SYSTEM "motorcycles.dtd">
<motorcycles>
<bike year="2000" color="black">
<name>
<manufacturer>Suzuki</manufacturer>
<brandName>Suzuki VL 1500</brandName>
<additionalName>Intruder</additionalName>
</name>
<engine>V-engine, 2-cylinders, 1500 cc</engine>
<kind>custom</kind>
<drive>cardan</drive>
<accessories>Sissy bar, luggage carrier,V&H exhaust pipes</accessories>
</bike>
<date>2004.08.25</date>
<bike year="1983" color="read pearl">
<name>
<manufacturer>Yamaha</manufacturer>
<brandName>XJ 400</brandName>
</name>
<engine>4 cylinder, 400 cc</engine>
<kind>alround</kind>
<drive>chain</drive>
<comment>Good shape!</comment>
</bike>
<bike year="2003" color="black" condition="new">
<name>
<manufacturer>Harley Davidsson</manufacturer>
<brandName>XL1200C</brandName>
<additionalName>Sportster</additionalName>
</name><engine>V-engine, 2-cylinders, 1200 cc</engine>
<kind>custom</kind>
<drive>belt</drive>
</bike>
</motorcycles>
上面生成的 XML 是为了可读性而格式化的。也可以使用另一个用 2 个空格缩进代码的导出器。为了使用它,只需要更改导出模块
...
Export=xmerl:export_simple([NewRootEl],xmerl_xml_indent,[{prolog,Prolog}]),
...
示例:将 XML 转换为 HTML
假设您想将 motorcycles.xml 文档转换为 HTML。如果您希望生成的 HTML 文档的结构和标记与 XML 文档的结构和标记相同,则可以使用 xmerl:export/2
函数。以下
2> {Doc,Misc}=xmerl_scan:file('motorcycles.xml').
{{xmlElement,motorcycles,
motorcycles,
[],
{xmlNamespace,[],[]},
[],
1,
[],
[{xmlText,[{motorcycles,1}],1,[],"\
",text},
{xmlElement,bike,
...
3> DocHtml=xmerl:export([Doc],xmerl_html).
["<!DOCTYPE HTML PUBLIC \\"",
"-//W3C//DTD HTML 4.01 Transitional//EN",
"\\"",
[],
">\
",
[[["<","motorcycles",">"],
["\
",
[["<",
"bike",
[[" ","year","=\\"","2000","\\""],[" ","color","=\\"","black","\\""]],
">"],
...
将给出结果 result_export.html
也许您想做一些更适合人类阅读的事情。假设您想在开头列出所有不同的品牌,并提供指向每个摩托车组的链接。您还希望所有摩托车按品牌排序,然后在顶部添加一些鲜艳的颜色。因此,您可以重新排列元素的顺序并放入任意 HTML 标记。这可以通过 Xmerl 中的 XSL 转换 (XSLT) 等功能来实现。
尽管以下示例展示了一种将数据从 XML 转换为 HTML 的方法,但它也适用于转换为其他格式。
xmerl_xs
不实现整个 XSLT 规范,但实现了基本功能。有关所有详细信息,请参阅 参考手册
首先,介绍一些关于 xmerl_xs 功能的说明
您需要编写模板函数,以便能够控制您想要的输出类型。因此,如果您想将 bike
元素封装在 <p> 标记中,您只需编写一个函数
template(E = #xmlElement{name='bike'}) ->
["<p>",xslapply(fun template/1,E),"</p>"];
使用 xslapply
,您告诉 XSLT 处理器它应该以什么顺序遍历 XML 结构。默认情况下,它按前序遍历,但是通过以下方式,我们有意识地选择打破该顺序
template(E = #xmlElement{name='bike'}) ->
["<p>",xslapply(fun template/1,select("bike/name/manufacturer")),"</p>"];
如果要输出 XML 元素或属性的内容,您将通过 value_of
函数获得作为字符串的值
template(E = #xmlElement{name='motorcycles'}) ->
["<p>",value_of(select("bike/name/manufacturer",E),"</p>"];
在 xmerl_xs 函数中,您可以提供 select(String) 调用,这是一个 XPath 功能。有关更多详细信息,请参阅 xmerl_xs 教程。
现在,回到我们希望输出更有序的示例。使用模板
template(E = #xmlElement{name='motorcycles'}) ->
[ "<head>\
<title>motorcycles</title>\
</head>\
",
"<body>\
",
\011 "<h1>Used Motorcycles</h1>\
",
\011 "<ul>\
",
\011 remove_duplicates(value_of(select("bike/name/manufacturer",E))),
\011 "\
</ul>\
",
\011 sort_by_manufacturer(xslapply(fun template/1, E)),
"</body>\
",
\011 "</html>\
"];
我们匹配顶层元素,并将内部部分嵌入 HTML body 中。然后,我们提取所有摩托车品牌的字符串值,对其进行排序,并通过 remove_duplicates(value_of(select("bike/name/manufacturer", E)))
删除重复项。我们还处理顶层元素的子结构,并将其传递给一个函数,该函数根据本示例开头任务中的描述,按品牌对所有摩托车信息进行排序。
下一个模板匹配 bike
元素
template(E = #xmlElement{name='bike'}) ->
{value_of(select("name/manufacturer",E)),["<dt>",xslapply(fun template/1,select("name",E)),"</dt>",
"<dd><ul>\
",
"<li style="color:green">Manufacturing year: ",xslapply(fun template/1,select("@year",E)),"</li>\
",
"<li style="color:red">Color: ",xslapply(fun template/1,select("@color",E)),"</li>\
",
"<li style="color:blue">Shape : ",xslapply(fun template/1,select("@condition",E)),"</li>\
",
"</ul></dd>\
"]};
这将创建一个包含摩托车品牌和输出格式的元组。我们仅将品牌名称用于排序目的。我们必须以“内置子句” template(E) -> built_in_rules(fun template/1, E).
结束模板函数。
整个程序是 motorcycles2html.erl
%%%-------------------------------------------------------------------
%%% File : motorcycles2html.erl
%%% Author : Bertil Karlsson <[email protected]>
%%% Description :
%%%
%%% Created : 2 Sep 2004 by Bertil Karlsson <[email protected]>
%%%-------------------------------------------------------------------
-module(motorcycles2html).
-include_lib("xmerl/include/xmerl.hrl").
-import(xmerl_xs,
[ xslapply/2, value_of/1, select/2, built_in_rules/2 ]).
-export([process_xml/1,process_to_file/2,process_to_file/1]).
process_xml(Doc) ->
template(Doc).
process_to_file(FileName) ->
process_to_file(FileName,'motorcycles.xml').
process_to_file(FileName,XMLDoc) ->
case file:open(FileName,[write]) of
{ok,IOF} ->
{XMLContent,_} = xmerl_scan:file(XMLDoc),
TransformedXML=process_xml(XMLContent),
io:format(IOF,"~s",[TransformedXML]),
file:close(IOF);
{error,Reason} ->
io:format("could not open file due to ~p.~n",[Reason])
end.
%%% templates
template(E = #xmlElement{name='motorcycles'}) ->
[ "<head>\n<title>motorcycles</title>\n</head>\n",
"<body>\n",
"<h1>Used Motorcycles</h1>\n",
"<ul>\n",
remove_duplicates(value_of(select("bike/name/manufacturer",E))),
"\n</ul>\n",
sort_by_manufacturer(xslapply(fun template/1, E)),
"</body>\n",
"</html>\n"];
template(E = #xmlElement{name='bike'}) ->
{value_of(select("name/manufacturer",E)),["<dt>",xslapply(fun template/1,select("name",E)),"</dt>",
"<dd><ul>\n",
"<li style=\"color:green\">Manufacturing year: ",xslapply(fun template/1,select("@year",E)),"</li>\n",
"<li style=\"color:red\">Color: ",xslapply(fun template/1,select("@color",E)),"</li>\n",
"<li style=\"color:blue\">Shape : ",xslapply(fun template/1,select("@condition",E)),"</li>\n",
"</ul></dd>\n"]};
template(E) -> built_in_rules(fun template/1, E).
%%%%%%%%%%% helper routines
%% sorts on the bike name element, unwraps the bike information and
%% inserts a line feed and indentation on each bike element.
sort_by_manufacturer(L) ->
Tuples=[X1||X1={_,_} <- L],
SortedTS = lists:keysort(1,Tuples),
InsertRefName_UnWrap=
fun([{[Name],V}|Rest],Name,F)->
[V|F(Rest,Name,F)];
([{[Name],V}|Rest],_PreviousName,F) ->
[["<a name=\"",Name,"\"></>"],V|F(Rest,Name,F)];
([],_,_) -> []
end,
SortedRefed=InsertRefName_UnWrap(SortedTS,no_name,InsertRefName_UnWrap),
% SortedTs=[Y||{X,Y}<-lists:keysort(1,Tuples)],
WS = "\n ",
Fun=fun([H|T],Acc,F)->
F(T,[H,WS|Acc],F);
([],Acc,_F)->
lists:reverse([WS|Acc])
end,
if length(SortedRefed) > 0 ->
Fun(SortedRefed,[],Fun);
true -> []
end.
%% removes all but the first of an element in L and inserts a html
%% reference for each list element.
remove_duplicates(L) ->
remove_duplicates(L,[]).
remove_duplicates([],Acc) ->
make_ref(lists:sort(lists:reverse(Acc)));
remove_duplicates([A|L],Acc) ->
case lists:delete(A,L) of
L ->
remove_duplicates(L,[A|Acc]);
L1 ->
remove_duplicates([A|L1],[Acc])
end.
make_ref([]) -> [];
make_ref([H]) when is_atom(H) ->
"<ul><a href=\"#"++atom_to_list(H)++"\">"++atom_to_list(H)++"</a></ul>";
make_ref([H]) when is_list(H) ->
"<ul><a href=\"#"++H++"\">\s"++H++"</a></ul>";
make_ref([H|T]) when is_atom(H) ->
["<ul><a href=\"#"++atom_to_list(H)++"\">\s"++atom_to_list(H)++",\n</a></ul>"
|make_ref(T)];
make_ref([H|T]) when is_list(H) ->
["<ul><a href=\"#"++H++"\">\s"++H++",\n</a></ul>"|make_ref(T)].
如果我们像这样运行它:motorcycles2html:process_to_file('result_xs.html', 'motorcycles2.xml').
结果将是 result_xs.html。当输入文件的结构与之前的 "motorcycles" XML 文件相同,但它有更多的 'bike' 元素并且 'manufacturer' 元素不是按顺序排列时。