java 核心技术Ⅱ--章三:XML

1、XML格式介绍

XML是一种能够表示层级结构的数据表现格式,下面是使用XML的使用要点:

  • XML是大小写敏感的。
  • XML的结束标签可以写成:/> 但是绝对不能省略。
  • XML中,属性值不管是什么类型的,都必须用引号括起来。
  • XML中,属性值必须有值。
  • 一条常用的经验法则:属性只应该用来修改值得解释,而不是用来指定值。

2、解析XML文档

要处理XML文档,就要先解析它。解析器是这样一个程序:它读入一个文件,确认这个文件具有正确的格式,然后将其分解成各种元素,使得程序员能够访问这些元素。java库提供了两种XML解析器:

  • 像文档对象模型(Document Object Model,DOM)解析器这样的树形解析器,它们将读入的XML文档转成树型结构放入内存中(若XML特别大,要考虑内存问题)。
  • 像XML简单API(Simple API for XML,SAX)解析器这样的流机制解析器,他们在读入文档是会触发相应的事件。

我们先来介绍DOM解析器:

test.xml结构:

<?xml version="1.0" encoding="UTF-8"?>

<XML>
  <HEAD>
    <PAGESIZE SIZE="页">15</PAGESIZE>
    <ORGID>00</ORGID>
  </HEAD>
  <RESULT>
    <RST>
      <CODE>000000</CODE>
      <MSG>交易成功</MSG>
    </RST>
    <FONT>
        <NAME>Times Roman</NAME>
        <SIZE UNITE="ps">15</SIZE>
    </FONT>
  </RESULT>
</XML>

DOM代码:

public class XMLForDomLearn {

    /**
     * 采用递归的方式展示所有DOM节点
     * @param ele 需要展示的节点
     * @param has 是否有子节点
     */
    public static void showXML(Element ele ,boolean has){
        if(has){
            //获取所有子节点
            NodeList children = ele.getChildNodes();
            for(int i = 0;i<children.getLength();i++){
                Node child = children.item(i);
                //去除空白
                if(child instanceof Element){
                    Element childElement = (Element) child;
                    //获取属性列表
                    String atList = getAttributeList(childElement);
                    if(hasNodes(childElement)){
                        System.out.println("<"+childElement.getTagName()+atList+">");
                        showXML(childElement,true);
                        System.out.println("<"+childElement.getTagName()+"/>");
                    }else{
                        showXML(childElement,false);
                    }
                }
            }
        }else{
            String atList = getAttributeList(ele);
            Text textNode = (Text)ele.getFirstChild();
            System.out.println("<"+ele.getTagName()+atList+">"+textNode.getData().trim()+"<"+ele.getTagName()+"/>");
        }

    }

    /**
     * 展示每一个节点的属性列表
     * @param ele 属性所在节点
     * @return 属性列表
     */
    public static String getAttributeList(Element ele ){
        NamedNodeMap nm;
        String atList = "";
        //获得属性列表并遍历
        for(int j =0;j< (nm = ele.getAttributes()).getLength();j++){
            Node attribute = nm.item(j);
            String name = attribute.getNodeName();
            String value = attribute.getNodeValue();
            atList += " "+name+":"+value;
        }
        return atList;
    }

    /**
     * 查看当前节点是否有子节点
     * @param ele 需要查看的节点
     * @return true:有 false:无
     */
    public static boolean hasNodes(Element ele){
        boolean flag = false;
        NodeList children = ele.getChildNodes();
        for(int i = 0;i<children.getLength();i++){
            Node child = children.item(i);
            if(child instanceof Element){
                flag = true;
            }
        }
        return flag;
    }

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
            //使用工厂模式创建DOM解析器
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            File xmlF = new File("doc/test.xml");
            //通过文件获得DOM对象
            Document docFile = builder.parse(xmlF);
            System.out.println(docFile);
            //通过io流获得DOM对象
            Document docIn = builder.parse(new FileInputStream(xmlF));
            System.out.println(docIn);
            //得到根节点 XML
            Element root = docFile.getDocumentElement();
            System.out.println("<"+root.getTagName()+">");
            //展示所有节点
            if(hasNodes(root)){
                showXML(root,true);
            }
            System.out.println("<"+root.getTagName()+"/>");

    }
}

