完整理解XML领域(耗费心血,欢迎纠错)

每一个人都知道什么是XML,也知道它的格式.若是深刻点理解如何使用XML,可能就不是每一个人都知道的了. XML是一种自描述性文档,它的做用是内容的承载,和展现没有任何关系.因此,如何将XML里的数据以合理的方式取出展现,是XML编程的主要部分. 这篇文章从广度上来描述XML的一切特性.  javascript

XML有一大堆的官方文档和Spec文档以及教程.可是它们都太专业,文字太官方,又难懂,文字多,例子少,篇幅分散且跨度大. 因而须要一篇小文章,以通俗的话语以归纳的角度来阐述XML领域的技术.再给几个小的example. 这就是我写这篇文章的缘由.写它也是为了自我学习总结. css

本文所用的代码结构以下图: html

首先肯定这篇文章使用的XML例子,后面全部的代码都基于此例. 前端

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="test/xsl" href="bookStore.xsl"?>
<!DOCTYPE bookStore PUBLIC "bookStore.dtd" "bookStore.dtd">
<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> &gt;18 </audlt:age>
        <title>Love</title>
        <author>teacher</author>
    </book>
  </books>
</bookStore>


XML的做用

  1. 一种文档格式.只是内容的载体.
  2. 经常使用来作数据存储,数据传输或者配置描述.
  3. 它不负责展现.至于里面的内容如何使用,由XML程序来控制.

XML的格式

  1. 首先第一行为XML的声明: 
    <?xml version="1.0" encoding="uft-8">
  2. 紧跟着可能会有DTD校验方法. 
    <!DOCTYPE root-element SYSTEM "filename">
  3. 若是XML想依托工具自动展示,须要XML展示方法. CSS或者XSLT.
    <?xml-stylesheet type="text/css" href="cd_catalog.css"?>
    或者
    <?xml-stylesheet type="text/xsl" href="simple.xsl"?>
  4. Element所构成的树形结构.
  5. Element上的namespace.
  6. 除了用DTD验证方法,也能够Element上使用XSD来校验XML的合法性.
    <note xmlns="http://www.w3schools.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3schools.com note.xsd">
      ...
    </note>

XML字符编码

XML存储时所使用的字符编码. 这个编码告诉解析程序应该使用什么编码格式来对XML解码. 为了国际通用,使用UTF-8吧. 对于纯英文,UTF8只须要一个字节来表示一个英文字符. XML的size也不会太大. java

XML命名空间

命名空间语法包括声明部分 默认命名xmlns="<URL>"或者指定命名xmlns:prefix="http://<namespace specification URL>" 和 使用部分<prefix:tag>或者<tag prefix:attr="">. node

命名空间解决了两个问题. jquery

  1. 相同名称的标签表示不一样的意义,它们各自存在与本身的命名空间中.好比<table>便可以表示表格,也能够表示桌子. 给他们一个命名空间. <n1:table>为表单,<n2:table>为桌子.
  2. 对既有的元素进行属性扩展或者元素扩展. 好比本文例子中的<book>多了audlt的属性和子元素.它是对原来元素的扩展.

在Java或者JavaScript中是使用namespace的, 注意如下几点: android

  1. DOM中存在两个方法getElementsByTagName()和getElementsByTagNameNS(). 第一个方法须要使用qualified name做为参数,而第二个方法须要使用namespace和localname做为参数. 以下
    document.getElementsByTagNameNS("http://japan.org/book/audlt", "age");
    document.getElementsByTagName("audlt:age");

  2. 若是XML里面使用了namespace, 那么XSLT和XPATH也必须使用同等的namespace,不然xpath将搜索不到你想查找的元素,在java的Xpath中,须要设置NamespaceContext. 请看DOM实例和我写的XSL文件.

XML语法验证

验证XML合法性靠的是DTD或者XSD.这是XML的两个规范. XSD比DTD要新,因此也先进. web

DTD

本文中的XML里面声明了DTD的引用,XML parser就会自动加载DTD来验证XML. 这须要给parser设定两个前提.一是开启了验证模式,而是明白DTD的加载位置. XML parser能够是JS,java或者browser. 加载位置可使用PUBLIC ID或者SYSTEM ID来判断.请看下面的声明: chrome

<!DOCTYPE bookStore SYSTEM "bookStore.dtd">

