由于我没有看到任何答案,而且其他人似乎也有同样的问题,所以我进一步研究了......
为了找到bug的根源,我使用了Xalan 2.7.1中的serializer源代码,Xerces中也用到了。
org.apache.xml.serializer.dom3.LSSerializerImpl 使用org.apache.xml.serializer.ToXMLStream,它扩展了org.apache.xml.serializer.ToStream。
ToStream.characters(final char chars[], final int start, final int length) 处理字符,并且不正确支持 unicode 字符(注意:org.apache.xml.serializer.ToTextSream(可以与 Transformer 一起使用)在字符方法中做得更好,但它只处理纯文本和忽略所有标记;有人会认为 XML 文件是文本,但由于某种原因 ToXMLStream 没有扩展 ToTextStream)。
org.apache.xalan.transformer.TransformerIdentityImpl 也使用了org.apache.xml.serializer.ToXMLStream(由org.apache.xml.serializer.SerializerFactory.getSerializer(Properties format) 返回),因此它存在相同的错误。
ToStream 正在使用org.apache.xml.serializer.CharInfo 来检查一个字符是否应该被String 替换,因此该错误也可以在那里修复而不是直接在ToStream 中修复。 CharInfo 正在使用带有字符实体列表的属性文件 org.apache.xml.serializer.XMLEntities.properties,因此更改此文件也可能是修复错误的一种方法,尽管到目前为止它仅针对特殊 XML 字符(quot ,amp,lt,gt)。使ToXMLStream 使用与包中的属性文件不同的属性文件的唯一方法是在类路径之前添加一个org.apache.xml.serializer.XMLEntities.properties 文件,这不是很干净...
使用默认的 JDK(1.6 和 1.7),TransformerFactory 返回一个 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl,它使用 com.sun.org.apache.xml.internal.serializer.ToXMLStream。在com.sun.org.apache.xml.internal.serializer.ToStream 中,characters() 有时调用processDirty(),它调用accumDefaultEscape(),可以更好地处理unicode 字符,但实际上它似乎不起作用(也许processDirty 不调用unicode 字符)。 ..
com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl 正在使用com.sun.org.apache.xml.internal.serialize.XMLSerializer,它支持 unicode。奇怪的是,XMLSerializer 来自Xerces,但当xalan 或xsltc 在类路径上时,Xerces 并没有使用它。这是因为org.apache.xerces.dom.CoreDOMImplementationImpl.createLSSerializer 在可用时使用org.apache.xml.serializer.dom3.LSSerializerImpl 而不是org.apache.xerces.dom.DOMSerializerImpl。在类路径上使用serializer.jar,则使用org.apache.xml.serializer.dom3.LSSerializerImpl。警告:xalan.jar 和 xsltc.jar 都在清单中引用 serializer.jar,所以如果 serializer.jar 在同一目录中并且 xalan.jar 或 xsltc.jar 在类路径上,则它最终会出现在类路径中!如果类路径中只有xercesImpl.jar和xml-apis.jar,则org.apache.xerces.dom.DOMSerializerImpl用作LSSerializer,并正确处理unicode字符。
结论和解决方法:该错误存在于 Apache 的 org.apache.xml.serializer.ToStream 类(在 JDK 中重命名为 com.sun.org.apache.xml.internal.serializer.ToStream)。正确处理 unicode 字符的序列化程序是 org.apache.xml.serialize.DOMSerializerImpl(在 JDK 中重命名为 com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl)。然而,当它可用时,Apache 更喜欢ToStream 而不是DOMSerializerImpl,所以它可能在其他事情上表现得更好(或者它可能只是一个重组)。最重要的是,他们甚至在 Xerces 2.9.0 中弃用了 DOMSerializerImpl。因此,以下解决方法可能会产生副作用:
-
当 Xerces 和 Apache 的 serializer 在类路径中时,将“(doc.getImplementation()).createLSSerializer()”替换为“new org.apache.xerces.dom.DOMSerializerImpl()”
-
当 Apache 的 serializer 在类路径上(例如因为 xalan)而不是 Xerces,尝试将“(doc.getImplementation()).createLSSerializer()”替换为“new com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl()”(回退是必要的,因为这个类将来可能会消失)
这两种解决方法在编译时会产生警告。
我没有XSLT transforms 的解决方法,但这超出了问题的范围。我想可以对另一个 DOM 文档进行转换并使用DOMSerializerImpl 进行序列化。
其他一些解决方法,对某些人来说可能是更好的解决方案:
-
使用Saxon 和Transformer
-
使用 UTF-16 编码的 XML 文档