完整理解XML领域(耗费心血,欢迎纠错)
http://my.oschina.net/xpbug/blog/104412
3月21日 深圳 OSC 源创会正在报名中,送华为海思开发板
每个人都知道什么是XML,也知道它的格式.如果深入点理解如何使用XML,可能就不是每个人都知道的了. XML是一种自描述性文档,它的作用是内容的承载,和展示没有任何关系.所以,如何将XML里的数据以合理的方式取出展示,是XML编程的主要部分. 这篇文章从广度上来描述XML的一切特性.
XML有一大堆的官方文档和Spec文档以及教程.但是它们都太专业,文字太官方,又难懂,文字多,例子少,篇幅分散且跨度大. 于是需要一篇小文章,以通俗的话语以概括的角度来阐述XML领域的技术.再给几个小的example. 这就是我写这篇文章的原因.写它也是为了自我学习总结.
本文所用的代码结构如下图:
首先确定这篇文章使用的XML例子,后面所有的代码都基于此例.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<
@H_301_103@bookStore
name
=
"java"
xmlns
=
"http://joey.org/bookStore"
xmlns:audlt
=
"http://japan.org/book/audlt"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation
=
"bookStore.xsd"
>
|
XML的作用
XML的格式
- 首先第一行为XML的声明:
1 - 紧跟着可能会有DTD校验方法.
1 - 如果XML想依托工具自动展现,需要XML展现方法. CSS或者XSLT.
- Element所构成的树形结构.
- Element上的namespace.
- 除了用DTD验证方法,也可以Element上使用XSD来校验XML的合法性.
XML字符编码
XML存储时所使用的字符编码. 这个编码告诉解析程序应该使用什么编码格式来对XML解码. 为了国际通用,使用UTF-8吧. 对于纯英文,UTF8只需要一个字节来表示一个英文字符. XML的size也不会太大.
XML命名空间
命名空间语法包括声明部分 默认命名xmlns="<URL>"或者指定命名xmlns:prefix="http://<namespace specification URL>" 和 使用部分<prefix:tag>或者<tag prefix:attr="">.
命名空间解决了两个问题.
- 相同名称的标签表示不同的意义,它们各自存在与自己的命名空间中.比如<table>即可以表示表格,也可以表示桌子. 给他们一个命名空间. <n1:table>为表单,<n2:table>为桌子.
- 对既有的元素进行属性扩展或者元素扩展. 比如本文例子中的<book>多了audlt的属性和子元素.它是对原来元素的扩展.
在Java或者JavaScript中是使用namespace的,注意以下几点:
- DOM中存在两个方法getElementsByTagName()和getElementsByTagNameNS(). 第一个方法需要使用qualified name作为参数,而第二个方法需要使用namespace和localname作为参数. 如下
12document.getElementsByTagNameNS(
"http://japan.org/book/audlt"
,
"age"
);
document.getElementsByTagName(
"audlt:age"
);
- 如果XML里面使用了namespace,那么XSLT和XPATH也必须使用同等的namespace,否则xpath将搜索不到你想查找的元素,在java的Xpath中,需要设置NamespaceContext. 请看DOM实例和我写的XSL文件.
XML语法验证
验证XML合法性靠的是DTD或者XSD.这是XML的两个规范. XSD比DTD要新,所以也先进.
DTD
本文中的XML里面声明了DTD的引用,XML parser就会自动加载DTD来验证XML. 这需要给parser设定两个前提.一是开启了验证模式,而是明白DTD的加载位置. XML parser可以是JS,java或者browser. 加载位置可以使用PUBLIC ID或者SYSTEM ID来判断.请看下面的声明:
1
|
|
上面的声明没有PUBLIC ID,只有SYSTEM ID,SYSTEM ID=XML当前路径+"/bookStore.dtd". 可见system id是一个相对与XML的路径.
声明PUBLIC ID:
PUBLIC ID也为"bookStore.dtd". 这时候,Parser会自动根据这两个ID去尝试加载DTD文件,如果加载不到,则抛出exception. JAVA中,我们可以通过实现EntityResolver接口的方法来自定义DTD的所在位置. 详情请看JAVA部分.
本文用的DTD是:
1
2
3
4
5
6
7
8
9
|
|
XSD
使用XSD来验证XML只需要一个XSD的定义文件,开启Parser的XSD验证功能. XSD的验证方法在后面的JAVA代码中可以看到. 本文使用的XSD如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
|
XML查询方法(XPath) 略.
XML展示方法(CSS,XSL)
如下面的代码片段所示,XML可以有stylesheet转换成其他格式,如HTML,TXT等. stylesheet可以是css,也可以是xsl.
1
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
xmlns:xsl
=
"http://www.w3.org/1999/XSL/Transform"
xmlns:b
=
"http://joey.org/bookStore"
xmlns:a
=
"http://japan.org/book/audlt"
>
<
@H_301_103@xsl:output
method
=
"html"
version
=
"1.0"
encoding
=
"UTF-8"
indent
=
"yes"
></
@H_301_103@xsl:output
>
<
@H_301_103@h2
>Book Store<<<
@H_301_103@xsl:value-of
select
=
"/b:bookStore/@name"
></
@H_301_103@xsl:value-of
>>></
@H_301_103@h2
>
There are <
@H_301_103@xsl:value-of
select
=
"count(/b:bookStore/b:books/b:book)"
></
@H_301_103@xsl:value-of
> books.
Keeper of this store is <
@H_301_103@xsl:value-of
select
=
"/b:bookStore/b:keeper/b:name"
></
@H_301_103@xsl:value-of
>
<
@H_301_103@span
>title=<
@H_301_103@xsl:value-of
select
=
"b:title"
></
@H_301_103@xsl:value-of
></
@H_301_103@span
>;
<
@H_301_103@span
>author=<
@H_301_103@xsl:value-of
select
=
"b:author"
></
@H_301_103@xsl:value-of
></
@H_301_103@span
>
|
XML与javascript
Javascript对XML的支持在IE和FF+Chrome上是不同的. IE使用的ActiveXObject来生成一个XML的实例.FF与Chrome等其它主流浏览器均遵循w3c规范. 生成的XML document可以使用其DOM方法对dom tree进行操作. 也可以借助框架dojo,jquery等简化操作.
下面这个例子是使用JS对XML进行XSLT转化,从而生成HTML.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
function
createXMLDoc(xmlStr) {
var
xmlDoc;
if
(window.DOMParser) {
// FF Chrome
var
parser=
new
DOMParser();
xmlDoc=parser.parseFromString(xmlStr,
"text/xml"
);
}
else
if
(window.ActiveXObject){
// Internet Explorer
xmlDoc=
new
ActiveXObject(
"Microsoft.XMLDOM"
);
xmlDoc.async=
"false"
;
xmlDoc.loadXML(xmlStr);
}
return
xmlDoc;
}
function
transform(xmlDoc,xslDoc) {
if
(window.XSLTProcessor) {
// chrome FF
var
xslp =
new
XSLTProcessor();
xslp.importStylesheet(xslDoc);
return
xslp.transformToFragment(xmlDoc,document);
}
else
if
(window.ActiveXObject){
// IE
return
xmlDoc.transformNode(xslDoc);
}
}
var
xmlStr =
[
'<bookStore name="java" xmlns="http://joey.org/bookStore" xmlns:audlt="http://japan.org/book/audlt" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="bookStore.xsd">'
,
'<keeper><name>Joey</name></keeper>'
,
'<books>'
,
'<book id="1"> <title>XML</title><author>Steve</author></book>'
,
'<book id="2"><title>JAXP</title> <author>Bill</author></book>'
,
'<book id="3" audlt:color="yellow"><audlt:age> >18 </audlt:age> <title>Love</title><author>teacher</author></book>'
,
'</books></bookStore>'
].join(
''
);
var
xslStr =
[
'<?xml version="1.0" encoding="UTF-8"?>'
,
'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:b="http://joey.org/bookStore" xmlns:a="http://japan.org/book/audlt">'
,
'<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />'
,
'<xsl:template match="/">'
,
'<html>'
,
'<body>'
,
'<h2>Book Store<<<xsl:value-of select="/b:bookStore/@name"/>>></h2>'
,
'<div>There are <xsl:value-of select="count(/b:bookStore/b:books/b:book)"/> books.</div>'
,
'<div>Keeper of this store is <xsl:value-of select="/b:bookStore/b:keeper/b:name"/></div>'
,
'<xsl:for-each select="/b:bookStore/b:books/b:book">'
,
'<div>Book: '
,
'<span>title=<xsl:value-of select="b:title"/></span>;<span>author=<xsl:value-of select="b:author"/></span>'
,
'<xsl:if test="@a:color">'
,
'<span color="yellow">H Book,require age<xsl:value-of select="a:age"/></span>'
,
'</xsl:if>'
,
'</div>'
,
'</xsl:for-each>'
,
'</body>'
,
'</html>'
,
'</xsl:template>'
,
'</xsl:stylesheet>'
].join(
''
);
var
xmlDoc = createXMLDoc(xmlStr);
var
xslDoc = createXMLDoc(xslStr);
var
dom = transform(xmlDoc,xslDoc);
console.log(dom.childNodes[0].outerHTML);
|
XML与java
Java对XML的支持被称为JAXP(Java API for XML Processing). JAXP被当做标准,放入了J2SE1.4.从此以后,JRE自带XML的处理类库. 当然,JAXP允许使用第三方的XML Parser,不同的parser有着不同的优缺点,用户可以自己选择. 但所有的Parser均必须实现JAXP所约定的Interface. 掌握JAXP,需要知道以下内容. 这些都会在后面进行描述.
- JAXP的parser以及如何使用第三方parser.
- XML的解析方法SAX,DOM以及STAX.
- XML的写出方法STAX和XSLT.
- 使用XPath搜索DOM.
- JAXP使用XSLT转换XML.
- DOM与JDOM,DOM4J的区别.
- JAXP验证XML.
- JAXP支持namespace
- javax.xml.parsers - 为各种第三方parser提供了接口.
- org.w3c.dom - 提供了DOM类
- org.xml.sax - 提供了SAX类
- javax.xml.transform - 提供了XSLT的API.
- javax.xml.stream - 提供了STAX的API. STAX比SAX简单,比DOM快.
- javax.xml.xpath - 使用xpath对DOM进行字段查询.
每个接口与类的使用方法就不使用文字描述了,后面会用代码和注释的方式一一介绍JAXP的类库.在描述SAX,StAX,DOM等方法之前,有必要做一个highlevel的比较. 每一个解析方法的优缺点是什么?改如何选择它们.
首先,XML解析器存在SAX,StAX和DOM,而XML文件生成方法又有StAX和DOM. XPath是一个查询DOM的工具. XSLT是转换XML格式的工具. 如下图所示:
XML的解析从数据结构上来讲,分两大类: Streaming和Tree. Streaming又分为SAX和StAX. Tree就是DOM. SAX和StAX均是顺序解析XML,并生成读取事件.我们可以通过监听事件来得到我们想要的内容. DOM是一次性的以tree结构形式载入内存.
Streaming VS DOM
- DOM需要内存.对于大文档或者多文档,DOM性能差.还有,在android手机上就少用DOM这种占内存的东东吧.
- Streaming是实时性的,它没有上下文. 如果一个XML的element需要上下文才能理解,使用DOM会方便.
- 如果XML来自网络,我们对其结构并不明朗,使用Streaming比较好. DOM适合对XML的结构非常清楚.比如web.xml的结构就是一个人人皆知的结构.
- 需要对XML进行增删改查.则使用DOM.
Pull VS Push
- Pull可以让我们的代码掌握主动权,在合适的时候去调用解析器继续工作. Push是被动的听从解析器只会.解析器会不停的读,并把事件push到handler中.
- Pull的代码简单,小.Lib也小.
- Pull可以一个线程同时解析多个文档. 因为主动权在我们.
- StAX可以将一个普通的数据流伪造成一个个XML的读取事件,从而在构造成一个XML.好似DB中的View.
SAX | StAX | DOM | |
API Type | Push,Streaming | Pull,Streaming | Tree,In momery |
Support XPath? | No | No | Yes |
Read XML | Yes | Yes | Yes |
Write XML | No | Yes | Yes |
CRUD | No | No | Yes |
Parsing Validation (DTD,XSD) |
Yes | Optional (JDK embedded |
Yes |
javax.xml.validation包提供了跟XML解析独立与解析过程的验证方法. 性能比不过Parsing Validation. Parsing validation指的是在解析过程中进行验证.
SAX实例
SAXParser是调用XMLReader的,如果使用SAXParser,则需要传参DefaultHandler. DefaultHandler实现了上图的4个Handler接口. 你也可以直接使用XMLReader,然后调用它的parser方法.只是在parser前,需set每个Handler. SAXParser是Event-Driven设计模式,随着读取XML的字节,随着传递event给handler来处理.
读的工作其实是有XMLReader来做的,所有的events也是XMLReader产生的.所以,将一个非XML格式的文件模拟成一个XML,只需要复写XMLReader,读取非XML文件时,发出假的Event,这样handler将会把这个文件当做一个XML来处理. 这种机制会在XSLT中用到.
关于模拟XML
SAX可以将一个非XML格式文件的读取模拟成一个XML的文件的读取.通过构造XML的读取Event. 只是SAX需要复写XMLReader.
ContentHandler
用于处理XML的各种数据类型的读取事件.这里面的事件有
- setDocumentLocator. 读取<?xml ...?>
- startDocument and endDocument. XML的最外层tag的开始与结束.
- startPrefixMapping and endPrefixMapping. 命名空间影响范围的进入与退出.
- startElement and endElement. 每个Element的开始与结束.
- characters. 读取Element的textnode value.
ErrorHandler
用于处理XML解析阶段所发生的警告和错误.里面有三个方法,warning(),error()和fatalError(). waring和error用于处理XML的validation(DTD或XSD)错误.这种错误并不影响XML的解析,你可以把这种错误产生的exception压下来,而不向上抛.这样XML的解析不会被终断. fatalError是XML结构错误,这种错误无法被压制,即使我的handler不抛,Parser会向外抛exception.
DTDHandler
DTD定义中存在ENTITY和NOTATION.这都属于用户自定义属性. XML Parser无法理解用户自定义的ENTITY或者NOTATION,于是它把这方面的验证工作交给了DTDHandler. DTDHandler里面只有2个方法:notationDecl和unparsedEntityDecl. 我们实现这两个方法来验证我们的NOTATION部分是否正确.
EntityResolver
在XML的验证段落里面提到过DTD的定位. EntityResolver可以帮助我们做这件事情. EntityResolver里面只有一个方法,叫做ResolveEntity(publicId,systemId). 每当Parser需要使用external文件的时候,就会调用这个方法. 我们可以在这个方法里面做一些预处理. 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
MyEntityResolver
implements
EntityResolver {
@Override
public
InputSource resolveEntity(String publicId,String systemId)
throws
SAXException,IOException {
if
(
"bookStore.dtd"
.equals(publicId)) {
InputStream in =
this
.getClass().getResourceAsStream(
"/jaxp/resources/bookStore.dtd"
);
InputSource is =
new
InputSource(in);
return
is;
}
return
null
;
}
}
|