Search code examples
xsltxslt-2.0xpath-2.0

Output values in a certain way using XSLT/XPath 2.0


I have an XML like this:

<?xml version="1.0" encoding="UTF-8"?>

<Section>
    <Chapter>
        <Cell colname="1">
            <Value>A</Value>
        </Cell>
        <Cell colname="2">
            <MyValue>AAA</MyValue>
            <MyValue>BBB</MyValue>
        </Cell>
        <Cell colname="3">
            <MyCar>Honda</MyCar>
        </Cell>
    </Chapter>
    <Chapter>
        <Cell colname="1">
            <Value>C</Value>
        </Cell>
        <Cell colname="2">
            <MyValue>CCC</MyValue>
        </Cell>
        <Cell colname="3">
            <MyCar>Toyota</MyCar>
        </Cell>
    </Chapter>
</Section>

I like the have a message (later on convert them tags) output like this:

A AAA Honda A BBB Honda C CCC Toyota

This is my XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">

    <xsl:output method="xml" version="1.0" encoding="iso-8859-1"   indent="yes"/>

    <xsl:template match="/">
        <xsl:apply-templates select="Section//Chapter"/>
    </xsl:template>

    <xsl:template match="Chapter">
        <xsl:for-each select="Cell[@colname='2']//MyValue">
            <xsl:message>
                <xsl:value-of select="Cell[@colname='1']/Value"/>
                <xsl:value-of select="."/>
                <xsl:value-of select="Cell[@colname='3']/MyCar"/>
            </xsl:message>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="text()" />

</xsl:stylesheet>

Unfortunately it doesn't output what I'd like it to do :(.

I realize that for-each will change the context so the remaining value-ofs won't do anything.

What would be a solution for this ?.

TIA,

John


Solution

  • This XSLT 2.0 transformation (the equivalent XSLT 1.0 transformation can be mechanically written from this one).

    Do note: This is a generic solution that works with any number of children and doesn't rely on hardcoded names.

    <xsl:stylesheet version="2.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:xs="http://www.w3.org/2001/XMLSchema">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <xsl:template match="Chapter">
      <xsl:apply-templates select="Cell[1]"/>
     </xsl:template>
    
     <xsl:template match="Cell">
      <xsl:param name="pText" as="xs:string*"/>
    
      <xsl:apply-templates select="*[1]">
       <xsl:with-param name="pText" select="$pText"/>
      </xsl:apply-templates>
     </xsl:template>
    
     <xsl:template match="Cell/*">
      <xsl:param name="pText" as="xs:string*"/>
    
      <xsl:variable name="vText" as="xs:string*"
       select="$pText, string(.)"/>
    
      <xsl:sequence select=
       "$vText
          [not(current()/../following-sibling::Cell)]"/>
      <xsl:apply-templates select="../following-sibling::Cell[1]">
        <xsl:with-param name="pText" select="$vText"/>
      </xsl:apply-templates>
      <xsl:apply-templates select="following-sibling::*">
        <xsl:with-param name="pText" select="$pText"/>
      </xsl:apply-templates>
     </xsl:template>
    </xsl:stylesheet>
    

    when applied on the provided XML document:

    <Section>
        <Chapter>
            <Cell colname="1">
                <Value>A</Value>
            </Cell>
            <Cell colname="2">
                <MyValue>AAA</MyValue>
                <MyValue>BBB</MyValue>
            </Cell>
            <Cell colname="3">
                <MyCar>Honda</MyCar>
            </Cell>
        </Chapter>
        <Chapter>
            <Cell colname="1">
                <Value>C</Value>
            </Cell>
            <Cell colname="2">
                <MyValue>CCC</MyValue>
            </Cell>
            <Cell colname="3">
                <MyCar>Toyota</MyCar>
            </Cell>
        </Chapter>
    </Section>
    

    produces exactly the wanted, correct result:

    A AAA Honda A BBB Honda C CCC Toyota
    

    Explanation:

    1. This is essentially a task for producing all combinations of values that belong to N groups (the children of a Cell make a group), taking one item from each group.

    2. At any item of group K, we add this item to the current combination of items of the groups 1, 2, ..., K-1. Then we pass this newly formed combination to the group K+1. If we are an item in the last group (N), we print out (xsl:sequence) the whole accumulated combination.

    3. When the control returns, all combinations of elements of the remaining groups (K+1, K+2, ..., N) have been appended to our current combination of the group items of groups 1, 2, ..., K. All these combinations have already been printed.

    4. We pass the same group elements combination that was passed to us -- now we pass it to the following item in the current group (the following-sibling).