上面的声明没有PUBLIC ID, 只有SYSTEM ID, SYSTEM ID=XML当前路径+"/bookStore.dtd". 可见system id是一个相对与XML的路径.

声明PUBLIC ID:

<!DOCTYPE bookStore PUBLIC "bookStore.dtd" "bookStore.dtd">

PUBLIC ID也为"bookStore.dtd". 这时候,Parser会自动根据这两个ID去尝试加载DTD文件,若是加载不到,则抛出exception. JAVA中,咱们能够经过实现EntityResolver接口的方法来自定义DTD的所在位置. 详情请看JAVA部分.

本文用的DTD是:

<!ELEMENT bookStore (keeper, books)>
<!ATTLIST bookStore name CDATA #REQUIRED>
<!ELEMENT keeper (name)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT books (book)>
<!ELEMENT book (title, author)>
<!ATTLIST book id ID #REQUIRED>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>

XSD

使用XSD来验证XML只须要一个XSD的定义文件,开启Parser的XSD验证功能. XSD的验证方法在后面的JAVA代码中能够看到. 本文使用的XSD以下:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:element name="bookStore" type="bookStoreType" />

    <xsd:complexType name="bookStoreType">
    	<xsd:sequence>
    		<xsd:element name="keeper" type="keeperType"></xsd:element>
    		<xsd:element name="books" type="booksType"></xsd:element>
    	</xsd:sequence>
    	<xsd:attribute name="name" type="xsd:string"></xsd:attribute>
    </xsd:complexType>

    <xsd:complexType name="keeperType">
    	<xsd:sequence>
    		<xsd:element name="name" type="xsd:string"></xsd:element>
    	</xsd:sequence>
    </xsd:complexType>

    <xsd:complexType name="booksType">
    	<xsd:sequence>
    		<xsd:element name="book" type="bookType"></xsd:element>
    	</xsd:sequence>
    </xsd:complexType>

    <xsd:complexType name="bookType">
    	<xsd:sequence>
    		<xsd:element name="title" type="xsd:string"></xsd:element>
    		<xsd:element name="author" type="xsd:string"></xsd:element>
    	</xsd:sequence>
    	<xsd:attribute name="id" type="xsd:int"></xsd:attribute>
    </xsd:complexType>

</xsd:schema>


XML查询方法(XPath) 略.

XML展现方法(CSS, XSL)

以下面的代码片断所示,XML能够有stylesheet转换成其余格式, 如HTML, TXT等. stylesheet能够是css,也能够是xsl.

<?xml-stylesheet type="test/xsl" href="bookStore.xsl"?>
主流browser都已经支持这种转换格式. 除了自动转换,咱们也可使用代码对转换进行控制.咱们能够用java在服务器端进行xslt的转换,也可使用javascript在前端对xml进行xslt转换. 代码在后面都可找到.  书写xsl的时候,namespace必定要注意. xpath必定要和namespace所对应. 我所使用的XSL为:
<?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:output>
	<xsl:template match="/">
		<html>
			<body>
				<h2>Book Store&lt;&lt;<xsl:value-of select="/b:bookStore/@name"></xsl:value-of>&gt;&gt;</h2>
				<div>
					There are <xsl:value-of select="count(/b:bookStore/b:books/b:book)"></xsl:value-of> books.
				</div>
				<div>
					Keeper of this store is <xsl:value-of select="/b:bookStore/b:keeper/b:name"></xsl:value-of>
				</div>
				<xsl:for-each select="/b:bookStore/b:books/b:book">
					<div> Book:
						<span>title=<xsl:value-of select="b:title"></xsl:value-of></span>;
						<span>author=<xsl:value-of select="b:author"></xsl:value-of></span>
						<xsl:if test="@a:color">
							<span style="color:yellow">H Book, require age<xsl:value-of select="a:age"></xsl:value-of></span>
						</xsl:if>
					</div>
				</xsl:for-each>
			</body>
		</html>
	</xsl:template>
</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.

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> &gt;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&lt;&lt;<xsl:value-of select="/b:bookStore/@name"/>&gt;&gt;</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,须要知道如下内容. 这些都会在后面进行描述.

  1. JAXP的parser以及如何使用第三方parser.
  2. XML的解析方法SAX,DOM以及STAX.
  3. XML的写出方法STAX和XSLT.
  4. 使用XPath搜索DOM.
  5. JAXP使用XSLT转换XML.
  6. DOM与JDOM,DOM4J的区别.
  7. JAXP验证XML.
  8. JAXP支持namespace
