完整理解XML领域
每个人都知道什么是XML,也知道它的格式.如果深入点理解如何使用XML,可能就不是每个人都知道的了. XML是一种自描述性文档,它的作用是内容的承载,和展示没有任何关系.所以,如何将XML里的数据以合理的方式取出展示,是XML编程的主要部分. 这篇文章从广度上来描述XML的一切特性.
XML有一大堆的官方文档和Spec文档以及教程.但是它们都太专业,文字太官方,又难懂,文字多,例子少,篇幅分散且跨度大. 于是需要一篇小文章,以通俗的话语以概括的角度来阐述XML领域的技术.再给几个小的example. 这就是我写这篇文章的原因.写它也是为了自我学习总结.
本文所用的代码结构如下图:
首先确定这篇文章使用的XML例子,后面所有的代码都基于此例.
01 <?xml version="1.0" encoding="UTF-8"?> 02 <?xml-stylesheet type="test/xsl" href="bookStore.xsl"?> 03 <!DOCTYPE bookStore PUBLIC "bookStore.dtd" "bookStore.dtd"> 04 <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"> 05 <keeper> 06 <name>Joey</name> 07 </keeper> 08 <books> 09 <book id="1"> 10 <title>XML</title> 11 <author>Steve</author> 12 </book> 13 <book id="2"> 14 <title>JAXP</title> 15 <author>Bill</author> 16 </book> 17 <book id="3" audlt:color="yellow"> 18 <audlt:age> >18 </audlt:age> 19 <title>Love</title> 20 <author>teacher</author> 21 </book> 22 </books> 23 </bookStore>
XML的作用
- 一种文档格式.只是内容的载体.
- 常用来做数据存储,数据传输或者配置描述.
- 它不负责展示.至于里面的内容如何使用,由XML程序来控制.
XML的格式
- 首先第一行为XML的声明:
1 <?xml version="1.0" encoding="uft-8">
- 紧跟着可能会有DTD校验方法.
1 <!DOCTYPE root-element SYSTEM "filename">
- 如果XML想依托工具自动展现,需要XML展现方法. CSS或者XSLT.
1 <?xml-stylesheet type="text/css" href="cd_catalog.css"?> 2 或者 3 <?xml-stylesheet type="text/xsl" href="simple.xsl"?>
- Element所构成的树形结构.
- Element上的namespace.
- 除了用DTD验证方法,也可以Element上使用XSD来校验XML的合法性.
1 <note xmlns="http://www.w3schools.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3schools.com note.xsd"> 2 ... 3 </note>
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作为参数. 如下
1 document.getElementsByTagNameNS("http://japan.org/book/audlt", "age"); 2 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 <!DOCTYPE bookStore SYSTEM "bookStore.dtd">
上面的声明没有PUBLIC ID, 只有SYSTEM ID, SYSTEM ID=XML当前路径+"/bookStore.dtd". 可见system id是一个相对与XML的路径.
声明PUBLIC ID:
1 <!DOCTYPE bookStore PUBLIC "bookStore.dtd" "bookStore.dtd">
PUBLIC ID也为"bookStore.dtd". 这时候,Parser会自动根据这两个ID去尝试加载DTD文件,如果加载不到,则抛出exception. JAVA中,我们可以通过实现EntityResolver接口的方法来自定义DTD的所在位置. 详情请看JAVA部分.
本文用的DTD是:
1 <!ELEMENT bookStore (keeper, books)> 2 <!ATTLIST bookStore name CDATA #REQUIRED> 3 <!ELEMENT keeper (name)> 4 <!ELEMENT name (#PCDATA)> 5 <!ELEMENT books (book)> 6 <!ELEMENT book (title, author)> 7 <!ATTLIST book id ID #REQUIRED> 8 <!ELEMENT title (#PCDATA)> 9 <!ELEMENT author (#PCDATA)>
XSD
使用XSD来验证XML只需要一个XSD的定义文件,开启Parser的XSD验证功能. XSD的验证方法在后面的JAVA代码中可以看到. 本文使用的XSD如下:
01 <?xml version="1.0" encoding="UTF-8"?> 02 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 03 <xsd:element name="bookStore" type="bookStoreType" /> 04 05 <xsd:complexType name="bookStoreType"> 06 <xsd:sequence> 07 <xsd:element name="keeper" type="keeperType"></xsd:element> 08 <xsd:element name="books" type="booksType"></xsd:element> 09 </xsd:sequence> 10 <xsd:attribute name="name" type="xsd:string"></xsd:attribute> 11 </xsd:complexType> 12 13 <xsd:complexType name="keeperType"> 14 <xsd:sequence> 15 <xsd:element name="name" type="xsd:string"></xsd:element> 16 </xsd:sequence> 17 </xsd:complexType> 18 19 <xsd:complexType name="booksType"> 20 <xsd:sequence> 21 <xsd:element name="book" type="bookType"></xsd:element> 22 </xsd:sequence> 23 </xsd:complexType> 24 25 <xsd:complexType name="bookType"> 26 <xsd:sequence> 27 <xsd:element name="title" type="xsd:string"></xsd:element> 28 <xsd:element name="author" type="xsd:string"></xsd:element> 29 </xsd:sequence> 30 <xsd:attribute name="id" type="xsd:int"></xsd:attribute> 31 </xsd:complexType> 32 33 </xsd:schema>
XML查询方法(XPath) 略.
XML展示方法(CSS, XSL)
如下面的代码片段所示,XML可以有stylesheet转换成其他格式, 如HTML, TXT等. stylesheet可以是css,也可以是xsl.
1 <?xml-stylesheet type="test/xsl" href="bookStore.xsl"?>
主流browser都已经支持这种转换格式. 除了自动转换,我们也可以使用代码对转换进行控制.我们可以用java在服务器端进行xslt的转换,也可以使用javascript在前端对xml进行xslt转换. 代码在后面均可找到. 书写xsl的时候,namespace一定要注意. xpath一定要和namespace所对应. 我所使用的XSL为:
01 <?xml version="1.0" encoding="UTF-8"?> 02 <xsl:stylesheet version="1.0" 03 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:b="http://joey.org/bookStore" 04 xmlns:a="http://japan.org/book/audlt"> 05 <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"></xsl:output> 06 <xsl:template match="/"> 07 <html> 08 <body> 09 <h2>Book Store<<<xsl:value-of select="/b:bookStore/@name"></xsl:value-of>>></h2> 10 <div> 11 There are <xsl:value-of select="count(/b:bookStore/b:books/b:book)"></xsl:value-of> books. 12 </div> 13 <div> 14 Keeper of this store is <xsl:value-of select="/b:bookStore/b:keeper/b:name"></xsl:value-of> 15 </div> 16 <xsl:for-each select="/b:bookStore/b:books/b:book"> 17 <div> Book: 18 <span>title=<xsl:value-of select="b:title"></xsl:value-of></span>; 19 <span>author=<xsl:value-of select="b:author"></xsl:value-of></span> 20 <xsl:if test="@a:color"> 21 <span style="color:yellow">H Book, require age<xsl:value-of select="a:age"></xsl:value-of></span> 22 </xsl:if> 23 </div> 24 </xsl:for-each> 25 </body> 26 </html> 27 </xsl:template> 28 </xsl:stylesheet>
XML与javascript
Javascript对XML的支持在IE和FF+Chrome上是不同的. IE使用的ActiveXObject来生成一个XML的实例.FF与Chrome等其它主流浏览器均遵循w3c规范. 生成的XML document可以使用其DOM方法对dom tree进行操作. 也可以借助框架dojo,jquery等简化操作.
下面这个例子是使用JS对XML进行XSLT转化,从而生成HTML.
01 function createXMLDoc(xmlStr) { 02 var xmlDoc; 03 if (window.DOMParser) { 04 // FF Chrome 05 var parser=new DOMParser(); 06 xmlDoc=parser.parseFromString(xmlStr,"text/xml"); 07 } else if (window.ActiveXObject){ 08 // Internet Explorer 09 xmlDoc=new ActiveXObject("Microsoft.XMLDOM"); 10 xmlDoc.async="false"; 11 xmlDoc.loadXML(xmlStr); 12 } 13 return xmlDoc; 14 } 15 16 function transform(xmlDoc, xslDoc) { 17 if (window.XSLTProcessor) { 18 // chrome FF 19 var xslp = new XSLTProcessor(); 20 xslp.importStylesheet(xslDoc); 21 return xslp.transformToFragment(xmlDoc,document); 22 } else if (window.ActiveXObject){ 23 // IE 24 return xmlDoc.transformNode(xslDoc); 25 } 26 } 27 28 var xmlStr = 29 ['<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">', 30 '<keeper><name>Joey</name></keeper>', 31 '<books>', 32 '<book id="1"> <title>XML</title><author>Steve</author></book>', 33 '<book id="2"><title>JAXP</title> <author>Bill</author></book>', 34 '<book id="3" audlt:color="yellow"><audlt:age> >18 </audlt:age> <title>Love</title><author>teacher</author></book>', 35 '</books></bookStore>'].join(''); 36 37 var xslStr = 38 ['<?xml version="1.0" encoding="UTF-8"?>', 39 '<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">', 40 '<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />', 41 '<xsl:template match="/">', 42 '<html>', 43 '<body>', 44 '<h2>Book Store<<<xsl:value-of select="/b:bookStore/@name"/>>></h2>', 45 '<div>There are <xsl:value-of select="count(/b:bookStore/b:books/b:book)"/> books.</div>', 46 '<div>Keeper of this store is <xsl:value-of select="/b:bookStore/b:keeper/b:name"/></div>', 47 '<xsl:for-each select="/b:bookStore/b:books/b:book">', 48 '<div>Book: ', 49 '<span>title=<xsl:value-of select="b:title"/></span>;<span>author=<xsl:value-of select="b:author"/></span>', 50 '<xsl:if test="@a:color">', 51 '<span color="yellow">H Book, require age<xsl:value-of select="a:age"/></span>', 52 '</xsl:if>', 53 '</div>', 54 '</xsl:for-each>', 55 '</body>', 56 '</html>', 57 '</xsl:template>', 58 '</xsl:stylesheet>'].join(''); 59 60 var xmlDoc = createXMLDoc(xmlStr); 61 var xslDoc = createXMLDoc(xslStr); 62 var dom = transform(xmlDoc, xslDoc); 63 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
J2SE的JAXP提供了5个包,用于支持XML.
- 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.
Streaming又包含SAX和StAX, SAX是推(push)解析方法,而StAX是拉(pull)解析方法. 后面有SAX和StAX的实例.
Pull VS Push
- Pull可以让我们的代码掌握主动权,在合适的时候去调用解析器继续工作. Push是被动的听从解析器只会.解析器会不停的读,并把事件push到handler中.
- Pull的代码简单,小.Lib也小.
- Pull可以一个线程同时解析多个文档. 因为主动权在我们.
- StAX可以将一个普通的数据流伪造成一个个XML的读取事件,从而在构造成一个XML.好似DB中的View.
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的text node value.
实现方式可以参考org.xml.sax.helpers.DefaultHandler.
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文件的时候,就会调用这个方法. 我们可以在这个方法里面做一些预处理. 代码如下:
01 public class MyEntityResolver implements EntityResolver { 02 03 @Override 04 public InputSource resolveEntity(String publicId, String systemId) 05 throws SAXException, IOException { 06 if ("bookStore.dtd".equals(publicId)) { 07 InputStream in = this.getClass().getResourceAsStream("/jaxp/resources/bookStore.dtd"); 08 InputSource is = new InputSource(in); 09 return is; 10 } 11 return null; 12 } 13 }
SAX Parser的使用
请注意里面是如何开启validation模式的. XSD有两种开启方法.
01 public class MySAX { 02 private SAXParser parser; 03 04 public static void main(String[] args) throws Exception { 05 new MySAX(); 06 } 07 08 public MySAX() throws ParserConfigurationException, SAXException, IOException { 09 // Use "javax.xml.parsers.SAXParserFactory" system property to specify a Parser. 10 // java -Djavax.xml.parsers.SAXParserFactory=yourFactoryHere [...] 11 // If property is not specified, use J2SE default Parser. 12 // The default Parser is "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl". 13 SAXParserFactory spf = SAXParserFactory.newInstance(); 14 spf.setNamespaceAware(true); 15 16 // Use XSD defined by JAXP 1.3, JAVA1.5 17 //SchemaFactory sf = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); 18 //spf.setSchema(sf.newSchema(this.getClass().getResource("/jaxp/resources/bookStore.xsd"))); 19 // or Use old way defined by JAXP 1.2 20 // parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage","http://www.w3.org/2001/XMLSchema"); 21 // parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", new File("schema.xsd")); 22 <p> 23 // XSD disabled, use DTD. <span style="font-size:9pt;line-height:1.5;"> spf.setValidating(true);</span><span style="font-size:9pt;line-height:1.5;"> </span><span style="font-size:9pt;line-height:1.5;"> this.parser = spf.newSAXParser();</span> 24 </p> 25 // You can directly use SAXParser to parse XML. Or use XMLReader. 26 // SAXParser warps and use XMLReader internally. 27 // I will use XMLReader here. 28 //this.parser.parse(InputStrean, DefaultHandler); 29 XMLReader reader = this.parser.getXMLReader(); 30 reader.setContentHandler(new MyContentHandler()); 31 reader.setDTDHandler(new MyDTDHandler()); 32 reader.setErrorHandler(new MyErrorHandler()); 33 reader.setEntityResolver(new MyEntityResolver()); 34 35 InputStream in = this.getClass().getResourceAsStream("/jaxp/resources/bookStore.xml"); 36 InputSource is = new InputSource(in); 37 is.setEncoding("UTF-8"); 38 reader.parse(is); 39 } 40 }
DOM实例 + XPath
JAVA对XML的解析标准存在DOM, JDOM, DOM4J. 有人认为JDOM和DOM4J都是DOM的另一种实现方法,这是错误的.
- DOM是XML的数据模型标准,它跨越java,javascript等一切语言和平台.
- JDOM和DOM4J是专门针对java的模型.它简化了DOM,更加容易使用. 比如DOM中可以包含混合元素,即<a>text<b>text</b>test</a>. JDOM和DOM4J只允许<a>text</a>. 此外,DOM的数据访问模型也非常的复杂. 如果你的XML结构简单,可以使用JDOM和DOM4J. DOM4J的性能最好.
这篇文章只讲一下DOM. DOM的code和SAX的code相似的地方有:
- 开启DTD或者XSD validation的方法.
- 都用到ErrorHandler处理parser error和EntityResolver处理external引用.
- 使用SAXException.但这都不意味着DomBuilder内部使用了SAXParser.
得到DOM数据模型以后,可以使用DOM的遍历方法来寻找元素,也可以使用XPATH来查找指定元素,XPath的重点注意事项是NamespaceContext. 接下来是DOM的code实例.
01 public class MyDOM { 02 03 public static void main(String[] args) throws Exception { 04 new MyDOM(); 05 } 06 07 public MyDOM() throws Exception { 08 // Use "javax.xml.parsers.DocumentBuilderFactory" system property to specify a Parser. 09 // java -Djavax.xml.parsers.DocumentBuilderFactory=yourFactoryHere [...] 10 // If property is not specified, use J2SE default Parser. 11 // The default Parser is "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl". 12 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 13 14 dbf.setIgnoringComments(false); 15 dbf.setNamespaceAware(true); 16 dbf.setIgnoringElementContentWhitespace(true); 17 18 // Use XSD defined by JAXP 1.3, JAVA1.5 19 // SchemaFactory sf = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); 20 // dbf.setSchema(sf.newSchema(this.getClass().getResource("/jaxp/resources/bookStore.xsd"))); 21 // or Use old way defined by JAXP 1.2 22 // dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage","http://www.w3.org/2001/XMLSchema"); 23 // dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", new File("schema.xsd")); 24 // dbf.setSchema(schema); 25 26 // XSD disabled, use DTD. 27 dbf.setValidating(true); 28 29 DocumentBuilder db = dbf.newDocumentBuilder(); 30 db.setErrorHandler(new MyErrorHandler()); 31 db.setEntityResolver(new MyEntityResolver()); 32 33 Document document = db.parse(this.getClass().getResourceAsStream("/jaxp/resources/bookStore.xml")); 34 35 // Operate on Document according to DOM module. 36 NodeList list = document.getElementsByTagNameNS("http://joey.org/bookStore", "book"); 37 System.out.println(list.item(2).getAttributes().item(0).getLocalName()); 38 // Node that if you don't specify name space, you need to use Qualified Name. 39 System.out.println(document.getElementsByTagName("audlt:age").item(0).getTextContent()); 40 41 // Use xpath to query xml 42 XPathFactory xpf = XPathFactory.newInstance(); 43 XPath xp = xpf.newXPath(); 44 // Need to set a namespace context. 45 NamespaceContext nc = new NamespaceContext() { 46 47 @Override 48 public String getNamespaceURI(String prefix) { 49 if (prefix.equals("b")) return "http://joey.org/bookStore"; 50 if (prefix.equals("a")) return "http://japan.org/book/audlt"; 51 return null; 52 } 53 54 @Override 55 public String getPrefix(String namespaceURI) { 56 if (namespaceURI.equals("http://joey.org/bookStore")) return "b"; 57 if (namespaceURI.equals("http://japan.org/book/audlt")) return "a"; 58 return null; 59 } 60 61 @Override 62 public Iterator getPrefixes(String namespaceURI) { 63 return null; 64 } 65 66 }; 67 xp.setNamespaceContext(nc); 68 System.out.println(xp.evaluate("/b:bookStore/@name", document)); 69 System.out.println(xp.evaluate("/b:bookStore/b:books/b:book[@id=3]/@a:color", document)); 70 } 71 }
StAX实例
StAX和SAX比较,代码简单,且可以写XML. 但StAX规范对于解析时的validation不是强制的.所以,JDK自带StAX解析器就不支持Parsing Validation.
StAX存在两种API, Cursor API(XMLStreamReader, XMLStreamWriter)和Iterator API(XMLEventReader, XMLEventWriter). Cursor API就是一个像游标一样的读或者写API. 我们得不停的调用XML writer和XML reader来读写XML每一个字段,这是的代码逻辑层和XML解析层交叉在一起,很混乱. Iterator API将逻辑层和XML解析层分离,对Event进行封装,所有的数据都封装在Event中,逻辑层和解析层靠Event实体来打交道,实现了松耦合. 这是我的理解:
- Cursor API比Iterator API更底层.
- Iterator API对Event封装的比较好,隔离了逻辑层和XML解析层.实现了松耦合.逻辑层只需要focus在event数据本身上.
- Iterator API更简单.推荐使用.
- 使用Iterator API很容易实现将普通文本格式的内容伪装转化成一个XML格式的文件.
下面代码分别用Cursor API和Iterator API对XML解析,然后再重新生成写到JAVA Console.
001 public class MyStAX { 002 003 public static void main(String[] args) throws Exception { 004 coursorAPIReadWrite(); 005 eventAPIReadWrite(); 006 } 007 008 // use cursor API to read and write XML. 009 public static void coursorAPIReadWrite() throws Exception { 010 XMLInputFactory xif = XMLInputFactory.newInstance(); 011 // Set properties for validation, namespace... 012 // But, JDK embeded StAX parser does not support validation. 013 //xif.setProperty(XMLInputFactory.IS_VALIDATING, true); 014 xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true); 015 016 // Handle the external Entity. 017 xif.setXMLResolver(new XMLResolver() { 018 public Object resolveEntity(String publicID, String systemID, 019 String baseURI, String namespace) throws XMLStreamException { 020 if (publicID.equals("bookStore.dtd")) { 021 return Class.class.getResourceAsStream("/jaxp/resources/bookStore.dtd"); 022 } 023 return null; 024 } 025 }); 026 027 XMLOutputFactory xof = XMLOutputFactory.newInstance(); 028 // Set namespace repairable. Sometimes it will bring you bug. Use it carefully. 029 // xof.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); 030 031 InputStream sourceIn = Class.class.getResourceAsStream("/jaxp/resources/bookStore.xml"); 032 OutputStream targetOut = System.out; //new FileOutputStream(new File("target.xml")); 033 034 XMLStreamReader reader = xif.createXMLStreamReader(sourceIn); 035 XMLStreamWriter writer = xof.createXMLStreamWriter(targetOut, reader.getEncoding()); 036 writer.writeStartDocument(reader.getEncoding(), reader.getVersion()); 037 038 while (reader.hasNext()) { 039 int event = reader.next(); 040 switch (event) { 041 case XMLStreamConstants.DTD: 042 out(reader.getText()); 043 writer.writeCharacters("\n"); 044 writer.writeDTD(reader.getText()); 045 writer.writeCharacters("\n"); 046 break; 047 case XMLStreamConstants.PROCESSING_INSTRUCTION: 048 out(reader.getPITarget()); 049 writer.writeCharacters("\n"); 050 writer.writeProcessingInstruction(reader.getPITarget(), reader.getPIData()); 051 break; 052 case XMLStreamConstants.START_ELEMENT: 053 out(reader.getName()); 054 NamespaceContext nc = reader.getNamespaceContext(); 055 writer.setNamespaceContext(reader.getNamespaceContext()); 056 writer.setDefaultNamespace(nc.getNamespaceURI("")); 057 writer.writeStartElement(reader.getPrefix(), reader.getLocalName(), reader.getNamespaceURI()); 058 059 for (int i=0; i<reader.getAttributeCount(); i++) { 060 QName qname = reader.getAttributeName(i); 061 String name=qname.getLocalPart(); 062 if (qname.getPrefix()!=null && !qname.getPrefix().equals("")) { 063 //name = qname.getPrefix()+":"+name; 064 } 065 writer.writeAttribute(name, reader.getAttributeValue(i)); 066 } 067 for (int i=0; i<reader.getNamespaceCount(); i++) { 068 writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i)); 069 } 070 break; 071 case XMLStreamConstants.ATTRIBUTE: 072 out(reader.getText()); 073 break; 074 case XMLStreamConstants.SPACE: 075 out("SPACE"); 076 writer.writeCharacters("\n"); 077 break; 078 case XMLStreamConstants.CHARACTERS: 079 out(reader.getText()); 080 writer.writeCharacters(reader.getText()); 081 break; 082 case XMLStreamConstants.END_ELEMENT: 083 out(reader.getName()); 084 writer.writeEndElement(); 085 break; 086 case XMLStreamConstants.END_DOCUMENT: 087 writer.writeEndDocument(); 088 break; 089 default: 090 out("other"); 091 break; 092 093 } 094 } 095 writer.close(); 096 reader.close(); 097 098 } 099 100 public static void eventAPIReadWrite() throws Exception { 101 XMLInputFactory xif = XMLInputFactory.newInstance(); 102 xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true); 103 // Handle the external Entity. 104 xif.setXMLResolver(new XMLResolver() { 105 public Object resolveEntity(String publicID, String systemID, 106 String baseURI, String namespace) throws XMLStreamException { 107 if (publicID.equals("bookStore.dtd")) { 108 return Class.class.getResourceAsStream("/jaxp/resources/bookStore.dtd"); 109 } 110 return null; 111 } 112 }); 113 XMLOutputFactory xof = XMLOutputFactory.newInstance(); 114 115 InputStream sourceIn = Class.class.getResourceAsStream("/jaxp/resources/bookStore.xml"); 116 OutputStream targetOut = System.out; 117 XMLEventReader reader = xif.createXMLEventReader(sourceIn); 118 XMLEventWriter writer = xof.createXMLEventWriter(targetOut); 119 120 while(reader.hasNext()) { 121 XMLEvent event = reader.nextEvent(); 122 out(event.getEventType()); 123 writer.add(event); 124 } 125 reader.close(); 126 writer.close(); 127 } 128 129 public static void out(Object o) { 130 System.out.println(o); 131 } 132 133 }
XSLT实例
上面了解了SAX,DOM和STAX,它们均为XML解析方法. 其中SAX只适合解析读取. DOM则是XML内存中的数据展现. STAX可以解析,也可以写出到文件系统.
如果将DOM从内存输出XML文件. 如果需要将一个XML文件转换成一个HTML或任意其他格式文件,则需要JAXP的XSLT特性. 这里的转换包括:
- 两个结构不同的DOM相互转换. DOMSouce -----> DOMResult
- DOM输出到XML. DOMSource -----> StreamResult
- DOM转化成另一种格式文件,比如HTML. DOMSource ---(XSL)--->StreamResult.
- XML文件转换成另一种格式文件. SAXSource|StreamSource ---(XSL)---->StreamResult
- XML文件到DOM. SAXSource|StreamSouce ------> DOMResult
- DOM到另一个SAX事件 DOMSource------>SAXResult
XSLT的下面包含了4个包:
- javax.xml.transform - 定义了Transformer类,调用Transformer的transform(source, result)方法,可以进行XML的转换.
- javax.xml.transform.sax - 里面定义了SAXSource和SAXResult.
- javax.xml.transfrom.dom - 定义了DOMSource和DOMResult.
- javax.xml.transform.stream - 定义了StreamSource和StreamResult.
- javax.xml.transform.stax - 定义了StAXSource和StAXResult.(java1.6)
从上面可以看出,JAXP可以进行4*4=16种转换方式.(sax, sax), (sax, dom), (sax, stream)...
再高级一点,利用SAXSouce----->DOMResult的转化功能, 和SAX模拟XML读取功能, XSLT可以将一个非XML格式的文件,转换成一个DOM. 下面的代码将包含此例. 代码中还包含另外一个例子,就是把XML按照XSL的格式转换成HTML.
注意, XSLT处理DTD有技巧:
在xml2html的转换中, 使用StreamSource在代码的书写上是最简单的, 但为什么使用了SAXSource? 那是因为要转换的XML中引用了DTD, StreamSource无法处理外部引用, 会导致Transformer抛TransformerException. 失败的异常内容为DTD文件找不到. 那么,在这种情况下,我们只能使用SAXSource,并给它赋予一个可以解析外部DTD引用的XMLReader. 终于成功了.
001 public class MyXSLT { 002 TransformerFactory tff; 003 004 public static void main(String[] args) throws Exception { 005 MyXSLT xslt = new MyXSLT(); 006 xslt.xml2html(); 007 xslt.str2xml(); 008 } 009 010 public MyXSLT() { 011 tff = TransformerFactory.newInstance(); 012 } 013 014 public void xml2html() throws Exception { 015 Transformer tr = tff.newTransformer(new SAXSource(new InputSource(this.getClass().getResourceAsStream("/jaxp/resources/bookStore.xsl")))); 016 017 SAXParserFactory spf = SAXParserFactory.newInstance(); 018 SAXParser parser = spf.newSAXParser(); 019 parser.getXMLReader().setEntityResolver(new EntityResolver() { 020 @Override 021 public InputSource resolveEntity(String publicId, String systemId) 022 throws SAXException, IOException { 023 if ("bookStore.dtd".equals(publicId)) { 024 InputStream in = this.getClass().getResourceAsStream("/jaxp/resources/bookStore.dtd"); 025 InputSource is = new InputSource(in); 026 return is; 027 } 028 return null; 029 } 030 }); 031 Source source = new SAXSource(parser.getXMLReader(), new InputSource(this.getClass().getResourceAsStream("/jaxp/resources/bookStore.xml"))); 032 Result target = new StreamResult(System.out); 033 tr.transform(source, target); 034 } 035 036 // "[joey,bill,cat]" will be transformed to 037 // <test><name>joey</name><name>bill</name><name>cat</name></test> 038 public void str2xml() throws Exception { 039 final String[] names = new String[]{"joey","bill","cat"}; 040 Transformer tr = tff.newTransformer(); 041 042 Source source = new SAXSource(new XMLReader() { 043 private ContentHandler handler; 044 045 @Override 046 public void parse(InputSource input) throws IOException, 047 SAXException { 048 handler.startDocument(); 049 handler.startElement("", "test", "test", null); 050 for (int i=0; i<names.length; i++) { 051 handler.startElement("", "name", "name", null); 052 handler.characters(names[i].toCharArray(), 0, names[i].length()); 053 handler.endElement("", "name", "name"); 054 } 055 handler.endElement("", "test", "test"); 056 handler.endDocument(); 057 } 058 059 @Override 060 public void parse(String systemId) throws IOException, SAXException { 061 } 062 063 @Override 064 public boolean getFeature(String name) 065 throws SAXNotRecognizedException, SAXNotSupportedException { 066 return false; 067 } 068 069 @Override 070 public void setFeature(String name, boolean value) 071 throws SAXNotRecognizedException, SAXNotSupportedException { 072 } 073 074 @Override 075 public Object getProperty(String name) 076 throws SAXNotRecognizedException, SAXNotSupportedException { 077 return null; 078 } 079 080 @Override 081 public void setProperty(String name, Object value) 082 throws SAXNotRecognizedException, SAXNotSupportedException { 083 } 084 085 @Override 086 public void setEntityResolver(EntityResolver resolver) { 087 } 088 089 @Override 090 public EntityResolver getEntityResolver() { 091 return null; 092 } 093 094 @Override 095 public void setDTDHandler(DTDHandler handler) { 096 } 097 098 @Override 099 public DTDHandler getDTDHandler() { 100 return null; 101 } 102 103 @Override 104 public void setContentHandler(ContentHandler handler) { 105 this.handler = handler; 106 } 107 108 @Override 109 public ContentHandler getContentHandler() { 110 return handler; 111 } 112 113 @Override 114 public void setErrorHandler(ErrorHandler handler) { 115 } 116 117 @Override 118 public ErrorHandler getErrorHandler() { 119 return null; 120 } 121 }, new InputSource()); 122 123 Result target = new StreamResult(System.out); 124 tr.transform(source, target); 125 } 126 127 }