结果:

[#document: null]
[#document: null]
<XML>
<HEAD>
<PAGESIZE SIZE:页>15<PAGESIZE/>
<ORGID>00<ORGID/>
<HEAD/>
<RESULT>
<RST>
<CODE>000000<CODE/>
<MSG>交易成功<MSG/>
<RST/>
<FONT>
<NAME>Times Roman<NAME/>
<SIZE UNITE:ps>15<SIZE/>
<FONT/>
<RESULT/>
<XML/>

3、文档类型定义:DTD

DTD文件是用来对XML文档进行约束规范的。

<FONT>
        <NAME>Times Roman</NAME>
        <SIZE UNITE="ps">15</SIZE>
</FONT>

当解析上面XML时,取得节点的子节点时,会得到5个节点:

  • 和之间的空白字符。
  • 节点。
  • 和之间的空白字符。
  • 节点。
  • 之间的空白字符。

这也是我为什么要用if(child instanceof Element)此行代码进行空白符的过滤,但是我们使用DTD文档类型定义文件的话,就不会出现这个问题。

关于文档类型定义DTD语法的学习,请点击此链接;

使用dtd文件后,不要忘记将工厂的验证特性打开。

factory.setValidating(true);

要想去掉空白字符的话,需要下面代码。

factory.setIgnoringElementContentWhitespace(true);

4、XML Schema

XML Schema与DTD文档的作用一致,都是描述XML文档的目录结构的,只是语法等有一些差别,在这里我就不进行详细的介绍了。

关于文档类型定义XML Schema语法的学习,请点击此链接;

解析带有Schema的XML文件和解析带有DTD的文件相似,但是有3点差别:

  1. 必须打开对命名空间的支持,即使在XML文件里可能不会用到它。

    factory.setNamespaceAware(true);
  2. 必须通过如下的“魔咒”来准备好处理Schema的工厂。

    final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com.xml/jaxp/properties/schemaLaguage";
    final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
    factory.setAttribute(JAXP_SCHEMA_LANGUAGE,W3C_XML_SCHEMA);
  3. 解析器不会丢弃元素中的空白字符。

5、使用XPth来定位信息

如果要定位某个XML文档中的一段特定信息,那么,通过遍历DOM树的众多节点来进行查找会显得有些麻烦。XPath语言使得访问树节点变得很容易。例如有如下XML文档:

test.xml:

<XML>
  <DATA>
    <USERNAME>admin</USERNAME>
    <PASSWORD>abc123</PASSWORD>
  </DATA>
  <RESULT>
    <RST>
      <HEAD>
        <CODE>000000</CODE>
        <MSG color="green">交易成功</MSG>
      </HEAD>
    </RST>
    <RST>
       <HEAD>
         <CODE>000001</CODE>
         <MSG color="red">交易失败</MSG>
       </HEAD>
    </RST>
  </RESULT>
</XML>

我们可以通过XPath表达式/XML/DATA/USERNAME来获得USERNAME的值。

public class XPathLearn {

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        File f = new File("doc/test.xml");
        DocumentBuilder  builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document doc = builder.parse(f);
        //创建XPath对象
        XPath path = XPathFactory.newInstance().newXPath();
        //获得/XML/DATA/USERNAME的text文本
        String username = path.evaluate("/XML/DATA/USERNAME", doc);
        System.out.println("username:"+username);
        // /XML/RESULT/RST[1] 表示/XML/RESULT下的第一个RST子元素,下标从1开始
        String succedCode = "/XML/RESULT/RST[1]/HEAD/CODE";
        //MSG/@color 表示MSG元素的属性color
        String color = "/XML/RESULT/RST[1]/HEAD/MSG/@color";
        succedCode = path.evaluate(succedCode, doc);
        System.out.println("succedCode:"+succedCode);
        color = path.evaluate(color, doc);
        System.out.println("color:"+color);
        //获得一组节点
        NodeList nodes = (NodeList)path.evaluate("/XML/RESULT/RST", doc, XPathConstants.NODESET);
        for(int i=0;i<nodes.getLength();i++){
            Node node = nodes.item(i);
            if(node instanceof Element){
                System.out.println(((Element) node).getTagName());
            }
        }
        //获得一组节点,若结果只有一个,则以XPathConstants.NODE替代。
        Node node = (Node)path.evaluate("/XML/DATA", doc,XPathConstants.NODE);
        if(node instanceof Element){
            System.out.println(((Element) node).getTagName());
        }
        //若结果是一个数字,那么用XPathConstants。NUMBER替代。
        int count = ((Number)path.evaluate("count(/XML/RESULT/RST)", doc,XPathConstants.NUMBER)).intValue();
        System.out.println("count:"+count);
    }
}

结果:

username:admin
succedCode:000000
color:green
RST
RST
DATA
count:2

关于XPath还有很多有用的函数,请结合API文档使用。

6、流机制解析器

DOM解析器会完整的读入XML文档,然后将其转换成一个树形的数据结构。对于大多数应用,DOM都运行的很好。但是,如果文档很大,并且处理算法又非常简单,可以在运行时解析节点,而不必看到完整的树形结构,那么DOM可能就会显得效率低下了。在这种情况下,我们应该使用流机制解析器。

6.1、使用SAX解析器

SAX解析器是解析XML输入数据的各个组成部分时会报告事件,但不会以任何方式存储文档,而是由事件处理器建立相应的数据结构。实际上,DOM解析器是在SAX解析器的基础上构建的,它在接收到解析器事件时构造DOM树。

在使用SAX解析器时,需要一个处理器来为各种解析器事件定义事件动作。ContentHandler接口定义了若干个在解析文档时解析器会调用的回调方法。下面是最重要的几个:

  • startElement 和endElement在每当遇到起始或终止标签时调用。
  • characters在每当遇到字符数据时调用。
  • startDocument和endDocument分别在文档开始和结束时各调用一次。

例如,在解析以下片段时:test.xml

<DATA>
  <USERNAME type="MD5">admin</USERNAME>
</DATA>

解析器会产生以下回调:

  1. startElement,元素名:DATA
  2. startElement,元素名:USERNAME
  3. characters,内容:admin
  4. endElement,元素名:USERNAME
  5. endElement,元素名:DATA

处理器必须覆盖这些方法,让它们执行我们想要让它们执行的动作。

public class XMLForSAXLearn {

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        File f = new File("doc/test.xml");
        //获得SAX解析器实例
        SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
        //定义处理器 该类为ContentHandler、DTDHandler、EntityResolver、ErrorHandler接口定义了空的方法,只需要覆盖即可
        DefaultHandler handler = new DefaultHandler(){

            @Override
            public void startDocument() throws SAXException {
                System.out.println("开始解析test.xml文档!");
            }

            @Override
            public void endDocument() throws SAXException {
                System.out.println("解析test.xml文档结束!");
            }

            @Override
            public void startElement(String uri, String localName,
                    String qName, Attributes attributes) throws SAXException {
                System.out.print("uri:"+uri+" localName:"+localName+" qName:"+qName);
                for(int i =0;i<attributes.getLength();i++){
                    String name = attributes.getLocalName(i);
                    String value = attributes.getValue(i);
                    System.out.print(" name:"+name+" value:"+value);
                }
                System.out.println();
            }

            @Override
            public void endElement(String uri, String localName, String qName)
                    throws SAXException {
                System.out.println("uri:"+uri+" localName:"+localName+" qName:"+qName);
            }

            @Override
            public void characters(char[] ch, int start, int length)
                    throws SAXException {
                System.out.println(new String(ch,start,length).trim());
            }

        };
        parser.parse(f, handler);
    }
}

