【问题标题】:XSLT: Transform nodes by comparing their child nodesXSLT:通过比较子节点来转换节点
【发布时间】:2017-01-13 15:23:46
【问题描述】:

首先,我知道SO question 有点不同。

我有一个如下所示的 XML 文件:

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03">
    <CstmrCdtTrfInitn>
        <GrpHdr>
            <MsgId>123</MsgId>
            <CreDtTm>321</CreDtTm>
            <NbOfTxs>10</NbOfTxs>
            <CtrlSum>18700.68</CtrlSum>
            <InitgPty>
                <Nm>some info</Nm>
            </InitgPty>
        </GrpHdr>

        <PmtInf>
            <!-- start -->
            <PmtInfId>asd</PmtInfId>
            <PmtMtd>TRF</PmtMtd>
            <BtchBookg>false</BtchBookg>
            <PmtTpInf>
                <InstrPrty>NORM</InstrPrty>
                <SvcLvl>
                    <Prtry>test</Prtry>
                </SvcLvl>
            </PmtTpInf>
            <ReqdExctnDt>date</ReqdExctnDt>
            <Dbtr>
                <Nm>something</Nm>
                <PstlAdr>
                    <AdrLine>addr 1</AdrLine>
                </PstlAdr>
            </Dbtr>
            <!-- end -->

            <CdtTrfTxInf>
                <PmtId>
                    <InstrId>16082672122</InstrId>
                    <EndToEndId>16082672122</EndToEndId>
                </PmtId>
                <Amt>
                    <InstdAmt Ccy="RON">2159.41</InstdAmt>
                </Amt>
                <CdtrAgt>
                    <FinInstnId>
                        <BIC>some bic</BIC>
                    </FinInstnId>
                </CdtrAgt>
            </CdtTrfTxInf>
        </PmtInf>

        <PmtInf>
            <!-- start -->
            <PmtInfId>asd</PmtInfId>
            <PmtMtd>TRF</PmtMtd>
            <BtchBookg>false</BtchBookg>
            <PmtTpInf>
                <InstrPrty>NORM</InstrPrty>
                <SvcLvl>
                    <Prtry>test</Prtry>
                </SvcLvl>
            </PmtTpInf>
            <ReqdExctnDt>date</ReqdExctnDt>
            <Dbtr>
                <Nm>something</Nm>
                <PstlAdr>
                    <AdrLine>addr 1</AdrLine>
                </PstlAdr>
            </Dbtr>
            <!-- end -->

            <CdtTrfTxInf>
                <PmtId>
                    <InstrId>16082672122</InstrId>
                    <EndToEndId>16082672122</EndToEndId>
                </PmtId>
                <Amt>
                    <InstdAmt Ccy="RON">2159.41</InstdAmt>
                </Amt>
                <CdtrAgt>
                    <FinInstnId>
                        <BIC>some bic</BIC>
                    </FinInstnId>
                </CdtrAgt>
            </CdtTrfTxInf>
        </PmtInf>

        <PmtInf>
            <!-- start -->
            <PmtInfId>asd</PmtInfId>
            <PmtMtd>TRF</PmtMtd>
            <BtchBookg>false</BtchBookg>
            <PmtTpInf>
                <InstrPrty>NORM</InstrPrty>
                <SvcLvl>
                    <Prtry>test</Prtry>
                </SvcLvl>
            </PmtTpInf>
            <ReqdExctnDt>date</ReqdExctnDt>
            <Dbtr>
                <Nm>something</Nm>
                <PstlAdr>
                    <AdrLine>addr 1</AdrLine>
                </PstlAdr>
            </Dbtr>
            <!-- end -->

            <CdtTrfTxInf>
                <PmtId>
                    <InstrId>16082672122</InstrId>
                    <EndToEndId>16082672122</EndToEndId>
                </PmtId>
                <Amt>
                    <InstdAmt Ccy="RON">2159.41</InstdAmt>
                </Amt>
                <CdtrAgt>
                    <FinInstnId>
                        <BIC>some bic</BIC>
                    </FinInstnId>
                </CdtrAgt>
            </CdtTrfTxInf>
        </PmtInf>

        <PmtInf>
            <!-- start -->
            <PmtInfId>asd</PmtInfId>
            <PmtMtd>TRF</PmtMtd>
            <BtchBookg>false</BtchBookg>
            <PmtTpInf>
                <InstrPrty>NORM</InstrPrty>
                <SvcLvl>
                    <Prtry>test</Prtry>
                </SvcLvl>
            </PmtTpInf>
            <ReqdExctnDt>date</ReqdExctnDt>
            <Dbtr>
                <Nm>something</Nm>
                <PstlAdr>
                    <AdrLine>addr 1</AdrLine>
                </PstlAdr>
            </Dbtr>
            <!-- end -->

            <CdtTrfTxInf>
                <PmtId>
                    <InstrId>16082672122</InstrId>
                    <EndToEndId>16082672122</EndToEndId>
                </PmtId>
                <Amt>
                    <InstdAmt Ccy="RON">2159.41</InstdAmt>
                </Amt>
                <CdtrAgt>
                    <FinInstnId>
                        <BIC>some bic</BIC>
                    </FinInstnId>
                </CdtrAgt>
            </CdtTrfTxInf>
        </PmtInf>
    </CstmrCdtTrfInitn>
