XSLT分组同级节点

6
我正在尝试在一个 XML 文件中对兄弟数据进行分组。
给定:
<?xml version="1.0" encoding="UTF-8"?>
<data>
    <competition>
        <timeline>10:00</timeline>
        <fixture>team a v team b</fixture>
        <fixture>team c v team d</fixture>
        <timeline>12:00</timeline>
        <fixture>team e v team f</fixture>
        <timeline>16:00</timeline>
        <fixture>team g v team h</fixture>
        <fixture>team i v team j</fixture>
        <fixture>team k v team l</fixture>
    </competition>
</data>

我正在尝试制作:
<?xml version="1.0" encoding="UTF-8"?>
<data>
    <competition>
        <timeline time="10:00">
            <fixture>team a v team b</fixture>
            <fixture>team c v team d</fixture>
        </timeline>
        <timeline time="12:00">
            <fixture>team e v team f</fixture>
        </timeline>
        <timeline time="16:00">
            <fixture>team g v team h</fixture>
            <fixture>team i v team j</fixture>
            <fixture>team k v team l</fixture>
        </timeline>
    </competition>
</data>

我正在使用以下XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:template match="competition" >

        <xsl:apply-templates select="timeline" />

    </xsl:template>

    <xsl:template match="timeline">
        <timeline>
            <xsl:attribute name="time" >
                <xsl:value-of select="." />
            </xsl:attribute>

            <xsl:apply-templates select="following-sibling::*" mode="copy"/>

        </timeline>
    </xsl:template>

    <xsl:template match="fixture" mode="copy">
        <fixture>
            <xsl:value-of select="." />
        </fixture>
    </xsl:template>

    <xsl:template match="timeline" mode="copy">
        <xsl:apply-templates select="following-sibling::*" mode="null" />
    </xsl:template>

    <xsl:template match="*" mode="null">
    </xsl:template>
</xsl:stylesheet>

我的问题是,当它到达下一个时间轴时,它没有停止处理装置节点。

不仅如此,如果相同的时间轴不是连续的,您的XSLT也无法对它们进行分组。 - Rashmi Pandit
请查看我的解决方案...即使您的XML时间表不是连续的,它也可以正常工作。 - Rashmi Pandit
@Rashmi:你从哪里得出将相同时间轴分组在一起的要求?我没有看到任何建议表明时间轴值不是唯一的。 - AnthonyWJones
过去处理XML的经验...虽然这可能与此处情况有所不同。 - Rashmi Pandit
6个回答

10

在以下情况下(我假设这是正确的)很容易实现:

  • 所有在<competition>中的<timeline>都是唯一的
  • 只有在给定<timeline>之后的<fixture>属于它
  • 不存在没有<timeline>元素的<fixture>

这个XSLT 1.0解决方案:

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

  <xsl:key name="kFixture" 
           match="fixture" 
           use="generate-id(preceding-sibling::timeline[1])" 
  />

  <xsl:template match="data">
    <xsl:copy>
      <xsl:apply-templates select="competition" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="competition">
    <xsl:copy>
      <xsl:apply-templates select="timeline" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="timeline">
    <xsl:copy>
      <xsl:attribute name="time">
        <xsl:value-of select="." />
      </xsl:attribute>
      <xsl:copy-of select="key('kFixture', generate-id())" />
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

输出结果为:

<data>
  <competition>
    <timeline time="10:00">
      <fixture>team a v team b</fixture>
      <fixture>team c v team d</fixture>
    </timeline>
    <timeline time="12:00">
      <fixture>team e v team f</fixture>
    </timeline>
    <timeline time="16:00">
      <fixture>team g v team h</fixture>
      <fixture>team i v team j</fixture>
      <fixture>team k v team l</fixture>
    </timeline>
    </competition>
</data>
请注意使用 <xsl:key> 来匹配所有属于(“在之前”)给定 <timeline><fixture>。另外一个略微更短但不太明显的解决方案是修改后的标识转换。
<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>

  <xsl:key name="kFixture" 
           match="fixture" 
           use="generate-id(preceding-sibling::timeline[1])" 
  />

  <xsl:template match="* | @*">
    <xsl:copy>
      <xsl:apply-templates select="*[not(self::fixture)] | @*" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="timeline">
    <xsl:copy>
      <xsl:attribute name="time">
        <xsl:value-of select="." />
      </xsl:attribute>
      <xsl:copy-of select="key('kFixture', generate-id())" />
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

非常棒的逻辑,先生。我使用了您的代码,并加上了一。 - Rudramuni TP

3

这是我的尝试。我做出的一个假设简化了事情,即具有特定文本值的时间轴元素已经是唯一的。

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="UTF-8" />

  <xsl:template match="/data">
    <data>
      <xsl:apply-templates select="competition" />
    </data>
  </xsl:template>

  <xsl:template match="competition">
    <xsl:for-each select="timeline">
      <timeline time="{text()}">
        <xsl:copy-of
          select="./following-sibling::fixture[count(preceding-sibling::timeline[1] | current()) = 1]" />
      </timeline>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

上面的代码已经根据Tomalak的建议修改为使用current()而不是变量。

