user1451239
user1451239

Reputation: 13

Using XSLT create separate parent for each node if specific attribute is present

I want to wrap <text> in separate <p> having @align attribute. For more clarification please see the below example.

Source

<p>
 <text>Check</text>
 <text>Check2</text>
 <text align="center">Final</text>
 <text align="right">Final Check</text>
</p>

XSLT Code:

<xsl:template match="p">
    <xsl:choose>
        <xsl:when test="child::text/@align">
            <xsl:element name="p">
                <xsl:attribute name="align">
                    <xsl:value-of select="child::text/@align"/>
                </xsl:attribute>
                <xsl:apply-templates/>
            </xsl:element>
        </xsl:when>
    </xsl:choose>
</xsl:template>

My Output

<p align="center right">
  <text>Check</text>
  <text>Check2</text>
  <text>Final</text>
  <text>Final Check</text>
</p>

Expected Output

<p>
  <text>Check</text>
  <text>Check2</text>
</p>
<p align="center">
  <text>Final</text>
</p>
<p align="right">
  <text>Final Check</text>
</p>

Upvotes: 0

Views: 149

Answers (3)

michael.hor257k
michael.hor257k

Reputation: 116972

I would suggest you do it this way:

XSLT 2.0

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

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

<xsl:template match="p">
    <xsl:for-each-group select="text" group-adjacent="string(@align)">
         <p>
            <xsl:if test="current-grouping-key()">
                <xsl:attribute name="align" select="current-grouping-key()" />
            </xsl:if>
            <xsl:apply-templates select="current-group()" />
        </p>
    </xsl:for-each-group>
</xsl:template>

<xsl:template match="text/@align"/>

</xsl:stylesheet>

To understand the difference between this and the other suggestions, use this as the test input:

<p>
    <text>Check</text>
    <text>Check2</text>
    <text align="center">Check3</text>
    <text align="center">Check4</text>
    <text align="right">Check5</text>
    <text>Check6</text>
</p>

Upvotes: 0

Tim C
Tim C

Reputation: 70598

As you are using XSLT 2.0, you can make use of xsl:for-each-group to group together text elements with an align attribute, and those without, and take the appropriate action in each case.

Try this XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output method="xml" indent="yes" />
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="p">
    <xsl:for-each-group select="text" group-adjacent="boolean(@align)">
      <xsl:choose>
        <xsl:when test="current-grouping-key()">
          <xsl:for-each select="current-group()">
             <p align="{./@align}">
                <xsl:apply-templates select="." />
             </p>
          </xsl:for-each>
        </xsl:when>
        <xsl:otherwise>
          <p>
            <xsl:apply-templates select="current-group()" />
          </p>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:template>

  <xsl:template match="text/@align" />
</xsl:stylesheet>

Upvotes: 1

helcim
helcim

Reputation: 819

Here is a simple solution that does exactly that.

  <xsl:template match="p">
    <xsl:apply-templates/>
  </xsl:template>
  <xsl:template match="text[@align='center']">
    <xsl:element name="p">
      <xsl:attribute name="align">
        <xsl:value-of select="'center'"/>
      </xsl:attribute>
      <xsl:element name="text">
        <xsl:value-of select="."/>
      </xsl:element>
    </xsl:element>
  </xsl:template>
  <xsl:template match="text[@align='right']">
    <xsl:element name="p">
      <xsl:attribute name="align">
        <xsl:value-of select="'right'"/>
      </xsl:attribute>
      <xsl:element name="text">
        <xsl:value-of select="."/>
      </xsl:element>
    </xsl:element>
  </xsl:template>  
  <xsl:template match="text">
    <xsl:element name="p">
      <xsl:element name="text">
        <xsl:value-of select="."/>
      </xsl:element>
    </xsl:element>
  </xsl:template>

Upvotes: 0

Related Questions