使用多个名称空间处理 XML 的 XSLT

XSLT to process XML with multiple namespaces

我 XML 有多个名称空间,但所有名称空间中的元素都相同。我需要将此 XML 转换为 JSON 但我不确定如何动态地 pass/change 命名空间而不用不同的命名空间重复相同的 XSLT 代码。

因此,我在输出中获取的数据仅为我定义的命名空间。

下面是我的示例 XML -

<?xml version="1.0" encoding="utf-8"?>
<root>
    <wd:Report_Data xmlns:wd="urn:com.workday.report/INT1111a_CR_REV_FINRA_Connect_AR_Adjustment_Transaction">
        <wd:Report_Entry>
            <wd:company>TESTCOMPANY</wd:company>
            <wd:revenue_stream>X</wd:revenue_stream>
            <wd:customer_id>XCUSTOMER</wd:customer_id>
            <wd:invoice_id>201900000035</wd:invoice_id>
            <wd:post_date>2019-05-01</wd:post_date>
            <wd:initiatedby>Test Data</wd:initiatedby>
            <wd:amount>-100</wd:amount>
            <wd:trans_date>2019-04-22</wd:trans_date>
            <wd:legacy>false</wd:legacy>
            <wd:exported>2019-05-01T12:13:02.773-07:00</wd:exported>
            <wd:reason>Credit Invoice</wd:reason>
        </wd:Report_Entry>
    </wd:Report_Data>
    <wd:Report_Data xmlns:wd="urn:com.workday.report/INT1111b_CR_REV_FINRA_Connect_AR_Writeoff_Transaction">
        <wd:Report_Entry>
            <wd:company>TESTCOMPANY</wd:company>                
            <wd:revenue_stream>X</wd:revenue_stream>            
            <wd:customer_id>XCUSTOMER</wd:customer_id>
            <wd:invoice_id>201900000020</wd:invoice_id>
            <wd:post_date>2019-05-01</wd:post_date>
            <wd:amount>30</wd:amount>
            <wd:trans_date>2019-04-01</wd:trans_date>
            <wd:legacy>false</wd:legacy>
            <wd:exported>2019-05-01T12:13:03.030-07:00</wd:exported>
            <wd:reason>Disputed Amount</wd:reason>
        </wd:Report_Entry>
    </wd:Report_Data>
