【问题标题】:XSLT to process XML with multiple namespacesXSLT 处理具有多个名称空间的 XML
【发布时间】:2019-05-01 22:12:43
【问题描述】:

我有多个名称空间的 XML,但所有名称空间中的元素都是相同的。我需要将此 XML 转换为 JSON,但我不确定如何动态传递/更改命名空间,而不用不同的命名空间重复相同的 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
    }
}

【问题讨论】:

  • 对于预期的结果,我不明白第二个“JSON对象”中的数据来自哪里,也不明白总数来自哪里,输入有&lt;wd:amount&gt;-100&lt;/wd:amount&gt;&lt;wd:amount&gt;30&lt;/wd:amount&gt;,输出两次"amount": -100。一般来说,XSLT 3 中的计数和求和,无论是否是流式的,都可以使用累加器以声明方式完成。 XSLT 2/3 中与命名空间无关的一种方式是使用例如&lt;xsl:template match="root"&gt;&lt;xsl:iterate select="*:Report_Data/*:ReportEntry".

标签: xml-namespaces xslt-3.0


【解决方案1】:

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

<?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

【讨论】:

  • Martin,首先感谢您花时间回答我的问题。让我也为之前没有在问题中提及这一点而道歉。我使用的工具使用 Saxon-EE 9.7。我不确定通配符方法是否适用于该版本。因为当我使用带有 xsl:iterate 的通配符方法时出现错误“Xpath is Invalid”。 Saxon-EE 9.7 是否有替代通配符方法?
  • @XYZ,如果我没有完全弄错的话,该功能自 XPath 2.0 以来就存在,所以我不明白为什么它会在 Saxon 9.7 中失败。但是 Saxon 9.7 是在 XSLT 3 规范最终确定之前完成的,所以我不能保证我发布的基于最终 XSLT 3 规范和 Saxon 9.9 测试的示例适用于 9.7。 “Xpath 无效”听起来不像是来自 Saxon 本身的精确错误消息。您使用哪种工具,它没有提供或允许您获得更精确的错误消息和错误位置吗?
  • @XYZ, w3.org/TR/xpath20/#doc-xpath-Wildcard 表明从 XPath 2 开始就存在通配符,因此 Saxon 9.7 肯定不会因为通配符而失败。
  • 我认为我必须用于集成的工具/IDE(Workday Studio)是问题所在。我使用 XML Spy 运行了相同的 XSLT,它运行良好。我需要看看那个 IDE 是否有一些限制或者是什么导致了这种情况。谢谢你的帮助,马丁。
猜你喜欢
  • 1970-01-01
  • 2013-04-11
  • 2018-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-11
相关资源
最近更新 更多