使用XSLT转换XML并保留Unicode字符

时间:2013-08-07 05:50:33

标签: java xml xslt unicode utf-8

我的XSLT转换已成功几个月,直到我遇到一个带有Unicode字符的XML文件(很可能是表情符号)。我需要保留Unicode,但XSLT将其转换为HTML实体。我认为将编码设置为UTF-8可以解决我的问题,但我仍然遇到问题。

任何帮助表示赞赏。代码:

private byte[] transform(InputStream stream) throws Exception{
    System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl"); 

    Transformer xmlTransformer;

    xmlTransformer = (TransformerImpl) TransformerFactory.newInstance().newTransformer(new   StreamSource(createXsltStylesheet()));
    xmlTransformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

    XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(stream,"UTF-8");
    Source staxSource = new StAXSource(reader, true); 
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    Writer writer = new OutputStreamWriter(outputStream, "UTF-8");
    xmlTransformer.transform(staxSource, new StreamResult(writer));


    return outputStream.toByteArray();
}

如果我添加

xmlTransformer.setOutputProperty(OutputKeys.METHOD, "text");

保留了Unicode,但XML不是。

4 个答案:

答案 0 :(得分:0)

这一行很可疑:

stream = IOUtils.toInputStream(outputStream.toString(),"UTF-8");

您正在使用平台的默认编码将ByteArrayOutputStream转换为字符串,该编码可能不是UTF-8。将其更改为

stream = IOUtils.toInputStream(outputStream.toString("UTF-8"),"UTF-8");

或者,为了获得更好的性能,只需将字节数组包装在ByteArrayInputStream

return new ByteArrayInputStream(outputStream.toByteArray());

答案 1 :(得分:0)

尝试使用Apache Serializer将XML转换为String。

//Serialize DOM
OutputFormat format    = new OutputFormat (doc); 
// as a String
StringWriter stringOut = new StringWriter ();    
XMLSerializer serial   = new XMLSerializer (stringOut, 
                                                  format);
serial.serialize(doc);
// Display the XML
System.out.println(stringOut.toString());

答案 2 :(得分:0)

只是遇到了同样的问题,经过长时间的研究,这就是我所得出的结论。

Java XSLT处理器将多字节UTF-8字符转换为HTML实体,即使输出模式是XML ...如果text()节点中出现多字节字符不是包裹在CDATA中。 如果字符包装在CDATA中(用于输出),则多字节字符将保留

我的问题:

我有一个看起来像这样的xml文件,带有表情符号。     

<events>
    <event>
       <id>RANDOMID</id>
       <blah>
          <blahId>FOOONE</blahId>
       </blah>
       <blah>
          <blahId>FOOTWO</blahId>
       </blah>
       <eventComment>Did some things. Had some Fun. </eventComment>
    </event>
</events>

我从一个看起来像这样的XSL样式表开始:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns="http://www.w3.org/TR/xhtml1/strict"
>
    <xsl:output method = "xml" version="1.0" encoding = "UTF-8" omit-xml-declaration="no" indent="yes" />

    <xsl:template match="/">
        <events>
            <xsl:for-each select="/events/event">
                <event>
                    <xsl:copy-of select="./*[name() != 'blah'"/>
                    <xsl:for-each select="./blah">
                        <blahId><xsl:copy-of select="./blahId/text()"/></blahId>
                    </xsl:for-each>
                </event>
            </xsl:for-each>
        </events>
    </xsl:template>
</xsl:stylesheet>

使用java Transformer运行此操作始终生成&#55357;&#56397;我的表情符号所在的位置。后续尝试解析生成的文档失败,并显示以下异常消息:

org.xml.sax.SAXParseException; lineNumber: y; columnNumber: x; Character reference "&#55357" is an invalid XML character.

猪食!

在命令行上使用xsltproc进行测试是没用的,因为xsltproc在涉及多字节字符时并不愚蠢。我得到了我预期的输出。

解决方案

通过在eventComment标记xsl:output属性中指定QName,让XSLT在CDATA中包装cdata-section-elements将保留字节并与xsltproc 和java Transformer一起使用

这里的神奇之处在于cdata-secion-elements标记的输出<xsl:output>属性。 https://www.w3.org/TR/xslt#output

我将我的XSL模板更新为:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns="http://www.w3.org/TR/xhtml1/strict"
>
    <xsl:output  cdata-section-elements="eventComment" method="xml" version="1.0" encoding="UTF-8" omit-xml-declaration="no" indent="yes"/>

    <xsl:template match="/">
        <events>
            <xsl:for-each select="/events/event">
                <event>
                    <xsl:copy-of select="./*[name() != 'blah' and name() != 'eventComment']"/>
                    <!-- For the cdata-section-elements to resolve that eventComment needs to be preserved as CDATA
                        (so we don't get java doing stupid things with unicode escapment)
                         it needs to be explicitly referenced here.
                    -->
                    <eventComment><xsl:copy-of select="./eventComment/text()"/></eventComment>
                    <xsl:for-each select="./blah">
                        <blahId><xsl:copy-of select="./blahId/text()"/></blahId>
                    </xsl:for-each>
                </event>
            </xsl:for-each>
        </events>
    </xsl:template>
</xsl:stylesheet>

现在我来自xsltproc和java Transformer的输出都是这样的,并且使用java DocumentBuilders快乐地解析。

<?xml version="1.0" encoding="UTF-8"?>
<events xmlns="http://www.w3.org/TR/xhtml1/strict">
  <event>
    <id xmlns="">RANDOMID</id>
    <eventComment><![CDATA[Did some things. Had some Fun. ]]></eventComment>
    <blahId>FOO</blahId>
    <blahId>FOOTOO</blahId>
  </event>
</events>

答案 3 :(得分:0)

通过将以下行添加到原始XML来解决了类似的问题: document.appendChild(document.createProcessingInstruction(StreamResult.PI_DISABLE_OUTPUT_ESCAPING, ""));

请参阅:Writing emoji to XML file in JAVA

也许可以对变压器使用类似的设置...