XSLT Grouping Siblings

前端 未结 6 1308
滥情空心
滥情空心 2021-01-05 11:23

I am trying to group sibling data in an XML file.

Given :



    
             


        
相关标签:
6条回答
  • 2021-01-05 11:52

    Here is my attempt. One assumption I have made which simplifies things is that timeline elements with a specific text value are already unique.

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

    The above is edited to use current() instead of a variable as per Tomalak's suggestion.

    0 讨论(0)
  • 2021-01-05 11:53

    With help from g andrieu I had to make it only get the next item and not the list following:

    <?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>
    
    0 讨论(0)
  • 2021-01-05 11:54

    The following xslt will work even if same timelines are scattered in multiple places. For e.g. in the foll xml there are 2 entries for timeline 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>
    

    Output:

    <?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>
    
    0 讨论(0)
  • 2021-01-05 12:01

    This is easy to do when the following is true (which I assume it is):

    • all <timeline>s within a <competition> are unique
    • only the <fixture>s right after a given <timeline> belong to it
    • there is no <fixture> without a <timeline> element before it

    This XSLT 1.0 solution:

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

    produces:

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

    Note the use of an <xsl:key> to match all <fixture>s that belong to ("are preceded by") a given <timeline>.

    A slightly shorter but less obvious solution would be a modified identity transform:

    <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>
    
    0 讨论(0)
  • 2021-01-05 12:01

    Try something like that :

    <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>
    
    0 讨论(0)
  • 2021-01-05 12:04

    G Andrieu's solution doesn't work, as there is no such axes as 'next-sibling' unfortunately.

    And alternative solution would be the following:

    <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>
    
    0 讨论(0)
提交回复
热议问题