这是一种方法,虽然不是特别高效。还是+1 :) 你能修复水平滚动吗? - Tomalak
虽然不够高效,但非常简单易懂,不过我更喜欢你的解决方案。稍微调整了一下内容以减少缩进,但仍然需要滚动。 - AnthonyWJones
在XML中,允许在属性中放置任意多的换行符和空格。这使得您的选择表达式中的XPath格式更加清晰和整洁,只需稍微分隔一下即可。 - Tomalak
XPath表达式可以简化为“following-sibling::fixture[count(preceding-sibling::timeline[1] | current()) = 1]”,这样就可以省略变量了。 :) - Tomalak
不错的发现,我忘记了current()。 - AnthonyWJones

1

G Andrieu的解决方案不起作用,因为很遗憾没有'next-sibling'这样的轴。

另一种解决方案如下:

<xsl:template match="timeline">
<timeline>
  <xsl:attribute name="time" >
    <xsl:value-of select="." />
  </xsl:attribute>

  <xsl:apply-templates select="following-sibling::*[local-name()='fixture' and position()=1]" />

</timeline>
</xsl:template>

<xsl:template match="fixture">
  <fixture>
      <xsl:value-of select="." />
  </fixture>
  <xsl:apply-templates select="following-sibling::*[local-name()='fixture' and position()=1]" />
</xsl:template>

1
以下 XSLT 可以处理即使相同的时间轴分散在多个地方也能正常工作。例如,在下面的 XML 中,有两个时间轴为 10:00 的条目:
<?xml version="1.0" encoding="UTF-8"?>
<data>
    <competition>
        <timeline>10:00</timeline>
        <fixture>team a v team b</fixture>
        <fixture>team c v team d</fixture>
        <timeline>12:00</timeline>
        <fixture>team e v team f</fixture>
        <timeline>16:00</timeline>
        <fixture>team g v team h</fixture>
        <fixture>team i v team j</fixture>
        <fixture>team k v team l</fixture>
        <timeline>10:00</timeline>
        <fixture>team a v team b new</fixture>
        <fixture>team c v team d new</fixture>
    </competition>
</data>

Xslt

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:key name="TimelineDistint" match="timeline" use="."/>

    <xsl:template match="data">
        <xsl:apply-templates select="competition"/>
    </xsl:template>

    <xsl:template match="competition">
        <data>
            <competition>
                <xsl:for-each select="timeline[generate-id() = generate-id(key('TimelineDistint', .)[1])]">
                    <timeline>
                        <xsl:variable name="varTimeline" select="."/>
                        <xsl:attribute name="time"><xsl:value-of select="normalize-space(.)"/></xsl:attribute>
                        <xsl:for-each select="../fixture[preceding::timeline[1] = $varTimeline]">
                            <fixture>
                                <xsl:value-of select="normalize-space(.)"/>
                            </fixture>
                        </xsl:for-each>
                    </timeline>
                </xsl:for-each>
            </competition>
        </data>
    </xsl:template>
</xsl:stylesheet>

输出:

<?xml version="1.0" encoding="UTF-8"?>
<data>
    <competition>
        <timeline time="10:00">
            <fixture>team a v team b</fixture>
            <fixture>team c v team d</fixture>
            <fixture>team a v team b new</fixture>
            <fixture>team c v team d new</fixture>
        </timeline>
        <timeline time="12:00">
            <fixture>team e v team f</fixture>
        </timeline>
        <timeline time="16:00">
            <fixture>team g v team h</fixture>
            <fixture>team i v team j</fixture>
            <fixture>team k v team l</fixture>
        </timeline>
    </competition>
</data>

这仅在每个文档中恰好有一个<competition>的情况下才有效,这很可能是错误的假设。没有必要在所有地方使用"//"简写,如果完全删除它们,解决方案甚至会更好。 - Tomalak
谢谢您的建议Tomalak,我已经编辑了XSLT以支持多个竞赛元素。 - Rashmi Pandit

0
尝试类似这样的代码:
<xsl:template match="timeline">
    <timeline>
            <xsl:attribute name="time" >
                    <xsl:value-of select="." />
            </xsl:attribute>

            <xsl:apply-templates select="following-sibling::*[name()=fixture][1]" />

    </timeline>
</xsl:template>

<xsl:template match="fixture">
    <fixture>
            <xsl:value-of select="." />
    </fixture>
    <xsl:apply-templates select="following-sibling::*[name()=fixture][1]" />
</xsl:template>

它不理解next-sibling。我使用了following-sibling :: *[1]。 - Xetius
next-sibling 不是我认识的轴? - AnthonyWJones
当然不是。我从未怀疑它可能不起作用。这就是为什么我说“类似于”。我提供这个答案作为提示,因为没有人回答过,而没有双重检查任何内容。然后我得到了负面反馈,尽管我的答案并不完美,但仍然有帮助。所以我稍微纠正了一下。抱歉,没有更多时间了。 - g andrieu

0

在 g andrieu 的帮助下,我只需要获取下一个项目而不是列表后面的内容:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:template match="competition" >

        <xsl:apply-templates select="timeline" />

    </xsl:template>

    <xsl:template match="timeline">
        <timeline>
            <xsl:attribute name="time" >
                <xsl:value-of select="." />
            </xsl:attribute>

            <xsl:apply-templates select="following-sibling::*[1]" mode="copy"/>

        </timeline>
    </xsl:template>

    <xsl:template match="fixture" mode="copy">
        <fixture>
            <xsl:value-of select="." />
        </fixture>
        <xsl:apply-templates select="following-sibling::*[1]" mode="copy"/>
    </xsl:template>

    <xsl:template match="timeline" mode="copy" />

</xsl:stylesheet>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接