J2SE的JAXP提供了5个包,用于支持XML.
  1. javax.xml.parsers - 为各类第三方parser提供了接口. 
  2. org.w3c.dom - 提供了DOM类
  3. org.xml.sax - 提供了SAX类
  4. javax.xml.transform - 提供了XSLT的API.
  5. javax.xml.stream - 提供了STAX的API. STAX比SAX简单,比DOM快.
  6. 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

  1. DOM须要内存.对于大文档或者多文档,DOM性能差.还有,在android手机上就少用DOM这种占内存的东东吧.
  2. Streaming是实时性的,它没有上下文. 若是一个XML的element须要上下文才能理解,使用DOM会方便.
  3. 若是XML来自网络,咱们对其结构并不明朗,使用Streaming比较好. DOM适合对XML的结构很是清楚.好比web.xml的结构就是一我的人皆知的结构.
  4. 须要对XML进行增删改查.则使用DOM.
Streaming又包含SAX和StAX, SAX是推(push)解析方法,而StAX是拉(pull)解析方法. 后面有SAX和StAX的实例.

Pull VS Push

  1. Pull可让咱们的代码掌握主动权,在合适的时候去调用解析器继续工做. Push是被动的遵从解析器只会.解析器会不停的读,并把事件push到handler中.
  2. Pull的代码简单,小.Lib也小.
  3. Pull能够一个线程同时解析多个文档. 由于主动权在咱们.
  4. 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
Parser does not support it).

Yes

javax.xml.validation包提供了跟XML解析独立与解析过程的验证方法. 性能比不过Parsing Validation. Parsing validation指的是在解析过程当中进行验证. 

SAX实例

借用oracle网上的一张图来讲明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的各类数据类型的读取事件.这里面的事件有

  1. setDocumentLocator. 读取<?xml ...?>
  2. startDocument and endDocument. XML的最外层tag的开始与结束.
  3. startPrefixMapping and endPrefixMapping. 命名空间影响范围的进入与退出.
  4. startElement and endElement. 每一个Element的开始与结束.
  5. 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文件的时候,就会调用这个方法. 咱们能够在这个方法里面作一些预处理. 代码以下:

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;
	}
}

SAX Parser的使用

请注意里面是如何开启validation模式的. XSD有两种开启方法.

public class MySAX {
	private SAXParser parser;

	public static void main(String[] args) throws Exception {
        new MySAX();
	}
	