</root>
Below is the XSLT i have created - 

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:wd="urn:com.workday.report/INT1111a_CR_REV_FINRA_Connect_AR_Adjustment_Transaction" xmlns:wd1="urn:com.workday.report/INT1111b_CR_REV_FINRA_Connect_AR_Writeoff_Transaction" exclude-result-prefixes="xs" version="3.0">
    <xsl:mode streamable="yes" on-no-match="shallow-skip"/>
    <xsl:output method="text" encoding="UTF-8" indent="no"/>
    <xsl:template match="root/wd:Report_Data">
        <xsl:iterate select="wd:Report_Entry/copy-of()">
            <!--Define Running Totals for Statistics -->
            <xsl:param name="TotalCount" select="0"/>
            <xsl:param name="TotalAmount" select="0"/>
            <!--Write Statistics -->
            <xsl:on-completion>
                <xsl:text>{"Stats": </xsl:text>
                <xsl:text>{"Total Count": </xsl:text>
                <xsl:value-of select="$TotalCount"/>
                <xsl:text>,</xsl:text>
                <xsl:text>"Total Amount": </xsl:text>
                <xsl:value-of select="$TotalAmount"/>
                <xsl:text>}}</xsl:text>
            </xsl:on-completion>
            <!--Write Details -->
            <xsl:text>{"id": "</xsl:text>
            <xsl:value-of select="wd:id"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"company": "</xsl:text>
            <xsl:value-of select="wd:company"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"trans_type": "</xsl:text>
            <xsl:value-of select="wd:trans_type"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"revenue_stream": "</xsl:text>
            <xsl:value-of select="wd:revenue_stream"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"customer_id": "</xsl:text>
            <xsl:value-of select="wd:customer_id"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"invoice_id": "</xsl:text>
            <xsl:value-of select="wd:invoice_id"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"post_date": "</xsl:text>
            <xsl:value-of select="wd:post_date"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"initiatedby": "</xsl:text>
            <xsl:value-of select="wd:initiatedby"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"amount": </xsl:text>
            <xsl:value-of select="wd:amount"/>
            <xsl:text>,</xsl:text>
            <xsl:text>"trans_date": "</xsl:text>
            <xsl:value-of select="wd:trans_date"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"legacy": </xsl:text>
            <xsl:value-of select="wd:legacy"/>
            <xsl:text>,</xsl:text>
            <xsl:text>"exported": "</xsl:text>
            <xsl:value-of select="wd:exported"/>
            <xsl:text>"}</xsl:text>
            <!--Store Running Totals -->
            <xsl:next-iteration>
                <xsl:with-param name="TotalCount" select="$TotalCount + 1"/>
                <xsl:with-param name="TotalAmount" select="$TotalAmount + wd:amount"/>
            </xsl:next-iteration>
        </xsl:iterate>
    </xsl:template>
</xsl:stylesheet>
Expected Result - 
{
    "id": "",
    "company": "TESTCOMPANY",
    "trans_type": "",
    "revenue_stream": "",
    "customer_id": "XCUSTOMER",
    "invoice_id": "201900000035",
    "post_date": "2019-05-01",
    "initiatedby": "Test Data",
    "amount": -100,
    "trans_date": "2019-04-22",
    "legacy": false,
    "exported": "2019-05-01T12:13:02.773-07:00"
}
{
    "id": "",
    "company": "TESTCOMPANY",
    "trans_type": "",
    "revenue_stream": "X",
    "customer_id": "XCUSTOMER",
    "invoice_id": "201900000035",
    "post_date": "2019-05-01",
    "initiatedby": "Test Data",
    "amount": -100,
    "trans_date": "2019-04-22",
    "legacy": false,
    "exported": "2019-05-01T12:13:02.773-07:00"
} {
    "Stats": {
        "Total Count": 2,
        "Total Amount": -200
    }
}

这是一个使用通配符选择器 *:foo 和累加器生成 JSON 输出的示例,因为我所知道的唯一流式 XSLT 3 处理器是 Saxon 9 EE 我也使用了扩展元素 saxon:array,因为它可以更轻松地使用数组创建 JSON 输出。在纯 XSLT 3 中,您只有 XPath 3.1 数组构造函数 []array { } 从像 xsl:apply-templates:[=21 这样的 XSLT 指令动态生成数组数据总是有点麻烦=]

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:saxon="http://saxon.sf.net/"
    extension-element-prefixes="saxon"
    exclude-result-prefixes="#all" version="3.0">

    <xsl:mode use-accumulators="#all" streamable="yes"/>

    <xsl:output method="json" indent="yes"/>

    <xsl:accumulator name="entry-count" as="xs:integer" initial-value="0" streamable="yes">
        <xsl:accumulator-rule match="*:Report_Data/*:Report_Entry" select="$value + 1"/>
    </xsl:accumulator>

    <xsl:accumulator name="amount-sum" as="xs:decimal" initial-value="0" streamable="yes">
        <xsl:accumulator-rule match="*:Report_Data/*:Report_Entry/*:amount/text()"
            select="$value + xs:decimal(.)"/>
    </xsl:accumulator>

    <xsl:template match="root">
        <xsl:map>
            <xsl:map-entry key="local-name()">
                <saxon:array>
                    <xsl:apply-templates select="*:Report_Data/*:Report_Entry"/>
                    <xsl:sequence
                        select="
                            map {
                                'Stats': map {
                                    'Total Count': accumulator-after('entry-count'),
                                    'Total Amount': accumulator-after('amount-sum')
                                }
                            }"
                    />
                </saxon:array>
            </xsl:map-entry>
        </xsl:map>
    </xsl:template>

    <xsl:template match="*:Report_Entry">
        <xsl:sequence
            select="
                map {
                    'id': string(*:id),
                    'amount': xs:decimal(*:amount)
                }"
        />
    </xsl:template>

</xsl:stylesheet>

不幸的是,在 oXygen 21、9.8.0.12 和 9.9.1.1 中有两个版本的 Saxon 9 EE,这只通过了 9.9 中的流式分析,所以结果是

{ 
    "root": [
      {
        "amount": -100,
        "id": null
      },
      {
        "amount": 30,
        "id": null
      },
      { 
        "Stats": { 
          "Total Count": 2,
          "Total Amount": -70 } }
    ] }

当然,使用通配符 *:foo 的方法也可以与您的 xsl:iterate 方法一起使用:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:wd="urn:com.workday.report/INT1111a_CR_REV_FINRA_Connect_AR_Adjustment_Transaction" xmlns:wd1="urn:com.workday.report/INT1111b_CR_REV_FINRA_Connect_AR_Writeoff_Transaction" exclude-result-prefixes="xs" version="3.0">
    <xsl:mode streamable="yes" on-no-match="shallow-skip"/>
    <xsl:output method="text" encoding="UTF-8" indent="no"/>
    <xsl:template match="root">
        <xsl:iterate select="*:Report_Data/*:Report_Entry/copy-of()">
            <!--Define Running Totals for Statistics -->
            <xsl:param name="TotalCount" select="0"/>
            <xsl:param name="TotalAmount" select="0"/>
            <!--Write Statistics -->
            <xsl:on-completion>
                <xsl:text>{"Stats": </xsl:text>
                <xsl:text>{"Total Count": </xsl:text>
                <xsl:value-of select="$TotalCount"/>
                <xsl:text>,</xsl:text>
                <xsl:text>"Total Amount": </xsl:text>
                <xsl:value-of select="$TotalAmount"/>
                <xsl:text>}}</xsl:text>
            </xsl:on-completion>
            <!--Write Details -->
            <xsl:text>{"id": "</xsl:text>
            <xsl:value-of select="*:id"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"company": "</xsl:text>
            <xsl:value-of select="*:company"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"trans_type": "</xsl:text>
            <xsl:value-of select="*:trans_type"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"revenue_stream": "</xsl:text>
            <xsl:value-of select="*:revenue_stream"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"customer_id": "</xsl:text>
            <xsl:value-of select="*:customer_id"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"invoice_id": "</xsl:text>
            <xsl:value-of select="*:invoice_id"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"post_date": "</xsl:text>
            <xsl:value-of select="*:post_date"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"initiatedby": "</xsl:text>
            <xsl:value-of select="*:initiatedby"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"amount": </xsl:text>
            <xsl:value-of select="*:amount"/>
            <xsl:text>,</xsl:text>
            <xsl:text>"trans_date": "</xsl:text>
            <xsl:value-of select="*:trans_date"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"legacy": </xsl:text>
            <xsl:value-of select="*:legacy"/>
            <xsl:text>,</xsl:text>
            <xsl:text>"exported": "</xsl:text>
            <xsl:value-of select="*:exported"/>
            <xsl:text>"}</xsl:text>
            <!--Store Running Totals -->
            <xsl:next-iteration>
                <xsl:with-param name="TotalCount" select="$TotalCount + 1"/>
                <xsl:with-param name="TotalAmount" select="$TotalAmount + *:amount"/>
            </xsl:next-iteration>
        </xsl:iterate>
    </xsl:template>
</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/ej9EGcA/1