结果:

开始解析test.xml文档!
uri: localName: qName:DATA

uri: localName: qName:USERNAME name:type value:MD5
admin
uri: localName: qName:USERNAME

uri: localName: qName:DATA
解析test.xml文档结束!

7、生成XML文档

现在已经知道怎样编写取读XML的java程序了。下面就让我们开始介绍它的反向过程,即产生XML输出:用文档的内容构建一颗DOM树,然后写出该树的所有内容。

public class WriterXML {

    /**
     * 不带命名空间生成DOM文档
     * @return DOM 文档
     * @throws ParserConfigurationException
     */
    public static Document getDOMWithoutNS() throws ParserConfigurationException {
        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        //创建一个空白DOM
        Document doc = builder.newDocument();
        //创建根节点 XML
        Element XML = doc.createElement("XML");
        //创建子节点 HEAD
        Element HEAD = doc.createElement("HEAD");
        //创建文本节点
        Text HEADText = doc.createTextNode("这是不带命名空间的头节点的内容!");
        //生成DOM树
        doc.appendChild(XML);
        XML.appendChild(HEAD);
        HEAD.appendChild(HEADText);
        //为HEAD 添加属性 color="red"
        HEAD.setAttribute("color", "red");
        return doc;
    }

    /**
     * 带命名空间生成DOM文档
     * @return DOM 文档
     * @throws ParserConfigurationException
     */
    public static Document getDOMWithNS() throws ParserConfigurationException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //设置命名空间感知
        factory.setNamespaceAware(true);
        //创建空白DOM
        Document doc = factory.newDocumentBuilder().newDocument();
        String nameSpace = "http://www。w3.org/2000/svg";
        //创建根节点 XML 带命名空间,注意使用NS的方法
        Element XML = doc.createElementNS(nameSpace, "XML");
        Element HEAD = doc.createElement("HEAD");
        Text HEADText = doc.createTextNode("这是带命名空间的头节点的内容");
        //生成DOM树
        doc.appendChild(XML);
        XML.appendChild(HEAD);
        HEAD.appendChild(HEADText);
        //为HEAD 添加属性 color="red" 带有命名空间
        HEAD.setAttributeNS(nameSpace,"color", "red");
        return doc;
    }

    /**
     * 写出XML 就是这种格式
     * @param doc
     * @param name
     * @throws IOException
     */
    public static void writeXML(Document doc, String name) throws IOException{
        //这是一个魔咒,就要这样写,
        DOMImplementation imp = doc.getImplementation();
        DOMImplementationLS impLS = (DOMImplementationLS) imp.getFeature("LS", "3.0");
        LSSerializer ser = impLS.createLSSerializer();
        //设置XML中的空格和换行
        ser.getDomConfig().setParameter("format-pretty-print", true);
        //得到XML字符串
        String str = ser.writeToString(doc);
        System.out.println(str);
        //将XML输出到文件中
        LSOutput out = impLS.createLSOutput();
        //设置字符编码集
        out.setEncoding("UTF-8");
        out.setByteStream(Files.newOutputStream(Paths.get("doc", name+".xml")));
        ser.write(doc, out);
    }
    public static void main(String[] args) throws ParserConfigurationException, IOException{

        Document docWithoutNS = getDOMWithoutNS();
        Document docWithNS = getDOMWithNS();
        writeXML(docWithoutNS,"docWithoutNS");
        writeXML(docWithNS,"docWithNS");
    }
}