	public MySAX() throws ParserConfigurationException, SAXException, IOException {
		// Use "javax.xml.parsers.SAXParserFactory" system property to specify a Parser.
		// java -Djavax.xml.parsers.SAXParserFactory=yourFactoryHere [...]
		// If property is not specified, use J2SE default Parser.
		// The default Parser is "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl".
		SAXParserFactory spf = SAXParserFactory.newInstance();
		spf.setNamespaceAware(true);
		
		// Use XSD defined by JAXP 1.3, JAVA1.5
		//SchemaFactory sf = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
		//spf.setSchema(sf.newSchema(this.getClass().getResource("/jaxp/resources/bookStore.xsd")));
		// or Use old way defined by JAXP 1.2
		// parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage","http://www.w3.org/2001/XMLSchema");
		// parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", new File("schema.xsd"));

// XSD disabled, use DTD. spf.setValidating(true); this.parser = spf.newSAXParser();

// You can directly use SAXParser to parse XML. Or use XMLReader. // SAXParser warps and use XMLReader internally. // I will use XMLReader here. //this.parser.parse(InputStrean, DefaultHandler); XMLReader reader = this.parser.getXMLReader(); reader.setContentHandler(new MyContentHandler()); reader.setDTDHandler(new MyDTDHandler()); reader.setErrorHandler(new MyErrorHandler()); reader.setEntityResolver(new MyEntityResolver()); InputStream in = this.getClass().getResourceAsStream("/jaxp/resources/bookStore.xml"); InputSource is = new InputSource(in); is.setEncoding("UTF-8"); reader.parse(is); } }


DOM实例 + XPath

借用oracle的图片来讲明DOM解析的架构.

JAVA对XML的解析标准存在DOM, JDOM, DOM4J. 有人认为JDOM和DOM4J都是DOM的另外一种实现方法,这是错误的. 

  1. DOM是XML的数据模型标准,它跨越java,javascript等一切语言和平台. 
  2. 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类似的地方有:
  1. 开启DTD或者XSD validation的方法.
  2. 都用到ErrorHandler处理parser error和EntityResolver处理external引用.
  3. 使用SAXException.但这都不意味着DomBuilder内部使用了SAXParser.

获得DOM数据模型之后,可使用DOM的遍历方法来寻找元素,也可使用XPATH来查找指定元素,XPath的重点注意事项是NamespaceContext.  接下来是DOM的code实例.

public class MyDOM {

    public static void main(String[] args) throws Exception {
        new MyDOM();
    }

    public MyDOM() throws Exception {
        // Use "javax.xml.parsers.DocumentBuilderFactory" system property to specify a Parser.
        // java -Djavax.xml.parsers.DocumentBuilderFactory=yourFactoryHere [...]
        // If property is not specified, use J2SE default Parser.
        // The default Parser is "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl".
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        
        dbf.setIgnoringComments(false);
        dbf.setNamespaceAware(true);
        dbf.setIgnoringElementContentWhitespace(true);
        
        // Use XSD defined by JAXP 1.3, JAVA1.5
        // SchemaFactory sf = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
        // dbf.setSchema(sf.newSchema(this.getClass().getResource("/jaxp/resources/bookStore.xsd")));
        // or Use old way defined by JAXP 1.2
        // dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage","http://www.w3.org/2001/XMLSchema");
        // dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", new File("schema.xsd"));
        // dbf.setSchema(schema);
        
        // XSD disabled, use DTD.
        dbf.setValidating(true);
        
        DocumentBuilder db = dbf.newDocumentBuilder();
        db.setErrorHandler(new MyErrorHandler());
        db.setEntityResolver(new MyEntityResolver());
        
        Document document = db.parse(this.getClass().getResourceAsStream("/jaxp/resources/bookStore.xml"));
        
        // Operate on Document according to DOM module.
        NodeList list = document.getElementsByTagNameNS("http://joey.org/bookStore", "book");
        System.out.println(list.item(2).getAttributes().item(0).getLocalName());
        // Node that if you don't specify name space, you need to use Qualified Name.
        System.out.println(document.getElementsByTagName("audlt:age").item(0).getTextContent());

        // Use xpath to query xml
        XPathFactory xpf = XPathFactory.newInstance();
        XPath xp = xpf.newXPath();
        // Need to set a namespace context.
        NamespaceContext nc = new NamespaceContext() {

            @Override
            public String getNamespaceURI(String prefix) {
                if (prefix.equals("b")) return "http://joey.org/bookStore";
                if (prefix.equals("a")) return "http://japan.org/book/audlt";
                return null;
            }

            @Override
            public String getPrefix(String namespaceURI) {
                if (namespaceURI.equals("http://joey.org/bookStore")) return "b";
                if (namespaceURI.equals("http://japan.org/book/audlt")) return "a";
                return null;
            }

            @Override
            public Iterator getPrefixes(String namespaceURI) {
                return null;
            }
            
        };
        xp.setNamespaceContext(nc);
        System.out.println(xp.evaluate("/b:bookStore/@name", document));
        System.out.println(xp.evaluate("/b:bookStore/b:books/b:book[@id=3]/@a:color", document));
    }
}

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实体来打交道,实现了松耦合. 这是个人理解:

  1. Cursor API比Iterator API更底层. 
  2. Iterator API对Event封装的比较好,隔离了逻辑层和XML解析层.实现了松耦合.逻辑层只须要focus在event数据自己上.
  3. Iterator API更简单.推荐使用.
  4. 使用Iterator API很容易实现将普通文本格式的内容假装转化成一个XML格式的文件.
下面代码分别用Cursor API和Iterator API对XML解析,而后再从新生成写到JAVA Console.

public class MyStAX {

    public static void main(String[] args) throws Exception {
        coursorAPIReadWrite();
        eventAPIReadWrite();
    }
    
    // use cursor API to read and write XML.
    public static void coursorAPIReadWrite() throws Exception {
        XMLInputFactory xif = XMLInputFactory.newInstance();
        // Set properties for validation, namespace...
        // But, JDK embeded StAX parser does not support validation.
        //xif.setProperty(XMLInputFactory.IS_VALIDATING, true);
        xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true);
        
        // Handle the external Entity.
        xif.setXMLResolver(new XMLResolver() {
            public Object resolveEntity(String publicID, String systemID,
                    String baseURI, String namespace) throws XMLStreamException {
                if (publicID.equals("bookStore.dtd")) {
                    return Class.class.getResourceAsStream("/jaxp/resources/bookStore.dtd");
                }
                return null;
            }
        });
        
        XMLOutputFactory xof = XMLOutputFactory.newInstance();
        // Set namespace repairable. Sometimes it will bring you bug. Use it carefully.
        // xof.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
          
        InputStream sourceIn = Class.class.getResourceAsStream("/jaxp/resources/bookStore.xml");
        OutputStream targetOut = System.out; //new FileOutputStream(new File("target.xml"));
        
        XMLStreamReader reader = xif.createXMLStreamReader(sourceIn);
        XMLStreamWriter writer = xof.createXMLStreamWriter(targetOut, reader.getEncoding());
        writer.writeStartDocument(reader.getEncoding(), reader.getVersion());
        
        while (reader.hasNext()) {
            int event = reader.next();
            switch (event) {
            case XMLStreamConstants.DTD:
                out(reader.getText());
                writer.writeCharacters("\n");
                writer.writeDTD(reader.getText());
                writer.writeCharacters("\n");
                break;
            case XMLStreamConstants.PROCESSING_INSTRUCTION:
                out(reader.getPITarget());
                writer.writeCharacters("\n");
                writer.writeProcessingInstruction(reader.getPITarget(), reader.getPIData());
                break;
            case XMLStreamConstants.START_ELEMENT:
                out(reader.getName());
                NamespaceContext nc = reader.getNamespaceContext();
                writer.setNamespaceContext(reader.getNamespaceContext());
                writer.setDefaultNamespace(nc.getNamespaceURI(""));
                writer.writeStartElement(reader.getPrefix(), reader.getLocalName(), reader.getNamespaceURI());

                for (int i=0; i<reader.getAttributeCount(); i++) {
                    QName qname = reader.getAttributeName(i);
                    String name=qname.getLocalPart();
                    if (qname.getPrefix()!=null && !qname.getPrefix().equals("")) {
                        //name = qname.getPrefix()+":"+name;
                    }
                    writer.writeAttribute(name, reader.getAttributeValue(i));
                }
                for (int i=0; i<reader.getNamespaceCount(); i++) {
                    writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
                }
                break;
            case XMLStreamConstants.ATTRIBUTE: 
                out(reader.getText());
                break;
            case XMLStreamConstants.SPACE:
                out("SPACE");
                writer.writeCharacters("\n");
                break;
            case XMLStreamConstants.CHARACTERS:
                out(reader.getText());
                writer.writeCharacters(reader.getText());
                break;
            case XMLStreamConstants.END_ELEMENT:
                out(reader.getName());
                writer.writeEndElement();
                break;
            case XMLStreamConstants.END_DOCUMENT:
                writer.writeEndDocument();
                break;
             default: 
                 out("other");
                 break;
            
            }
        }
        writer.close();
        reader.close();
        
    }
    
    public static void eventAPIReadWrite() throws Exception {
        XMLInputFactory xif = XMLInputFactory.newInstance();
        xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true);
        // Handle the external Entity.
        xif.setXMLResolver(new XMLResolver() {
                    public Object resolveEntity(String publicID, String systemID,
                            String baseURI, String namespace) throws XMLStreamException {
                        if (publicID.equals("bookStore.dtd")) {
                            return Class.class.getResourceAsStream("/jaxp/resources/bookStore.dtd");
                        }
                        return null;
                    }
                });
        XMLOutputFactory xof = XMLOutputFactory.newInstance();
        
        InputStream sourceIn = Class.class.getResourceAsStream("/jaxp/resources/bookStore.xml");
        OutputStream targetOut = System.out;
        XMLEventReader reader = xif.createXMLEventReader(sourceIn);
        XMLEventWriter writer = xof.createXMLEventWriter(targetOut);
        
        while(reader.hasNext()) {
            XMLEvent event = reader.nextEvent();
            out(event.getEventType());
            writer.add(event);
        }
        reader.close();
        writer.close();
    }
    
