java学习脚印:xml中空白文本结点(whitespace TextNode)处理及验证方法

java学习脚印:xml中空白文本结点(whitespace TextNode)处理及验证方法


1.空白结点在解析过程中引起的麻烦

  首先来看下一个非常简单的xml文件,如下:

清单1-1 books-no.xml

  1. <?xml version="1.0" encoding="UTF-8"?>   
  2.  <books>   
  3.    <book>   
  4.       <title>Harry Potter</title>   
  5.       <author>J K. Rowling</author>   
  6.    </book>   
  7.  </books>   

我们在1-1中看到的DOM树结点关系图如下图所示:


   由于xml规范允许空白字符的文本结点,因此实际上就会包含一些空白字符的文本结点(我们的本意也许并不想包含空白字符结点,但是在编辑时可能无意引入了空白字符)

利用vim的搜索空白字符功能,我们看下图:



      其中黄色高亮显式的部分为空白字符,其中2-6行的空白字符生成了空白结点,这样实际的DOM树结点关系图如下:


     空白字符文本结点的出现,导致在没有使用验证方式时,遍历DOM树要做过多的结点类型检测,可以参看清单2-6 DOMParserDemo.java ,观察代码以加强理解。


2.提供验证,避免空白结点引起的麻烦


    如果使用了验证文件的话,则解析器会自动忽略空白结点,省去很多不必要的麻烦。

xml文件使用dtd或者xsd Schema模式来验证xml文件。


首先,我们来看配合dtd验证文件的xml。