结果生成:

docWithoutNS.xml文档:

<?xml version="1.0" encoding="UTF-8"?>
<XML>
    <HEAD color="red">这是不带命名空间的头节点的内容!</HEAD>
</XML>

docWithNS.xml文档:

<?xml version="1.0" encoding="UTF-8"?>
<XML xmlns="http://www。w3.org/2000/svg">
    <HEAD xmlns:NS1="http://www。w3.org/2000/svg" NS1:color="red">这是带命名空间的头节点的内容</HEAD>
</XML>

8、XSL转换

XSL转换(XSLT)机制可以指定将XML文档转换为其他格式的规则,例如,转换为纯文本、XHTML或任何其他的XML格式。XSLT通常用来将各种机器可读的XML格式转译为另一种机器可读的XML格式,或者将XML转译为适用于人类阅读的表示格式。

假如我们想要把有雇员记录的XML文件转换成HTML文件。

XML文件:

<staff>
    <employee>
        <name>Carl Cracker</name>
        <salary>75000</salary>
        <hiredate year="1987" moth="12" day="15"/>
    </employee>
    <employee>
        <name>Harry Hacker</name>
        <salary>50000</salary>
        <hiredate year= "1989" month="10" day="1"/>
    </employee>
    <employee>
        <name>Tony Tester</name>
        <salary>40000</salary>
        <hiredate year="1990" month="3" day="15"/>
    </employee>