    public static void out(Object o) {
        System.out.println(o);
    }

}

XSLT实例

上面了解了SAX,DOM和STAX,它们均为XML解析方法. 其中SAX只适合解析读取. DOM则是XML内存中的数据展示. STAX能够解析,也能够写出到文件系统. 

若是将DOM从内存输出XML文件. 若是须要将一个XML文件转换成一个HTML或任意其余格式文件,则须要JAXP的XSLT特性. 这里的转换包括:

  1. 两个结构不一样的DOM相互转换. DOMSouce -----> DOMResult
  2. DOM输出到XML.  DOMSource -----> StreamResult
  3. DOM转化成另外一种格式文件,好比HTML.  DOMSource ---(XSL)--->StreamResult.
  4. XML文件转换成另外一种格式文件.  SAXSource|StreamSource ---(XSL)---->StreamResult
  5. XML文件到DOM. SAXSource|StreamSouce ------> DOMResult
  6. DOM到另外一个SAX事件 DOMSource------>SAXResult

XSLT的下面包含了4个包:

  1. javax.xml.transform - 定义了Transformer类,调用Transformer的transform(source, result)方法,能够进行XML的转换.
  2. javax.xml.transform.sax - 里面定义了SAXSource和SAXResult.
  3. javax.xml.transfrom.dom - 定义了DOMSource和DOMResult.
  4. javax.xml.transform.stream - 定义了StreamSource和StreamResult.
  5. 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. 终于成功了.

