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

Posted

技术标签:

【中文标题】使用 Java 将补充 unicode 字符序列化为 XML 文档【英文标题】:Serializing supplementary unicode characters into XML documents with Java 【发布时间】:2012-08-10 17:51:22 【问题描述】:

我正在尝试使用补充 unicode 字符(例如 U+1D49C(????,数学脚本大写 A))序列化 DOM 文档。创建具有这样一个字符的节点不是问题(我只是将节点值设置为等效的 UTF-16,“\uD835\uDC9C”)。然而,在序列化时,Xalan 和 XSLTC(使用 Transformer)和 Xerces(使用 LSSerializer)都会创建无效字符实体,例如“”而不是“【参考方案1】:

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

为了找到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,但当xalanxsltc 在类路径上时,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.jarxsltc.jar 都在清单中引用 serializer.jar,所以如果 serializer.jar 在同一目录中并且 xalan.jarxsltc.jar 在类路径上,则它最终会出现在类路径中!如果类路径中只有xercesImpl.jarxml-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 进行序列化。

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

使用SaxonTransformer

使用 UTF-16 编码的 XML 文档

【讨论】:

我在 jira 上创建了一个错误报告:issues.apache.org/jira/browse/XALANJ-2560 Christopher Taylor 对 jira 发表了评论:“可能是 XALANJ-2419 的骗局,它附带了一个补丁。在 ***.com/questions/10511474/… 中也有引用”。 Xalan 最近有新的活动,因此有希望最终修复错误。 你让我进入了正确的方向 :) 我使用了一个库,它本身就使用了 xalan,其中不需要 xalan-serializer。虽然 xalan 附带对 xalan-serializer 的依赖项,但转储该依赖项会使我的库正常工作,万岁! 嗯,实际上我认为它是需要 xml-apis 的 xercesImpl 的依赖项,转储该依赖项使我的库工作。随便:万岁!注意:尽可能少的使用依赖【参考方案2】:

这是一个对我有用的例子。代码是用在 Java 7 上运行的 Groovy 编写的,您可以轻松地将其转换为 Java,因为我在示例中使用了所有 Java API。如果您传入一个具有补充(平面 1)unicode 字符的 DOM 文档,您将返回一个字符串,该字符串已正确序列化了这些字符。例如,如果文档有一个 unicode Script L(参见http://www.fileformat.info/info/unicode/char/1d4c1/index.htm),它将在返回的字符串中序列化为&#x1d4c1,而不是��(这是您将使用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()

【讨论】:

以上是关于使用 Java 将补充 unicode 字符序列化为 XML 文档的主要内容,如果未能解决你的问题,请参考以下文章

Java字符串就是Unicode字符序列

关于前台js编码后台java解码

day13.复习补充

java里关于String的编码与解码

java.sql.SQLException: Illegal mix of collations (utf8_unicode_ci,IMPLICIT) ....错误

js 对Array的补充