Search code examples
xmlxsltxpath-1.0

How can I concatenate all of these XML tags into one larger tag of the same name with XSLT?



I am transforming this XML:

<root>
  <contrib contrib-type="author">
    <name>
      <last-name>Kottke</last-name>
      <first-name>Leo</first-name>
    </name>
  </contrib>

  <contrib contrib-type="author">
    <name>
      <last-name>McKee</last-name>
      <first-name>Andy</first-name>
    </name>
  </contrib>

  <contrib contrib-type="author">
    <name>
      <last-name>Hedges</last-name>
      <first-name>Michael</first-name>
    </name>
  </contrib>
</root>

...with this XSL:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes" method="xml"/>
    <xsl:strip-space elements="*"/>

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

        <!-- Authors -->
        <xsl:template match="contrib[@contrib-type='author']">
            <Authors>
              <xsl:if test="position() != last()">
                <xsl:value-of select = "concat(name/first-name, ' ', name/last-name, ', ')" />
              </xsl:if>

              <xsl:if test="position() = last()">
                <xsl:value-of select = "concat('and ', name/first-name, ' ', name/last-name, '.')" />
              </xsl:if>
            </Authors>
        </xsl:template>
</xsl:stylesheet>

...and am getting this output:

<root>
    <Authors>Leo Kottke, </Authors>
    <Authors>Andy McKee, </Authors>
    <Authors>and Michael Hedges.</Authors>
</root>





However, I am trying to concatenate all of the  Author  tags into one larger  Author  tag, so that the output would resemble this:

<root>
    <Authors>Leo Kottke, Andy McKee, and Michael Hedges.</Authors>
</root>


Do I need to use a  for-each  loop in some way?


Solution

  • No xsl:for-each needed. Just add a template for /* (or /root) and remove Authors from your contrib match:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output omit-xml-declaration="yes" indent="yes" method="xml"/>
        <xsl:strip-space elements="*"/>
    
        <!-- identity rule -->
        <xsl:template match="node()|@*">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="/root">
            <xsl:copy>
                <xsl:apply-templates select="@*"/>
                <Authors>
                    <xsl:apply-templates select="contrib[@contrib-type='author']"/>
                </Authors>
            </xsl:copy>
        </xsl:template>
    
        <!-- Authors -->
        <xsl:template match="contrib[@contrib-type='author']">
            <xsl:if test="position() != last()">
                <xsl:value-of select = "concat(name/first-name, ' ', name/last-name, ', ')" />
            </xsl:if>
    
            <xsl:if test="position() = last()">
                <xsl:value-of select = "concat('and ', name/first-name, ' ', name/last-name, '.')" />
            </xsl:if>
        </xsl:template>
    </xsl:stylesheet>