Zygmuntix
Zygmuntix

Reputation: 359

XSLT transformation - set variable during iteration through set of nodes

My task is to create XSLT transformation. In procedural language solution would look like this:

foreach( Book book in set)
{
   int importanceIndicator = 0;
   foreach( Page page in book )
   {
      if(page.name == "important_page")
      {
         importanceIndicator = 1;
         break;
      }
   }
   book.newField.importanceIndicator = importanceIndicator;
}

How can I solve this problem in XSLT (which is very different from procedural languages)?

Sample XML files:

Input:

<set>
    <book>
        <page name="important_page"></page>
        <page name="first_page"></page>
        <page name="important_page"></page>
        <page name="second_page"></page>
        <page name="important_page"></page>
    </book>
    <book>
        <page name="another_page"></page>
        <page name="first_page"></page>
        <page name="3_page"></page>
        <page name="second_page"></page>
        <page name="another_page"></page>
    </book>
    <book>
        <page name="important_page"></page>
        <page name="first_page"></page>
        <page name="important_page"></page>
        <page name="second_page"></page>
        <page name="another_page"></page>
    </book>
</set>

Output:

<set>
    <book>
        <newField importance_indicator="1"></newField>
    </book>
    <book>
        <newField importance_indicator="0"></newField>
    </book>
    <book>
        <newField importance_indicator="1"></newField>
    </book>
</set>

Upvotes: 0

Views: 253

Answers (3)

Zygmuntix
Zygmuntix

Reputation: 359

I found the answer, it can be done like this:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="yes" />
  <xsl:template match="/">

    <set>
        <xsl:for-each select="set">
          <xsl:for-each select="book">
            <book>
            <xsl:variable name="importanceIndicatorTmp">
              <xsl:choose>
                <xsl:when test= "page/@name = 'important_page'">1</xsl:when>
                <xsl:otherwise>0</xsl:otherwise>
              </xsl:choose>
            </xsl:variable>
            <newField importanceIndicator="{$importanceIndicatorTmp}"></newField>
            </book>
          </xsl:for-each>
        </xsl:for-each>
    </set>

  </xsl:template>
</xsl:stylesheet>

Upvotes: 0

Tim C
Tim C

Reputation: 70618

First start off with the identity template (which in this case will just copy the set node)

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

Then, as you are transforming book elements, you need a new template that matches book, where you can copy the book element and add a newField element

<xsl:template match="book">
    <xsl:copy>
        <newField>
           <!-- Add code for attribute -->
        </newField>
    </xsl:copy>
</xsl:template>

And to create the attribute, you can use xsl:attribute with an xsl:choose containing your condition to check

    <xsl:attribute name="importance_indicator">
        <xsl:choose>
            <xsl:when test="page[@name='important_page']">1</xsl:when>
            <xsl:otherwise>0</xsl:otherwise>
        </xsl:choose>
    </xsl:attribute>

Try this XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.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="book">
        <xsl:copy>
            <newField>
                <xsl:attribute name="importance_indicator">
                    <xsl:choose>
                        <xsl:when test="page[@name='important_page']">1</xsl:when>
                        <xsl:otherwise>0</xsl:otherwise>
                    </xsl:choose>
                </xsl:attribute>
            </newField>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Note that the template matching "book" can be simplified, by using Attribute Value Templates (indicated by the { } symbols which cause the expression inside to be evaluated, rather than output literally)

<xsl:template match="book">
    <xsl:copy>
        <newField importance_indicator="{number(boolean(page[@name='important_page']))}"/>
    </xsl:copy>
</xsl:template>

So page[@name='important_page']) will select the page if it is an "important_page". boolean() will return true or false depending if that page exists, and number will then convert "true" to "1" or "false" to "0".

Upvotes: 1

Hobbes
Hobbes

Reputation: 2125

In XSLT, you don't need a loop to accomplish this. When you process the book node, you can examine the contents of its children.

<xsl:template match="book">
    <newfield>
         <xsl:choose>
            <xsl:when test="page/@name='important page'">
                <xsl:attribute name="importance_indicator" select="1">
            </xsl:when>
             <xsl:otherwise>
                <xsl:attribute name="importance_indicator" select="0">
            </xsl:otherwise>

Upvotes: 2

Related Questions