由于我没看到任何答案,并且其他人似乎也有同样的问题,所以我进一步研究了这个问题...
为了找出错误的起源,我使用了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
)在characters方法中执行得更好,但它仅处理纯文本并忽略所有标记;人们会认为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字符,但在实践中似乎不起作用(也许不能为Unicode字符调用processDirty
)...
com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl
使用支持unicode的com.sun.org.apache.xml.internal.serialize.XMLSerializer
。奇怪的是,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
将出现在类路径上!如果类路径上只有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
)。然而,当ToStream
可用时,Apache更喜欢使用它,因此可能对其他事情有更好的表现(或者只是一种重新组织)。此外,他们甚至在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转换的解决方法,但这超出了问题的范围。我猜可以将其转换为另一个DOM文档,并使用DOMSerializerImpl进行序列化。
其他一些解决方法可能更适合某些人:
- 使用带有Transformer的Saxon
- 使用UTF-16编码的XML文档