将 XSLT 转换的 XML 片段写入 XMLStreamWriter

Posted

技术标签:

【中文标题】将 XSLT 转换的 XML 片段写入 XMLStreamWriter【英文标题】:Writing XSLT transformed XML fragments to a XMLStreamWriter 【发布时间】:2018-09-23 14:08:07 【问题描述】:

我遇到了以下问题:

大型输出文件 (zip),其中包含一个 xml 文档(“FeatureCollection”) 相对较小的 xml 片段 XSLT 转换后,每个片段都需要作为“featureMember”写入 XMLStream 名称空间定义仅在“FeatureCollection”(根)标签上。

现在,我通过使用单独的字节流来解析片段来实现这一点。我还包装了 XMLStream 以避免 XSLT 转换器 (Saxon) 打开/关闭文档或关闭流。

但是,我觉得解决方案太复杂了。应该可以将 JAXB 上下文作为源(没有中间字节流)。见code-sn-p:

        try 
            XMLStreamWriterWrapper writer = getWriter( xmlFile );
            for ( Map.Entry<String, String> entry : prefixMapper.getNamespaces().entrySet() ) 
                writer.setPrefix( entry.getValue(), entry.getKey() );
            

            writer.getWrapperWriter().writeStartDocument();
            writer.writeStartElement( GML_URI, "FeatureCollection" );

            for ( Map.Entry<String, String> entry : prefixMapper.getNamespaces().entrySet() ) 
                writer.getWrapperWriter().writeNamespace( entry.getValue(), entry.getKey() );
            

            while ( dtoIterator.hasNext() ) 
                writer.writeStartElement( GML_URI, "featureMember" );
                D dto = dtoIterator.next();
                hideAttributes( dto );

                J jaxb = transformToJaxb( dto );

                Source untransformed = new JAXBSource( jaxbContext, getRootElement( jaxb ) );
                getTransformer().transform( untransformed, new StAXResult( writer) );
                writer.writeEndElement();
            

            writer.writeEndElement();
            writer.getWrapperWriter().writeEndDocument();
            writer.getWrapperWriter().flush();
            writer.getWrapperWriter().close();
        
        catch ( IOException | JAXBException | TransformerException | XMLStreamException e ) 
            LOG.error( e );
            throw new IllegalArgumentException( e );
        

private XMLStreamWriterWrapper getWriter( File xmlFile ) throws XMLStreamException, FileNotFoundException, IOException 
    XMLOutputFactory xof = XMLOutputFactory.newFactory();
    xof.setProperty( XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE );

    XMLStreamWriter writer = xof.createXMLStreamWriter( new BufferedOutputStream( new FileOutputStream( xmlFile ) ) );

    return new XMLStreamWriterWrapper( writer );
           

预期结果(来自非优化解决方案):

<?xml version="1.0" ?><gml:FeatureCollection xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:brocom="http://www.broservices.nl/xsd/brocommon/3.0" xmlns:bro="http://www.pdok.nl/bro">
<gml:featureMember>
    <bro:Characteristics gml:id="BRO_id_1">
        <brocom:broId>id_1</brocom:broId>
    </bro:Characteristics>
</gml:featureMember>
<gml:featureMember>
    <bro:Characteristics gml:id="BRO_id_2">
        <brocom:broId>id_2</brocom:broId>
    </bro:Characteristics>
</gml:featureMember>

但是结果(来自上面的代码片段)是:

<?xml version="1.0" ?><gml:FeatureCollection xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:brocom="http://www.broservices.nl/xsd/brocommon/3.0" xmlns:bro="http://www.pdok.nl/bro">
<gml:featureMember>
    <bro:Characteristics gml:id="BRO_id_1">
        <broId xmlns="http://www.broservices.nl/xsd/brocommon/3.0">id_1</broId>
    </bro:Characteristics>
</gml:featureMember>
<gml:featureMember>
    <bro:Characteristics gml:id="BRO_id_2">
        <broId xmlns="http://www.broservices.nl/xsd/brocommon/3.0">id_2</broId>
    </bro:Characteristics>
</gml:featureMember>

问题:

    XMLStreamWriter 似乎忽略了属性 IS_REPAIRING_NAMESPACES。怎么了? 我能否优化 Saxon 转换器,使其在部分 xml 上运行。 Ergo:我真的需要包装 XMLOutputStream 以使转换器不会编写打开/关闭文档或完全关闭流吗? 我是否正确定义了命名空间(使用 setPrefix 和 writeNameSpace)。 使用 JAXB 编组器时,我可以在编组器上设置属性,例如:JAXB_FORMATTED_OUTPUT、JAXB_FRAGMENT。我也可以这样解决吗?

【问题讨论】:

【参考方案1】:

请注意,您可以使用 XMLStreamWriter 的 Saxon 实现来代替您正在使用的实现 (Processor.newSerializer().getXMLStreamWriter())。这可能会给您更多的控制权,并可能解决命名空间问题。

您可以尝试提供new net.sf.saxon.stax.ReceiverToXMLStreamWriter(writer),而不是提供new StaxResult(writer) 作为transform() 的第二个参数,然后您可以子类化ReceiverToXMLStreamWriter,这样startDocument()endDocument() 调用什么都不做。

关于命名空间的 XMLStreamWriter 处理,恐怕 API 规范非常模糊。我发现咨询http://veithen.github.io/2009/11/01/understanding-stax.html 很有帮助,尽管它没有官方地位。我不能保证 Saxon 的解释是 API 的作者想要的(没有参考实现或测试套件)。

【讨论】:

@Mickael Kay:感谢您的回复。 XMLStreamWriter 的 Saxon 实现看起来很有希望。使用建议的 ReceiverToXMLStreamWriter 会给出一个空指针:在 net.sf.saxon.s9api.XsltTransformer.getDestinationReceiver(XsltTransformer.java:631) 在没有配置的情况下使用Processor.newSerializer().getXMLStreamWriter() 已经解决了问题。 参考确实很有用。我现在写startDocumentstartElement,并在使用setPrefixwriteNamespace 初始化命名空间之后直接写。作者似乎认识到了这些,并且不再在嵌套元素上添加新的命名空间定义。 谢谢。

以上是关于将 XSLT 转换的 XML 片段写入 XMLStreamWriter的主要内容,如果未能解决你的问题,请参考以下文章

XSLT 无效令牌导致 XML 文档无效

需要示例代码片段帮助

使用 Xslt 将 XML 转换为 XML

XSLT 将顺序 XML 转换为分层 XML

使用带有条件的 XSLT 将 XML 转换为 XML

如何使用 XSLT 将 JSON 转换为 XML?