使用tokenize和distinct-values进行复杂的XSLT转换

Mat*_*der 2 xml xslt xpath xslt-2.0

我正在进行一个由四个步骤组成的XSL-Transformation.我想出了各个步骤,但我仍然坚持如何将它们组合在一起.这是我想要做的:

源XML文档:

<application>
    <contactPerson>
        <name>Dominik</name>
        <countryCode>DE,SP</countryCode>
    </contactPerson>
    <contactPerson>
        <name>Andrea</name>
        <countryCode>FR</countryCode>
    </contactPerson>
    <contactPerson>
        <name>Alex</name>
        <countryCode>FR,DE</countryCode>
    </contactPerson>
    <contactPerson>
        <name>Andre</name>
        <countryCode>FR</countryCode>
    </contactPerson>
</application>
Run Code Online (Sandbox Code Playgroud)

目标XML文档:

<application>
    <memberState>
        <countryCode>DE</countryCode>
        <contactPerson>
            <name>Dominik</name>
        </contactPerson>
        <contactPerson>
            <name>Dorothea</name>
        </contactPerson>
    </memberState>
    <memberState>
        <countryCode>FR</countryCode>
        <contactPerson>
            <name>Fiona</name>
        </contactPerson>
        <contactPerson>
            <name>Fabian</name>
        </contactPerson>
        <contactPerson>
            <name>Florian<name>
        </contactPerson>
    </memberState>
    <memberState>
        <countryCode>GB</countryCode>
        <contactPerson>
            <name>Gabi</name>
        </contactPerson>
        <contactPerson>
            <name>Gert</name>
        </contactPerson>
    </memberState>
</application>
Run Code Online (Sandbox Code Playgroud)

我确定了该过程的以下步骤:

  1. 使用countryCode -Tags,将值拆分为逗号并将它们放在一个列表中
  2. 删除列表中的双重出现
  3. 为列表中的每个值创建一个新的countryCode -node
  4. 对于每个新的countryCode -node,添加该国家/地区的联系人的所有人员

现在我想出了如何做第1步:

<memberState>
    <countryCodes>
        <xsl:for-each select="/application/contactPerson">
            <xsl:for-each select="tokenize(./countryCode, ',')">
                <countryCode>
                    <xsl:value-of select="."/>
                </countryCode>
            </xsl:for-each>
        </xsl:for-each>
    </countryCodes>
</memberState>
Run Code Online (Sandbox Code Playgroud)

对于第2步,我可以使用distinct-values().

对于步骤3 + 步骤4,我实现了以下解决方案:

<xsl:for-each select="/application/contactPerson/countryCode[not(. = ../preceding-sibling::*/countryCode)]">
    <memberState>
        <countryCode>
            <xsl:value-of select="."/>
        </countryCode>
        <xsl:for-each select="/application/contactPerson[countryCode = current()]">
            <contactPerson>
                <name>
                    <xsl:value-of select="name"/>
                </name>
            </contactPerson>
        </xsl:for-each>
    </memberState>
</xsl:for-each>
Run Code Online (Sandbox Code Playgroud)

但是我如何把所有东西放在一起呢?我的想法是将每个步骤的输出保存在变量中,并在下一步中使用它,但我遇到的问题是XSLT中的变量是只读的.有没有办法以某种方式连接单个解决方案以获得所需的结果?

Mar*_*nen 7

我只想建议一步for-each-group解决方案:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">

<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>

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

<xsl:template match="application">
  <xsl:copy>
    <xsl:for-each-group select="contactPerson" group-by="tokenize(countryCode, ',')">
      <memberState>
        <countryCode><xsl:value-of select="current-grouping-key()"/></countryCode>
        <xsl:apply-templates select="current-group()"/>
      </memberState>
   </xsl:for-each-group>
  </xsl:copy>
</xsl:template>

<xsl:template match="contactPerson/countryCode"/>

</xsl:stylesheet>
Run Code Online (Sandbox Code Playgroud)

当然有几个转换步骤是可能的,但是使用像for-each-groupXSLT 2.0 这样的工具,我首先会看到使用它们而不是使用几个转换步骤.

如果你想使用distinct-values它当然是可能的; 然而,我只将字符串值存储在变量中并对其进行操作,我不明白为什么使用XSLT 2.0你会想要一个临时树.所以这里有一个使用的示例distinct-values和一个变量来存储它们以便在第二步中处理(我使用一个键来提高效率):

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">

<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:variable name="countryCodes" select="distinct-values(application/contactPerson/countryCode/tokenize(., ','))"/>

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

<xsl:key name="country" match="contactPerson" use="tokenize(countryCode, ',')"/>

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

<xsl:template match="application">
  <xsl:copy>
    <xsl:for-each select="$countryCodes">
      <memberState>
        <countryCode><xsl:value-of select="."/></countryCode>
        <xsl:apply-templates select="key('country', ., $main-input)"/>
      </memberState>
   </xsl:for-each>
  </xsl:copy>
</xsl:template>

<xsl:template match="contactPerson/countryCode"/>

</xsl:stylesheet>
Run Code Online (Sandbox Code Playgroud)

不过我认为,在XSLT 2.0支持下,我的第一个建议更容易.