天下第二博

Tian Xia The Second BO
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

解决XML中不可识别的字符

Posted on 2010-07-14 18:00  Nuke'Blog  阅读(5798)  评论(0编辑  收藏  举报
 SAXParseException的神秘

      许多SAXParseException异常报告可以非常容易的理解,但是我们要这篇报告做什么呢? 并且这篇报告是当在解析一个使用文本编辑器创建或编辑的XML文档时将会看到的。
目标指令匹配"[xX][mM][lL]"是不被允许的,特别神秘的是在当你知道你不需要试着去创建一个处理指令的时候。它使得你所得到的就是这些,如果XML声明的开始的"<"字符——

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

       ——并不是这个文件中的第一个字符。这看其他特别的神秘,因为这个文件在打印和查看的时候看起来是很好的。

      在消失代码的情况下

      为了能清楚的说明这个问题,假设你有一个用来保持一个在线签约雇佣的用户的列表XML文档,并用这个作为通常的一个入口。

< user unum="101">
 < firstname>Bill
 < lastname>Brogden
< /user>

       当作为一个org.w3c.dom文档被解析到内存中时,firstname元素有一个是TEXT_NODE类型的子节点。程序员也许会被引导使用以下的Java代码来获得firstname字符串。

// where fnE is the firstname org.w3c.dom.Element reference
 String name = fnE.getFirstChild().getNodeValue();

       使用类似的代码来设置firstname的新值。

  fnE.getFirstChild().setNodeValue( newfirstname );

      这段代码将会被编译并看起来能正确的工作,直到有一天由于某种原因一个空字符串被负值给了这个文本节点。当该文本仍然在内存中时,上面的代码将会继续工作。但是当该文档被一个转换器序列化为一个文档的时候,被序列化的并不是所期望的文档: 

  < firstname>< /firstname>

      实际上你能得到的是:

  < firstname/>

      转换器意识到firstname元素为空,并考虑了这种首选的形式。现在当这个校订的文档再次被解析进入内存的时候,那个firstname元素就不再含有一个子节点,并且下面这个语句——

  String f = fnE.getFirstChild().getNodeValue();

      ——只会给程序员一个令人厌恶的打击,导致一个NullPointerException异常

     解决的办法当然就是防御性的进行编码,确认子节点的 存在并证明它的默认值不存在。例如,使用下面的代码来获得firstname。

 // where fnE is the firstname org.w3c.dom.Element reference
  Node fnNode = fnE.getFirstChild();
  if( fnNode == null ){  name= "" ;
  } else { name = fnNode.getNodeValue();
  }
 

      当子节点不存在时设置一个firstname的值需要更加复杂的代码,因为我们需要首先创建这个节点。

  Node fnNode = fnE.getFirstChild();
  if( fnNode == null ){
     Document doc = fnE.getOwnerDocument();
     fnNode = doc.createTextNode( newfirstname) ;
     fnE.appendChild( fnNode );
  } else {
     fnNode.setNodeValue( newfirstname );
  }

      为什么我的文档是空的?

      一些程序员已经习惯了使用以下的方式来编写代码,即在一个方法中把XML、解析为一个Document对象,从而确保这些Document本身已经被创建。

  // where builder is an instance of DocumentBuilder
  Document doc = builder.parse( f );
  System.out.println("Document is:" + doc );

      使用Java1.5 XML库,输出的结果如下所示:

  Document is:[#document: null]

      对一个新的程序员这看起来似乎是说Document没有内容。实际上它要表达的是你已经真正的有了一个Document对象我总是发现节点接口的Javadocs表对这种情况总是一个很大的帮助。它告诉你的是从getNodeValue和getNode命名的方法所能期望获得的是不同的DOM对象。这是一个Sun的为org.w3c.dom节点的在线文档的链接:http://java.sun.com/j2se/1.5.0/docs/api/org/w3c/dom/Node.html.

      从这张表你可以看到getNodeValue()方法总是从一个Document类型节点饭或空值。文档的toString()方法把"#document"名同从getNodeValue()得到的值结合起来。

      Unicode错误

      你在处理XML文档时会经常遇到的一个令人沮丧的错误是无效。Unicode字符错误将会产生一个如下所示的异常报告:

org.xml.sax.SAXParseException: An invalid XML character (Unicode: 0x1a) was found in the element content of the document.

javax.xml.transform.TransformerException: org.xml.sax.SAXParseException: An invalid XML character (Unicode: 0x1a) was found in the CDATA section.
        at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:686)
        at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1088)
        at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1066)
        at com.cicro.cws.cache.staticpage.ContentPage.formStaticIndexPage(ContentPage.java:427)
        at com.cicro.cws.info.StaticContetnPageManager.formStaticContentPage(StaticContetnPageManager.java:34)
        at com.cicro.cws.info.TaskThread.createPages(InfoManager.java:2109)
        at com.cicro.cws.info.TaskThread.run(InfoManager.java:2127)
Caused by: org.xml.sax.SAXParseException: An invalid XML character (Unicode: 0x1a) was found in the CDATA section.
        at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
        at org.apache.xml.dtm.ref.DTMManagerDefault.getDTM(DTMManagerDefault.java:349)
        at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:630)
        ... 6 more

      或者是更为严重的警告:

java.io.UTFDataFormatException: Invalid byte 1 of 1-byte UTF-8 sequence.

      找出无效字符的来源是一个极富挑战的侦探工作,特别是这些字符也许在通常的检测中都是很正常的。

      0x1a字符在一些特定的应用程序中被用作一个表示文件结束的标志的控制编码。在某种情况下,该字符在一个数据库域中结束并且接下来同不适宜的结果被插入到一个XML文档中去。

      一个通常的无效字符的来源通常是一些文档,这些文档是用微软的转换为“smart”标点的文档处理器生成的。如果你能看见打开和关闭引用的不同字符,你就有了"smart"标点。

      不幸的,微软选择了那些再0x82到0x95之间的字符作为"smart"标点,这些是Unicode保留作为控制编码的,并且在XML中是不合法的。从而当微软的文档作为XML文档进行粘贴和裁减操作的来源使用时,可能会导致引入阻止文档被解析的字符的危险。

       如果你有一个SAXParseException,你可以摘录异常中的那些令人不快的位置的代码,如下所示:

 }catch(SAXParseException spe ){
    String err = spe.toString() +
       "\n  Line number: " + spe.getLineNumber() +
       "\nColumn number: " + spe.getColumnNumber()+
       "\n Public ID: " + spe.getPublicId() +
       "\n System ID: " + spe.getSystemId() ;
    System.out.println( err );
 }

      如果你有一个程序编辑器也会是一个很大的帮助,特别是如果这个编辑器能够在显示正常的文本和以16进制显示的字符编码之间切换。我个人的更喜欢采用UltraEdit-32作为这种侦探工作的工具。