清单 2-1 books.dtd

  1. <!ELEMENT books (book)*>  
  2. <!ELEMENT book (title,author)>  
  3. <!ELEMENT title (#PCDATA)>  
  4. <!ELEMENT author (#PCDATA)>  

清单2-2 books-dtd.xml

  1. <?xml version="1.0" encoding="UTF-8"?>   
  2. <!DOCTYPE books SYSTEM "books.dtd">  
  3.  <books>   
  4.    <book>   
  5.       <title>Harry Potter</title>   
  6.       <author>J K. Rowling</author>   
  7.    </book>   
  8.  </books>   


另一种方式是采用xsd文件验证。


清单2-3 books.xsd

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">  
  3.  <xs:element name="books">  
  4.       <xs:complexType>  
  5.        <xs:sequence>  
  6.             <xs:element name="book" maxOccurs="unbounded">  
  7.               <xs:complexType>  
  8.                <xs:sequence>  
  9.                <xs:element name="title" type="xs:string"/>  
  10.                <xs:element name="author" type="xs:string"/>  
  11.                </xs:sequence>  
  12.               </xs:complexType>  
  13.             </xs:element>  
  14.        </xs:sequence>  
  15.       </xs:complexType>  
  16.  </xs:element>  
  17. </xs:schema>  


清单2-4 books-xsd.xml

  1. <?xml version="1.0" encoding="UTF-8"?>   
  2.  <books xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  3.   xsi:noNamespaceSchemaLocation="books.xsd">   
  4.    <book>   
  5.       <title>Harry Potter</title>   
  6.       <author>J K. Rowling</author>   
  7.    </book>   
  8.  </books>   


    为了提高代码的重用性,我们可以根据验证文件类型,对解析器进行配置,可参考如下代码。

清单2-5 ParserUtil.java

  1. package com.learningjava;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5.   
  6. import javax.xml.parsers.DocumentBuilder;  
  7. import javax.xml.parsers.DocumentBuilderFactory;  
  8. import javax.xml.parsers.ParserConfigurationException;  
  9.   
  10. import org.w3c.dom.Document;  
  11. import org.w3c.dom.Node;  
  12. import org.w3c.dom.NodeList;  
  13. import org.w3c.dom.Text;  
  14. import org.xml.sax.SAXException;  
  15. /** 
  16.  * This class is a util class to help parse xml file 
  17.  * @author wangdq 
  18.  * 2011-11-10 
  19.  */  
  20. public class  ParserUtil {  
  21.     /** 
  22.      * build and configure dom parser according to the filepath  
  23.      * we test the filepath,if contain 'dtd' or 'xsd' 
  24.      *  
  25.      * @param filePath the path of xml file 
  26.      * @return the DOM Document Obeject 
  27.      */  
  28.     public static Document getDocument(String filePath) {   
  29.         Document document = null;   
  30.         try {  
  31.             //step1: get DocumentBuilderFactory  
  32.             DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();  
  33.                
  34.             //configure the factory to set validate mode  
  35.             boolean dtdValidate = false;  
  36.             boolean xsdValidate = false;  
  37.             if(filePath.contains("dtd")) {  
  38.                 dtdValidate = true;  
  39.             } else if(filePath.contains("xsd")) {  
  40.                 xsdValidate = true;  
  41.                 dbFactory.setNamespaceAware(true);  
  42.                 final String JAXP_SCHEMA_LANGUAGE =  
  43.                         "http://java.sun.com/xml/jaxp/properties/schemaLanguage";  
  44.                 final String W3C_XML_SCHEMA =  
  45.                         "http://www.w3.org/2001/XMLSchema";  
  46.                 dbFactory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);  
  47.             }  
  48.             dbFactory.setValidating(dtdValidate || xsdValidate);  
  49.             dbFactory.setIgnoringElementContentWhitespace(dtdValidate || xsdValidate);  
  50.               
  51.             //parse an XML file into a DOM tree   
  52.             DocumentBuilder builder = dbFactory.newDocumentBuilder();  
  53.             document = builder.parse(new File(filePath));  
  54.         }catch (ParserConfigurationException | SAXException | IOException e) {  
  55.             // TODO Auto-generated catch block  
  56.             e.printStackTrace();  
  57.         }   
  58.         return document;  
  59.     }  
  60.     /** 
  61.      * print element and text node of the given node  
  62.      * @param level the dom tree level ,the root is at level 1 
  63.      * @param node  the node to print 
  64.      */  
  65.     public static void printElementAndTextNode(int level,Node node) {  
  66.         final int INDENT = 4;  
  67.         if(node.getNodeType() == Node.ELEMENT_NODE)   
  68.         {  
  69.             System.out.printf("%" + INDENT*level + "s+%d"" ",level);  
  70.             System.out.format(" ELEMENT: <%s> %n",node.getNodeName());  
  71.             int newLevel = level+1;  
  72.             NodeList childList = node.getChildNodes();  
  73.             for(int ix = 0;ix<childList.getLength();ix++) {  
  74.                 printElementAndTextNode(newLevel,childList.item(ix));  
  75.             }  
  76.         } else if(node.getNodeType() == Node.TEXT_NODE) {  
  77.             Text textNode = (Text)node;  
  78.             System.out.printf("%" + INDENT*level + "s+%d"" ",level);  
  79.             String data = textNode.getData().trim();  
  80.             System.out.format(" TEXT: \"%s\" %n",data);  
  81.         }  
  82.     }  
  83.     /** 
  84.      * remove whitespace textnode 
  85.      * note,here we only consider the  ELEMENT_NODE and TEXT_NODE 
  86.      * @param node the node needed to purify by removing whitespace textnode 
  87.      * @return the nums of whitespace textnode that had been removed 
  88.      */  
  89.     public static int removeWhiteSpaceTextElement(Node node) {  
  90.           
  91.         int count = 0;  
  92.         if(node == null)  
  93.             return 0;  
  94.         //System.out.println("visting :"+node.getNodeName());  
  95.         if(node.getNodeType() == Node.ELEMENT_NODE)   
  96.         {     
  97.             //iterate child node  
  98.             for(Node childNode = node.getFirstChild(); childNode!=null;){  
  99.                 Node nextChild = childNode.getNextSibling();  
  100.                 // Do something with childNode, including move or delete...  
  101.                 count += removeWhiteSpaceTextElement(childNode);  
  102.                 childNode = nextChild;  
  103.             }  
  104.         } else if(node.getNodeType() == Node.TEXT_NODE) {  
  105.             Text textNode = (Text)node;  
  106.             String data = textNode.getData().trim();  
  107.             if(data.isEmpty()) {  
  108.                 //remove whitespace textNode  
  109.                 //System.out.println("remove "+textNode.getNodeName());  
  110.                 textNode.getParentNode().removeChild(textNode);  
  111.                 count++;  
  112.             }  
  113.         }  
  114.         return count;  
  115.     }  
  116.       
  117. }  



   下面给出不使用验证方式,以及使用dtd和xsd文件对xml进行验证的三种方式解析books xml文件的代码,可通过对比增强理解。


清单2-6 DOMParserDemo.java

  1. package com.learningjava;  
  2.   
  3. import org.w3c.dom.Document;  
  4. import org.w3c.dom.Element;  
  5. import org.w3c.dom.Node;  
  6. import org.w3c.dom.NodeList;  
  7. import org.w3c.dom.Text;  
  8. /** 
  9.  * This program illustrate ways to validate xml  
  10.  * @author wangdq 
  11.  * 2013-11-10 
  12.  */  
  13. public class DOMParserDemo {  
  14.     public static void main(String[] args) {  
  15.           
  16.         //use dtd to validate books-dtd.xml  
  17.         TimeCounter.start();  
  18.         parseWithValidate("books-dtd.xml");  
  19.         System.out.format("dtd validate,consumed: %d ns%n%n",TimeCounter.end());  
  20.           
  21.         //use schema to validate books-xsd.xml  
  22.         TimeCounter.start();  
  23.         parseWithValidate("books-xsd.xml");  
  24.         System.out.format("xsd validate,consumed: %d ns%n%n",TimeCounter.end());  
  25.           
  26.         //not using validation   
  27.         TimeCounter.start();  
  28.         parseWithNoValidate("books-no.xml");  
  29.         System.out.format("not validate,consumed: %d ns%n%n",TimeCounter.end());  
  30.     }  
  31.     public static void parseWithValidate(String filepath) {  
  32.         Document doc = ParserUtil.getDocument(filepath);  
  33.           
  34.         //traverse nodelist  
  35.         // get root element (Level1)  
  36.         Element rootElement = doc.getDocumentElement();  
  37.         //get Level2 element  
  38.         Element book = (Element)rootElement.getFirstChild();  
  39.         //get Level3 element  
  40.         NodeList children = book.getChildNodes();  
  41.         for(int iy = 0;iy<children.getLength();iy++) {  
  42.             Node child = children.item(iy);  
  43.             //get Level4 element  
  44.             Text textNode = (Text)child.getFirstChild();  
  45.             System.out.format("%s%n",textNode.getData().trim());  
  46.         }  
  47.     }  
  48.     public static void parseWithNoValidate(String filepath) {  
  49.           
  50.         Document doc = ParserUtil.getDocument(filepath);  
  51.           
  52.         //traverse nodelist  
  53.         //get root element (Level1)  
  54.         Element rootElement = doc.getDocumentElement();  
  55.         //get Level2 element  
  56.         NodeList nodeList = rootElement.getChildNodes();  
  57.         for(int ix = 0;ix<nodeList.getLength();ix++) {  
  58.             Node node = nodeList.item(ix);  
  59.             if(node.getNodeType() == Node.ELEMENT_NODE) {  
  60.                 //get Level3 element  
  61.                 NodeList children = node.getChildNodes();  
  62.                 for(int iy = 0;iy<children.getLength();iy++) {  
  63.                     Node child = children.item(iy);  
  64.                     if(child.getNodeType() == Node.ELEMENT_NODE) {  
  65.                         //get Level4 element  
  66.                         Text textNode = (Text)child.getFirstChild();  
  67.                         System.out.format("%s%n",textNode.getData().trim());  
  68.                     }  
  69.                 }  
  70.             }  
  71.         }  
  72.     }  
  73. }  
  74. /** 
  75.  * calculate time consumed 
  76.  */  
  77. class TimeCounter {  
  78.     public static void start() {  
  79.         startTime = System.nanoTime();  
  80.     }  
  81.     public static long end() {  
  82.         return  System.nanoTime() - startTime;  
  83.     }  
  84.     private static long startTime;  
  85. }  



运行输出

Harry Potter
J K. Rowling
dtd validate,consumed: 98839944 ns

Harry Potter
J K. Rowling
xsd validate,consumed: 68073601 ns

Harry Potter
J K. Rowling
not validate,consumed: 4853899 ns


可见,虽然验证方式简化了代码,但是也增了处理的时间。


3.去除空白结点

    如果在处理xml文件之前就把空白字符结点去掉,那样也是提高解析速度的一种方法。

    上面的辅助类ParserUtil类中给出了打印树结点和删除空白字符结点的方法,下面的代码给出了空白结点删除前后,1-1 books-no.xml文件的结点结构。

清单 3-1 PrintNodeDemo.java

  1. package com.learningjava;  
  2.   
  3. import org.w3c.dom.Document;  
  4. import org.w3c.dom.Element;  
  5. /** 
  6.  * This program print simple DOM tree node 
  7.  * @author wangdq 
  8.  * 2011-11-10 
  9.  */  
  10. public class PrintNodeDemo {  
  11.     public static void main(String[] args) {  
  12.         Document doc = ParserUtil.getDocument("books-no.xml");  
  13.         Element rootElement = doc.getDocumentElement();  
  14.           
  15.         //before whitespace node removed  
  16.         System.out.format("Node Architecture of %s as follow:%n%n","books-no.xml");  
  17.         ParserUtil.printElementAndTextNode(1,rootElement);  
  18.           
  19.         //remove whitespace node  
  20.         System.out.format("%nremoved %d whitespace node.%n",  
  21.                 ParserUtil.removeWhiteSpaceTextElement(rootElement));  
  22.         System.out.format("after removed: %n%n");  
  23.         ParserUtil.printElementAndTextNode(1,rootElement);  
  24.     }  
  25. }  

运行输出

Node Architecture of books-no.xml as follow:

    +1 ELEMENT: <books>
        +2 TEXT: ""
        +2 ELEMENT: <book>
            +3 TEXT: ""
            +3 ELEMENT: <title>
                +4 TEXT: "Harry Potter"
            +3 TEXT: ""
            +3 ELEMENT: <author>
                +4 TEXT: "J K. Rowling"
            +3 TEXT: ""
        +2 TEXT: ""

removed 5 whitespace node.
after removed:

    +1 ELEMENT: <books>
        +2 ELEMENT: <book>
            +3 ELEMENT: <title>
                +4 TEXT: "Harry Potter"
            +3 ELEMENT: <author>
                +4 TEXT: "J K. Rowling"


   这里注意一点,就是删除空白字符结点的时候,避免使用这一版的代码:


  1. /** 
  2.   * This code will not work to remove whitespace text node 
  3.   */  
  4. public static int removeWhiteSpaceTextElement_failed(Node node) {  
  5.       
  6.     int count = 0;  
  7.     if(node == null)  
  8.         return 0;  
  9.     System.out.println("visting :"+node.getNodeName());  
  10.     if(node.getNodeType() == Node.ELEMENT_NODE)   
  11.     {     
  12.         NodeList childList = node.getChildNodes();   
  13.         //iterate childList  
  14.         //here we can not guarantee the node order after remove element  
  15.         //so this incur errors  
  16.         for(int ix = 0;ix<childList.getLength();ix++) {  
  17.             count += removeWhiteSpaceTextElement_failed(childList.item(ix));  
  18.         }  
  19.     } else if(node.getNodeType() == Node.TEXT_NODE) {  
  20.         Text textNode = (Text)node;  
  21.         String data = textNode.getData().trim();  
  22.         if(data.isEmpty()) {  
  23.             //remove whitespace textNode  
  24.             //System.out.println("remove "+textNode.getNodeName());  
  25.             textNode.getParentNode().removeChild(textNode);  
  26.             count++;  
  27.         }  
  28.     }  
  29.     return count;  
  30. }  

    

因为NodeList对象会动态更新,当删除了子节点之后,再按照原先的索引就得不到相应的子节点,因而引发了与迭代相关的错误,这一点值得引起注意。



     通过对比移除空白字符结点,前后的树形结构图,相信你对空白字符结点以及xml验证有了一个很好的理解。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值