XML to CSV with XSLT - Grouping nodes

前端 未结 1 1763
生来不讨喜
生来不讨喜 2021-01-26 00:34

So I\'ve got an XML file I\'ve generated from a php curl response that is then transformed to CSV such that each mods element below is one line. I\'ve got some CSV using the sty

相关标签:
1条回答
  • 2021-01-26 00:41

    One way to do this is firstly change your XSLT to only select the elements which do not have a preceding-sibling with the same child name (i.e select elements that are the 'first' in each group)

    <xsl:for-each select="*[name(*) != name(preceding-sibling::*[1]/*)]">
    

    Then, you can define a variable to get the following sibling if (and only if) it has the same name, so you can then check if the current element is indeed in a group of more than 1.

    <xsl:variable name="nextWithSameName" 
                  select="following-sibling::*[1][name(*)=name(current()/*)]"/>
    <xsl:if test="$nextWithSameName">**</xsl:if>
    

    (I am not sure if you actually wanted the ** in the final results, or whether they are just there to highlight the group! I am keeping them in my example, but obviously it will be easy enough to remove the relevant lines of code).

    To group together the following-siblings with the same name, you could call a recursive template for the first following-sibling

    <xsl:apply-templates select="$nextWithSameName" mode="group"/>
    

    Then, within this template you would recursively call it where the immediate following sibling has the same name

    <xsl:template match="*" mode="group">
       <xsl:text>;</xsl:text>
       <xsl:value-of select="normalize-space(.)"/>
       <xsl:apply-templates select="following-sibling::*[1][name(*)=name(current()/*)]" />
    </xsl:template>
    

    Try the following XSLT

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
       <xsl:output method="text" encoding="iso-8859-1"/>
       <xsl:strip-space elements="*"/>
    
       <xsl:template match="/*/*">
          <xsl:for-each select="*[name(*) != name(preceding-sibling::*[1]/*)]">
             <xsl:variable name="nextWithSameName" select="following-sibling::*[1][name(*)=name(current()/*)]"/>
             <xsl:if test="position() &gt; 1">,    </xsl:if>
             <xsl:if test="$nextWithSameName">**</xsl:if>
             <xsl:value-of select="normalize-space(.)"/>
             <xsl:apply-templates select="$nextWithSameName" mode="group"/>
             <xsl:if test="$nextWithSameName">**</xsl:if>
          </xsl:for-each>
          <xsl:text>&#xD;</xsl:text>
       </xsl:template>
    
       <xsl:template match="*" mode="group">
          <xsl:text>;</xsl:text>
          <xsl:value-of select="normalize-space(.)"/>
          <xsl:apply-templates select="following-sibling::*[1][name(*)=name(current()/*)]" />
       </xsl:template>
    </xsl:stylesheet>
    

    Now, if you could use XSLT 2.0, things become much, much easier, as you could use the xsl:for-each-group construct which, among other things, comes with an operation to 'group-adjacent'. And you could also do away with the recursive template by using the improved xsl:value-of which would have a 'separator' property to use when multiple elements are select.

    For XSLT 2.0, the following should also work

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text" encoding="iso-8859-1"/>
        <xsl:strip-space elements="*"/>
    
        <xsl:template match="/*/*">
            <xsl:for-each-group select="*" group-adjacent="name(*)">
                <xsl:if test="position() &gt; 1">,    </xsl:if>
                <xsl:if test="current-group()[2]">**</xsl:if>
                <xsl:value-of select="current-group()" separator=";" />
                <xsl:if test="current-group()[2]">**</xsl:if>
            </xsl:for-each-group >
            <xsl:text>&#xD;</xsl:text>
        </xsl:template>
    </xsl:stylesheet>
    
    0 讨论(0)
提交回复
热议问题