XSLT-XPath:For-each路径规范,用于重新分组元素

时间:2013-01-09 11:56:41

标签: xslt xpath foreach

我有XML文档,我想将其转移到另一个结构中。文档示例如下:

<application>
    <contactPerson>
        <name>Dominik</name>
        <countryCode>DE</countryCode>
    </contactPerson>
    <contactPerson>
        <name>Dorothea</name>
        <countryCode>DE</countryCode>
    </contactPerson>
    <contactPerson>
        <name>Fiona</name>
        <countryCode>FR</countryCode>
    </contactPerson>
    <contactPerson>
        <name>Fabian</name>
        <countryCode>FR</countryCode>
    </contactPerson>
    <contactPerson>
        <name>Florian</name>
        <countryCode>FR</countryCode>
    </contactPerson>
    <contactPerson>
        <name>Gabi</name>
        <countryCode>GB</countryCode>
    </contactPerson>
    <contactPerson>
        <name>Gert</name>
        <countryCode>GB</countryCode>
    </contactPerson>
</application>

现在我要做的是按国家/地区对元素进行分组,这意味着结果应如下所示:

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

我正在使用XPath for-each模式来选择所有国家/地区,但它没有按预期执行。我的模式如下:

  <xsl:template match="/">
    <application>
      <xsl:for-each select="/application/contactPerson/countryCode[not(.=preceding-sibling::*/application/contactPerson/countryCode)]">
        <memberState>
          <countryCode>
            <xsl:value-of select="."/>
          </countryCode>
          <contactPerson>
            <name>
              <xsl:value-of select="../name"/>
            </name>
          </contactPerson>
        </memberState>
      </xsl:for-each>
    </application>
  </xsl:template>

错误可能在XPath表达式中的哪个地方无法编译。我将其更改为以下

<xsl:for-each select="/application/contactPerson/countryCode[not(.=preceding-sibling::*)]">

因为我认为我已经在树的正确位置了。这个解决方案可以编译,但它不会像我预期的那样使用“previous-sibling”来提供一个唯一的国家列表,而是使用完整的列表。

除了我需要解决我的问题之外,我还要特别感谢一些帮助,以了解这里实际发生的事情。

  1. 是否可以像我的第二个解决方案一样给出相对路径,或者每次都必须给出完整的路径?
  2. 我是否正在制作国家代码的唯一列表,或者通常会以不同的方式实施?
  3. 我正在寻找的解决方案是否可以通过XSLT实现?
  4. 非常感谢你的帮助。

3 个答案:

答案 0 :(得分:2)

您可以使用key标记通过countryCode访问每个元素,如下所示:

<xsl:key name="groups" match="/application/contactPerson" use="countryCode" />

然后您可以迭代该键的内容。见Walk/loop through an XSL key: how?

因此,如果您坚持使用XSLT 1.0,那将是一个很好的开始。如果您可以使用XSLT 2.0,那么您应该查看group-by

答案 1 :(得分:2)

以下是使用Muenchian grouping的XSLT 1.0解决方案:

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

<xsl:output indent="yes"/>

<xsl:key name="country" match="contactPerson" use="countryCode"/>

<xsl:template match="application">
  <xsl:copy>
    <xsl:apply-templates select="contactPerson[generate-id() = generate-id(key('country', countryCode)[1])]" mode="group"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="contactPerson" mode="group">
  <memberState>
    <xsl:copy-of select="countryCode"/>
    <xsl:apply-templates select="key('country', countryCode)"/>
  </memberState>
</xsl:template>

<xsl:template match="contactPerson">
  <xsl:copy>
    <xsl:copy-of select="*[not(self::countryCode)]"/>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

答案 2 :(得分:1)

你的XPath表达式几乎是正确的:

<xsl:for-each 
    select="/application/contactPerson/countryCode[not(.=preceding-sibling::*)]"> 

它不起作用的原因是countryCode没有任何兄弟姐妹。要查找以前的countryCodes,您需要向上移动一个级别然后向下移动:

<xsl:for-each 
    select="/application/contactPerson/countryCode[not(. = ../preceding-sibling::*/countryCode)]"> 

我认为应该成功地遍历不同的countryCodes。

对模板的这种修改应该有效。你需要另一个循环遍历每个国家的所有人:

<xsl:template match="/">
    <application>
      <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>
    </application>
  </xsl:template>