使用Java将补充unicode字符序列化为XML文档

时间:2012-08-14 12:28:39

标签: java xml xslt unicode xml-serialization

我正在尝试使用补充的unicode字符序列化DOM文档,例如U + 1D49C(?,数学脚本大写字母A)。创建具有这样一个字符的节点不是问题(我只是将节点值设置为UTF-16等价物,“\ uD835 \ uDC9C”)。但是,在序列化时,Xalan和XSLTC(使用Transformer)和Xerces(使用LSSerializer)都会创建无效的字符实体,例如“��”而不是“𝒜”。我尝试了LSSerializer的“normalize-characters”参数,但不支持它。当编码为unicode时,只有Saxon才能正确使用,而不使用字符实体。

我不能在实践中使用Saxon(除了其他原因,我使用Java applet并且不想加载另一个jar),所以我正在寻找一个使用默认JDK库的解决方案。是否可以从带有补充unicode字符的DOM文档中序列化有效的XML文档?

[edit]我发现遇到此问题的其他人:http://www.dragishak.com/?p=131

[edit2]实际上,当我在类路径上没有xerces时,似乎可以使用LSSerializer(使用的类是com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl)。它不适用于变压器和com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl。

2 个答案:

答案 0 :(得分:1)

由于我没有看到任何答案,而其他人似乎也有同样的问题,我进一步研究了......

为了找到错误的起源,我使用了Xalan 2.7.1中的序列化程序源代码,该代码也用于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(属性格式)返回),因此它受到影响同样的错误。

ToStream正在使用org.apache.xml.serializer.CharInfo来检查字符是否应该被String替换,因此bug也可以在那里修复,而不是直接在ToStream中修复。 CharInfo正在使用一个带有字符实体列表的propery文件org.apache.xml.serializer.XMLEntities.properties,因此更改此文件也可能是修复错误的一种方法,尽管到目前为止它仅针对特殊设计而设计XML字符“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正在使用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都引用了manifest中的serializer.jar,因此如果serializer.jar位于同一目录中并且xalan.jar或xsltc.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)。但是,Apache在可用时更喜欢ToStream而不是DOMSerializerImpl,因此它可能对其他事情表现得更好(或者它可能只是重组)。最重要的是,他们甚至在Xerces 2.9.0中弃用了DOMSerializerImpl。因此,以下解决方法可能会产生副作用:

  • 当Xerces和Apache的序列化程序在类路径上时,将“(doc.getImplementation())。createLSSerializer()”替换为“new org.apache.xerces.dom.DOMSerializerImpl()”

    < / LI>
  • 当Apache的序列化程序在类路径上(例如因为xalan而不是Xerces)时,尝试用“new com.sun.org.apache”替换“(doc.getImplementation())。createLSSerializer()” .xml.internal.serialize.DOMSerializerImpl()“(后备是必要的,因为此类可能在将来消失)

这两个解决方法在编译时会产生警告。

我没有针对XSLT转换的解决方法,但这超出了问题的范围。我猜一个人可以转换到另一个DOM文档并使用DOMSerializerImpl进行序列化。

其他一些解决方法,对某些人来说可能是更好的解决方案:

  • 将Saxon与变形金刚一起使用

  • 使用UTF-16编码的XML文档

答案 1 :(得分:1)

这是一个适合我的例子。代码是用在Java 7上运行的Groovy编写的,您可以轻松地将其转换为Java,因为我在示例中使用了所有Java API。如果传入一个具有补充(平面1)unicode字符的DOM文档,您将返回一个字符串,该字符串具有正确序列化的字符。例如,如果文档具有unicode脚本L(请参阅http://www.fileformat.info/info/unicode/char/1d4c1/index.htm),它将在返回的字符串中序列化为&#x1d4c1而不是&#55349;&#56513;(这是您将获得的Xalan Transformer)。

import org.w3c.dom.Document
...

def String writeToStringLS( Document doc ) {
  def domImpl = doc.getImplementation()
  def implLS = domImpl.getFeature("LS", "3.0")
  def lsOutput = implLS.createLSOutput()
  lsOutput.encoding = "UTF-8"
  def bo = new ByteArrayOutputStream()
  def out = new BufferedWriter( new OutputStreamWriter( bo, "UTF-8") )
  lsOutput.characterStream = out
  def lsWriter = implLS.createLSSerializer()
  def result = lsWriter.write(doc, lsOutput)
  return bo.toString()
}