public class MyXSLT {
    TransformerFactory tff;
    
    public static void main(String[] args) throws Exception {
        MyXSLT xslt = new MyXSLT();
        xslt.xml2html();
        xslt.str2xml();
    }
    
    public MyXSLT() {
        tff = TransformerFactory.newInstance();
    }
    
    public void xml2html() throws Exception {
        Transformer tr = tff.newTransformer(new SAXSource(new InputSource(this.getClass().getResourceAsStream("/jaxp/resources/bookStore.xsl"))));
        
        SAXParserFactory spf = SAXParserFactory.newInstance();
        SAXParser parser = spf.newSAXParser();
        parser.getXMLReader().setEntityResolver(new 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;
            }
        });
        Source source = new SAXSource(parser.getXMLReader(), new InputSource(this.getClass().getResourceAsStream("/jaxp/resources/bookStore.xml")));
        Result target = new StreamResult(System.out);
        tr.transform(source, target);
    }
    
    // "[joey,bill,cat]" will be transformed to 
    // <test><name>joey</name><name>bill</name><name>cat</name></test>
    public void str2xml() throws Exception {
        final String[] names = new String[]{"joey","bill","cat"};
        Transformer tr = tff.newTransformer();
        
        Source source = new SAXSource(new XMLReader() {
            private ContentHandler handler;
            
            @Override
            public void parse(InputSource input) throws IOException,
                    SAXException {
                handler.startDocument();
                handler.startElement("", "test", "test", null);
                for (int i=0; i<names.length; i++) {
                    handler.startElement("", "name", "name", null);
                    handler.characters(names[i].toCharArray(), 0, names[i].length());
                    handler.endElement("", "name", "name");
                }
                handler.endElement("", "test", "test");
                handler.endDocument();
            }

            @Override
            public void parse(String systemId) throws IOException, SAXException {
            }
            
            @Override
            public boolean getFeature(String name)
                    throws SAXNotRecognizedException, SAXNotSupportedException {
                return false;
            }

            @Override
            public void setFeature(String name, boolean value)
                    throws SAXNotRecognizedException, SAXNotSupportedException {
            }

            @Override
            public Object getProperty(String name)
                    throws SAXNotRecognizedException, SAXNotSupportedException {
                return null;
            }

            @Override
            public void setProperty(String name, Object value)
                    throws SAXNotRecognizedException, SAXNotSupportedException {
            }

            @Override
            public void setEntityResolver(EntityResolver resolver) {
            }

            @Override
            public EntityResolver getEntityResolver() {
                return null;
            }

            @Override
            public void setDTDHandler(DTDHandler handler) {
            }

            @Override
            public DTDHandler getDTDHandler() {
                return null;
            }

            @Override
            public void setContentHandler(ContentHandler handler) {
                this.handler = handler;
            }

            @Override
            public ContentHandler getContentHandler() {
                return handler;
            }

            @Override
            public void setErrorHandler(ErrorHandler handler) {
            }

            @Override
            public ErrorHandler getErrorHandler() {
                return null;
            }
        }, new InputSource());
        
        Result target = new StreamResult(System.out);
        tr.transform(source, target);
    }

}
相关文章
相关标签/搜索