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

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

在实践中我不能使用 Saxon(除其他原因外,我使用 Java 小程序并且不想加载另一个 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。

【问题讨论】:

  • 你在处理两端吗?如果是这样,您是否有理由不能在序列化端自己编码(base64、urlencode 等),然后在反序列化端反转过程?
  • 我可以这样做,但我正在寻找更简单的解决方案。
  • 这看起来像是序列化程序中的一个明确的错误。 Character Range 包括补充并明确排除代理项,因此 XML 中的“char”是 Unicode 标量值,Character References 存在格式良好的约束:“使用字符引用引用的字符必须与 Char 的产生相匹配。” Xalan 和 XSLTC 违反了这一规定。
  • 我发现避免 xalan 序列化程序错误的一种方法是使用 UTF-16 编码... LSSerializer.writeToString 也在使用 UTF-16...
  • 您是说使用 Xalan 对该 XML 文档应用身份转换:<t>𝒜</t> 会产生其他结果吗?我简直不敢相信。

标签: java xml xslt unicode xml-serialization


【解决方案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 的骗局,它附带了一个补丁。在 stackoverflow.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()
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-25
    • 1970-01-01
    相关资源
    最近更新 更多