</Document>
  • 如您所见,我有多个 (4) 个&lt;PmtInf&gt;&lt;/PmtInf&gt; 部分,它们几乎具有相同的结构。
  • 我想做的是:

    1. 比较来自第一个PmtInf 的&lt;PmtInfId&gt;asd&lt;/PmtInfId&gt; 和来自第二个PmtInf 的&lt;PmtInfId&gt;asd&lt;/PmtInfId&gt;。如果有完美匹配(如在相同的标签和文本中),移动到下一个标签元素并比较它们(&lt;PmtMtd&gt;TRF&lt;/PmtMtd&gt; 来自第一个 PmtInf 与 &lt;PmtMtd&gt;TRF&lt;/PmtMtd&gt; 来自第二个 PmtInf... 如果有在到达&lt;CdtTrfTxInf&gt; 标签之前,总是完美匹配。
    2. 当我们到达&lt;CdtTrfTxInf&gt;时,这意味着第一个PmtInf的第一部分与第二个PmtInf的第一部分相同。此时,将&lt;CdtTrfTxInf&gt;&lt;/CdtTrfTxInf&gt; 从第二个PmtInf 移到第一个PmtInf 的&lt;CdtTrfTxInf&gt;&lt;/CdtTrfTxInf&gt; 部分之后。然后,删除第二个 PmtInf 部分。

所以,此时,xml 应该是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03">
    <CstmrCdtTrfInitn>
        <GrpHdr>
            <MsgId>123</MsgId>
            <CreDtTm>321</CreDtTm>
            <NbOfTxs>10</NbOfTxs>
            <CtrlSum>18700.68</CtrlSum>
            <InitgPty>
                <Nm>some info</Nm>
            </InitgPty>
        </GrpHdr>

        <PmtInf>
            <!-- start -->
            <PmtInfId>asd</PmtInfId>
            <PmtMtd>TRF</PmtMtd>
            <BtchBookg>false</BtchBookg>
            <PmtTpInf>
                <InstrPrty>NORM</InstrPrty>
                <SvcLvl>
                    <Prtry>test</Prtry>
                </SvcLvl>
            </PmtTpInf>
            <ReqdExctnDt>date</ReqdExctnDt>
            <Dbtr>
                <Nm>something</Nm>
                <PstlAdr>
                    <AdrLine>addr 1</AdrLine>
                </PstlAdr>
            </Dbtr>
            <!-- end -->

            <CdtTrfTxInf>
                <PmtId>
                    <InstrId>16082672122</InstrId>
                    <EndToEndId>16082672122</EndToEndId>
                </PmtId>
                <Amt>
                    <InstdAmt Ccy="RON">2159.41</InstdAmt>
                </Amt>
                <CdtrAgt>
                    <FinInstnId>
                        <BIC>some bic</BIC>
                    </FinInstnId>
                </CdtrAgt>
            </CdtTrfTxInf>

            <CdtTrfTxInf>
                <PmtId>
                    <InstrId>16082672122</InstrId>
                    <EndToEndId>16082672122</EndToEndId>
                </PmtId>
                <Amt>
                    <InstdAmt Ccy="RON">2159.41</InstdAmt>
                </Amt>
                <CdtrAgt>
                    <FinInstnId>
                        <BIC>some bic</BIC>
                    </FinInstnId>
                </CdtrAgt>
            </CdtTrfTxInf>


        </PmtInf>

        <PmtInf>
            <!-- start -->
            <PmtInfId>qwe</PmtInfId>
            <PmtMtd>TRF</PmtMtd>
            <BtchBookg>false</BtchBookg>
            <PmtTpInf>
                <InstrPrty>HIGH</InstrPrty>
                <SvcLvl>
                    <Prtry>test</Prtry>
                </SvcLvl>
            </PmtTpInf>
            <ReqdExctnDt>date</ReqdExctnDt>
            <Dbtr>
                <Nm>something</Nm>
                <PstlAdr>
                    <AdrLine>addr 1</AdrLine>
                </PstlAdr>
            </Dbtr>
            <!-- end -->

            <CdtTrfTxInf>
                <PmtId>
                    <InstrId>16082672122</InstrId>
                    <EndToEndId>16082672122</EndToEndId>
                </PmtId>
                <Amt>
                    <InstdAmt Ccy="RON">2159.41</InstdAmt>
                </Amt>
                <CdtrAgt>
                    <FinInstnId>
                        <BIC>some bic</BIC>
                    </FinInstnId>
                </CdtrAgt>
            </CdtTrfTxInf>
        </PmtInf>

        <PmtInf>
            <!-- start -->
            <PmtInfId>asd</PmtInfId>
            <PmtMtd>TRF</PmtMtd>
            <BtchBookg>false</BtchBookg>
            <PmtTpInf>
                <InstrPrty>NORM</InstrPrty>
                <SvcLvl>
                    <Prtry>test</Prtry>
                </SvcLvl>
            </PmtTpInf>
            <ReqdExctnDt>date</ReqdExctnDt>
            <Dbtr>
                <Nm>something</Nm>
                <PstlAdr>
                    <AdrLine>addr 1</AdrLine>
                </PstlAdr>
            </Dbtr>
            <!-- end -->

            <CdtTrfTxInf>
                <PmtId>
                    <InstrId>16082672122</InstrId>
                    <EndToEndId>16082672122</EndToEndId>
                </PmtId>
                <Amt>
                    <InstdAmt Ccy="RON">2159.41</InstdAmt>
                </Amt>
                <CdtrAgt>
                    <FinInstnId>
                        <BIC>some bic</BIC>
                    </FinInstnId>
                </CdtrAgt>
            </CdtTrfTxInf>
        </PmtInf>
    </CstmrCdtTrfInitn>
</Document>
  1. 现在对第一个PmtInf 部分和第三个部分重复该过程,然后对第四个部分重复该过程。如果有几乎完美的匹配,我们应该只有一个PmtInf标签,里面有4个CdtTrfTxInf标签。
  2. 如果在某些时候不匹配(例如,将第一个 PmtInf 中的 &lt;InstrPrty&gt;NORM&lt;/InstrPrty&gt; 与第三个 PmtInf 中的 &lt;InstrPrty&gt;HIGH&lt;/InstrPrty&gt; 进行比较时,请保留该 PmtInf 部分并转到下一个。
  3. 在我们将第一个PmtInfs 与它上面的所有PmtInfs 进行比较后,将第二个PmtInf 与第三个比较并应用相同的规则,然后将第三个与第四个...以此类推。

现在我可能要求太多了,但是这可以用 XSLT 完成吗?我知道我没有尝试任何事情,但我只是花了太多时间尝试通过简单的 Python 字符串操作来实现这一点,而且看起来 XSLT 转换文档需要一些时间来适应语法。


我这样调用脚本:

def parse_xml(file, output_path):
    parser = ET.XMLParser(encoding='utf-8', recover=True)
    dom = ET.parse(file, parser=parser)
    xslt = ET.fromstring(TEMPLATE_XSLT)  # TEMPLATE_XSLT contains the transformation

    transform = ET.XSLT(xslt)
    new_dom = transform(dom)

    with open(output_path, 'wb') as xml_file:
        xml_file.write(new_dom)

【问题讨论】:

  • 你可以使用 XSLT 2.0 吗?如果是,请查看:w3.org/TR/xpath-functions/#func-deep-equal
  • 显然,来自 Python 的 lxml 模块只允许 v1.0,所以我想我必须坚持下去。你有时间帮我解决这个问题吗?它看起来像是一种很棒的转换语言,我真的没有完全理解。欢迎任何解释!
  • 恐怕这是一项艰巨的任务。查看 stackoverflow.com/questions/33610324/… 了解深度相等的 XSLT 1.0 实现。
  • 是的,我看到了……但我想我需要一些指导。至少,某种伪代码可以让我开始。
  • 为什么说要比较的部分有几乎相同的结构?在给定的示例中,它们都具有完全相同的结构 - 如果该结构是恒定的,您可以通过显式列出要比较的节点来进行比较。这会很冗长,但很容易。

标签: xml python-3.x xslt


【解决方案1】:

我犹豫是否要发布这个,因为您似乎是 XSLT 的初学者,这是一个复杂问题的复杂解决方案。您可能需要很长时间才能理解这一点。

这是如何工作的:

在第一遍中,我们为输入 XML 中的每个 PmtInf 元素生成一个 pmt 元素,并使用包含每个节点(元素或属性)后代的名称/值对的字符串填充它当前PmtInf。在给定的示例中,每个这样的 pmt 元素看起来类似于:

<pmt id="idp1696">[PmtInfId:asd][PmtMtd:TRF][BtchBookg:false][PmtTpInf:NORMtest][InstrPrty:NORM][SvcLvl:test][Prtry:test][ReqdExctnDt:date][Dbtr:somethingaddr 1][Nm:something][PstlAdr:addr 1][AdrLine:addr 1]</pmt>

在下一步中,我们将Muenchian grouping 应用于第一遍生成的pmt 节点。对于每个不同的 pmt 节点,我们创建一个 PmtInf 元素并填充它:

  1. 对应PmtInf元素的内容,CdtTrfTxInf除外;

  2. 来自该组所有成员的所有CdtTrfTxInf 元素的副本。

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03"
xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="ns0"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="pmt" match="ns0:pmt" use="." />
<xsl:key name="PmtInf" match="ns0:PmtInf" use="generate-id()" />

<xsl:variable name="input" select="/" />

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="ns0:CstmrCdtTrfInitn">
    <!-- first pass -->
    <xsl:variable name="first-pass-rtf">
        <xsl:apply-templates select="ns0:PmtInf" mode="gen-key"/>
    </xsl:variable>
    <xsl:variable name="first-pass" select="exsl:node-set($first-pass-rtf)" />
    <!-- output -->
    <xsl:copy>
        <xsl:copy-of select="ns0:GrpHdr"/>
            <!-- for each distinct pmt -->
            <xsl:for-each select="$first-pass/ns0:pmt[count(. | key('pmt', .)[1]) = 1]">
                <xsl:variable name="id" select="@id" />
                <xsl:variable name="ids" select="key('pmt', .)/@id" />
                <PmtInf>
                    <!-- switch context back to XML input -->
                    <xsl:for-each select="$input">
                        <xsl:copy-of select="key('PmtInf', $id)/*[not(self::ns0:CdtTrfTxInf)]"/>
                        <xsl:copy-of select="key('PmtInf', $ids)/ns0:CdtTrfTxInf"/>
                    </xsl:for-each>
                </PmtInf>
            </xsl:for-each>
    </xsl:copy>
</xsl:template>

<xsl:template match="ns0:PmtInf" mode="gen-key">
    <pmt id="{generate-id()}">
        <xsl:apply-templates select="@*|*" mode="gen-key"/>
    </pmt>
</xsl:template>

<xsl:template match="@*|node()" mode="gen-key">
    <xsl:text>[</xsl:text>
    <xsl:value-of select="name()"/>
    <xsl:text>:</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>]</xsl:text>
    <xsl:apply-templates select="@*|*" mode="gen-key"/>
</xsl:template>

<xsl:template match="ns0:CdtTrfTxInf" mode="gen-key"/>

</xsl:stylesheet>

【讨论】:

  • 哇,太棒了。有用!但这看起来像是一个我几乎不理解的非常先进的解决方案。当您看到 xsl 可以做多少事情时,真是太棒了。非常感谢:)
  • 除了这个很棒的解决方案之外,我还有一个简单的要求。我刚刚意识到&lt;PmtInfId&gt;asd&lt;/PmtInfId&gt; 对于每个PmtInf 总是不同的,所以当我们进行比较时我想跳过它。这可能吗 ?这需要一个新问题?
  • 我现在没有时间对此进行测试,但我相信如果您将&lt;xsl:template match="ns0:CdtTrfTxInf" mode="gen-key"/&gt; 更改为&lt;xsl:template match="ns0:PmtInfId | ns0:CdtTrfTxInf" mode="gen-key"/&gt;,它将从为评估目的计算的密钥中排除PmtInfId - 相同因为CdtTrfTxInf 现在被排除在外了。
  • 谢谢,它成功了。我面临的另一个问题是,只要标签为空(不包含任何信息(例如:&lt;tag&gt;&lt;/tag&gt;),此转换就会删除&lt;tag&gt;,只留下&lt;tag\&gt; 之类的东西。我宁愿不这样做发生。有没有办法做到这一点?
  • 这不是你可以控制的(至少不容易)。如果您将输出方法更改为html,您将不会获得自闭合标签 - 但您也可能会获得您没有要求的东西(例如 DOCTYPE 声明)。我建议你习惯&lt;tag/&gt;,不要担心。
猜你喜欢
  • 1970-01-01
  • 2021-03-21
  • 1970-01-01
  • 2014-12-27
  • 1970-01-01
  • 2011-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多