A better way to use streaming in XSLT 3.0

可紊 提交于 2021-01-29 13:58:55

问题


A document I am trying to transform using streaming have the structure as follows

<Document>
    <Header>
        <Number>23</Number>
        <Type>3</Type>
    </Header>
    <Lines>
        <Line>
            <LineNumber>1</LineNumber>
        </Line>
        <Line>
            <LineNumber>2</LineNumber>
        </Line>
    </Lines>
    <Summary>
        <Total>42</Total>
    </Summary>
</Document>

The real output should have more complex structure, but for the moment I simplified it to just having a different naming

<Transformed>
    <DocumentHeader>
        <DocumentNumber>23</DocumentNumber>
        <DocumentType>P</DocumentType>
    </DocumentHeader>
    <DocumentLines>
        <DocumentLine>
            <LineNumber>1</LineNumber>
        </DocumentLine>
        <DocumentLine>
            <LineNumber>2</LineNumber>
        </DocumentLine>
    </DocumentLines>
    <DocumentTotal>42</DocumentTotal>
</Transformed>

As I could see one way to do it is to have separate templates for each element I need to process. All the templates could be stremable in this case, but it seems like it would be hard to maintain such an implementation. Unlike the sample above, a real-life document would contain many more fields to be processed

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.0">

    <xsl:mode streamable="yes"/>

    <xsl:template match="/Document">
        <xsl:element name="Transformed">
            <xsl:apply-templates select="*"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="/Document/Header">
        <xsl:element name="DocumentHeader">
            <xsl:apply-templates select="*"/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="/Document/Header/Type">
        <xsl:element name="DocumentType">
            <xsl:value-of select="if (text()='3') then 'P' else 'K'"/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="/Document/Header/Number">
        <xsl:element name="DocumentNumber">
            <xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="/Document/Lines">
        <xsl:element name="DocumentLines">
            <xsl:apply-templates select="*"/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="/Document/Lines/Line">
        <xsl:element name="DocumentLine">
            <xsl:element name="LineNumber">
                <xsl:value-of select="LineNumber"/>
            </xsl:element>
        </xsl:element>
    </xsl:template>

    <xsl:template match="/Document/Summary">
        <xsl:element name="DocumentTotal">
            <xsl:value-of select="Total"/>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

Another option is so-called burst-mode and here is my attempt to use it, in my opinion it looks ugly when I test an element name to choose which mode should be used

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.0">

    <xsl:mode streamable="yes"/>

    <xsl:template match="/">
        <xsl:element name="Transformed">
            <xsl:for-each select="Document/*">
                <xsl:choose>
                    <xsl:when test="self::Lines">
                        <xsl:apply-templates select="."/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates select="copy-of(.)" mode="non-streamable"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each>
        </xsl:element>
    </xsl:template>

    <xsl:template match="Header" mode="non-streamable">
        <xsl:element name="DocumentHeader">
            <xsl:element name="DocumentNumber">
                <xsl:value-of select="Number"/>    
            </xsl:element>
            <xsl:if test="string-length(Type) > 0">
                <xsl:element name="DocumentType">
                    <xsl:value-of select="if (Type='3') then 'P' else 'K'"/>
                </xsl:element>
            </xsl:if>
        </xsl:element>
    </xsl:template>

    <xsl:template match="Lines">
        <xsl:element name="DocumentLines">
            <xsl:apply-templates select="copy-of(Line)" mode="non-streamable"/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="Line" mode="non-streamable">
        <xsl:element name="DocumentLine">
            <xsl:element name="LineNumber">
                <xsl:value-of select="LineNumber"/>
            </xsl:element>
        </xsl:element>
    </xsl:template>

    <xsl:template match="Summary" mode="non-streamable">
        <xsl:element name="DocumentTotal">
            <xsl:value-of select="Total"/>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

So I am wonder if it could be done in a more pleasant way?


回答1:


If you need to rename each element node then the usual way is to write a template for each element performing that micro-transformation. In any case I would use literal result elements e.g.

<xsl:template match="Document">
    <Transformed>
        <xsl:apply-templates/>
    </Transformed>
</xsl:template>

and not xsl:element. It is not clear what you want to save or where you think that streaming for your first example requires those templates, it seems to be the natural XSLT solution for your transformation task, with or without streaming.

The only use of xsl:element would make sense to eliminate some templates with e.g.

<xsl:template match="Number | Lines | Line">
  <xsl:element name="Document{local-name()}">
    <xsl:apply-templates/>
  </xsl:element>
</xsl:template>

but it is not clear whether that renaming pattern is part of a simplification of your sample or a real requirement.

The only change you can't do in a pure element based template matching is the content change you perform with

<xsl:template match="/Document/Header/Type">
    <xsl:element name="DocumentType">
        <xsl:value-of select="if (text()='3') then 'P' else 'K'"/>
    </xsl:element>
</xsl:template>

in that case you would need to add a template for the text() child e.g. (with expand-text="yes" in place)

<xsl:template match="Document/Header/Type/text()">{if (. = 3) then 'P' else 'K'}</xsl:template>

or I would be tempted to use

<xsl:template match="Document/Header/Type/text()[. = 3]">P</xsl:template>
<xsl:template match="Document/Header/Type/text()">K</xsl:template>

Based on your comments you want to shorten the second XSLT sample you have shown, as I said, I see no need for any xsl:element use; as for the xsl:choose, I think you can just write a matching template:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.0">
    
    <xsl:strip-space elements="*"/>
    
    <xsl:output indent="yes"/>
    
    <xsl:mode streamable="yes"/>
    
    <xsl:template match="/*">
        <Transformed>
            <xsl:apply-templates/>
        </Transformed>
    </xsl:template>
    
    <xsl:template match="Document/*[not(self::Lines)]">
        <xsl:apply-templates select="copy-of()" mode="non-streamable"/>
    </xsl:template>
    
    <xsl:template match="Header" mode="non-streamable">
        <DocumentHeader>
            <DocumentNumber>
                <xsl:value-of select="Number"/>    
            </DocumentNumber>
            <xsl:if test="string-length(Type) > 0">
                <DocumentType>
                    <xsl:value-of select="if (Type='3') then 'P' else 'K'"/>
                </DocumentType>
            </xsl:if>
        </DocumentHeader>
    </xsl:template>
    
    <xsl:template match="Lines">
        <DocumentLines>
            <xsl:apply-templates select="Line!copy-of()" mode="non-streamable"/>
        </DocumentLines>
    </xsl:template>
    
    <xsl:template match="Line" mode="non-streamable">
        <Line>
            <LineNumber>
                <xsl:value-of select="LineNumber"/>
            </LineNumber>
        </Line>
    </xsl:template>
    
    <xsl:template match="Summary" mode="non-streamable">
        <DocumentTotal>
            <xsl:value-of select="Total"/>
        </DocumentTotal>
    </xsl:template>
    
</xsl:stylesheet>


来源:https://stackoverflow.com/questions/62850634/a-better-way-to-use-streaming-in-xslt-3-0

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!