</staff>

我们希望的输出是一张HTML表格:

<table border="1">
<tr>
<td>Carl Cracker</td><td>$75000</td><td>1987-12-15</td>
</tr>
<tr>
<td>Harry Hacker</td><td>$50000</td><td>1989-10-1</td>
</tr>
<tr>
<td>Tony Tester</td><td>$40000</td><td>1990-3-15</td>
</tr>
</table>

具有转换模板的样式表形式如下:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:output method="html"/>
    <xsl:template match="/staff">
        <table border="1"><xsl:apply-templates/></table>
    </xsl:template>
    <xsl:template match="/staff/employee">
        <tr><xsl:apply-templates/></tr>
    </xsl:template>
    <xsl:template match="/staff/employee/name">
        <td><xsl:apply-templates/></td>
    </xsl:template>
    <xsl:template match="/staff/employee/salary">
        <td>$<xsl:apply-templates/></td>
    </xsl:template>
    <xsl:template match="/staff/employee/hiredate">
        <td><xsl:value-of select="@year"/>-<xsl:value-of select="@month"/>-<xsl:value-of select="@day"/></td>
    </xsl:template>
</xsl:stylesheet>

在我们的例子中,xsl:output元素将方法设定为HTML。而其他有效的方法为:xml和text。

一个典型的模板(取代上面模板的template):

<xsl:template match="/staff/employee">
    <tr><xsl:apply-templates/></tr>
</xsl:template>

match属性的值是一个XPathc表达式。改模板声明没看到XPath集/staff/employee中的一个节点时,将做以下操作:

  1. 产生字符串。
  2. 在处理其子节点时,持续应用该模板。
  3. 当处理完所有子节点时,产生字符串。

XSLT处理器以检查根元素开始其处理过程。每当一个节点匹配某个模板时,就会应用该模板(如果匹配多个模板,就会使用最佳匹配的那个)如果没有匹配的模板,处理器会执行默认操作。对于文本节点,默认操作是把它的内容囊括到输出中去。对于元素,默认操作是不产生任何输出,但会继续处理其子节点。

public class XMLTOHTML {

    public static void main(String[] args) throws TransformerFactoryConfigurationError, TransformerException {
        //获取样式表
        File styleSheet = new File("doc/xmlToHtml.xsl");
        StreamSource styleSource = new StreamSource(styleSheet);
        //获取样式表转换器
        Transformer t = TransformerFactory.newInstance().newTransformer(styleSource);
        //获取要转换有几种方式:1、StreamSource  2、DOMSource  3、SAXSource  4、StAXSource
        StreamSource source = new StreamSource(new File("doc/test.xml"));
        //要转换为的方式有几种:1、DOMResult  2、SAXResult  3、StreamResult
        StreamResult result = new StreamResult(new File("doc/result.html"));
        t.transform(source, result);
    }
}

结果生成result.html:

<table border="1">

<tr>

<td>Carl Cracker</td>
        <td>$75000</td>
        <td>1987--15</td>

</tr>

<tr>

<td>Harry Hacker</td>
        <td>$50000</td>
        <td>1989-10-1</td>

</tr>

<tr>

<td>Tony Tester</td>
        <td>$40000</td>
        <td>1990-3-15</td>

</tr>

</table>

关于文档转换XSLT语法的学习,请点击此链接;

猜你喜欢

转载自blog.csdn.net/tjy_521/article/details/80495757
今日推荐