Search code examples
xslt-2.0

Creating output groups from input groups


I have the following XML source file:

<?xml version = "1.0" encoding = "UTF-8"?>
<Master revision="B" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Data>
        <Block name="PRIMARY" >
            <BlockGroup name="GROUP_PRIMARY" >
                <BlockLayer Name="COVER" sequence="1">
                    <BlockRef id="BLOCK_COVER"/>
                </BlockLayer>
                <BlockLayer Name="TOP" sequence="2">
                    <BlockRef id="BLOCK_TOP"/>
                </BlockLayer>
                <BlockLayer Name="INDEX_3" sequence="3">
                    <BlockRef id="BLOCK_INDEX_3"/>
                </BlockLayer>
                <BlockLayer Name="INT2" sequence="4">
                    <BlockRef id="BLOCK_INT2"/>
                </BlockLayer>
            </BlockGroup>
        </Block>
        <Block name="SECONDARY" >
            <BlockGroup name="GROUP_SECONDARY" >
                <BlockLayer Name="BLOCK_TOP" sequence="5">
                    <BlockRef id="BLOCK_TOP"/>
                </BlockLayer>
                <BlockLayer Name="INT6" sequence="6">
                    <BlockRef id="BLOCK_INT6"/>
                </BlockLayer>
                <BlockLayer Name="INDEX_7" sequence="7">
                    <BlockRef id="BLOCK_INDEX_7"/>
                </BlockLayer>
            </BlockGroup>
        </Block>
        <Block name="TERTIARY">
            <BlockGroup name="GROUP_TERTIARY">
                <BlockLayer Name="COVER" sequence="1">
                    <BlockRef id="BLOCK_COVER"/>
                </BlockLayer>
                <BlockLayer Name="TOP" sequence="2">
                    <BlockRef id="BLOCK_TOP"/>
                </BlockLayer>
                <BlockLayer Name="INDEX_3" sequence="3">
                    <BlockRef id="BLOCK_INDEX_3"/>
                </BlockLayer>
                <BlockLayer Name="INT2" sequence="4">
                    <BlockRef id="BLOCK_INT2"/>
                </BlockLayer>
            </BlockGroup>
        </Block>
    </Data>
</Master>

I need to create an output file that consists of three groups each containing a unique set of all elements across the three input groups, like so:

<?xml version="1.0" encoding="UTF-8"?>
<Blocks>
   <Block Name="PRIMARY">
      <BlockNumber>1</BlockNumber>
      <BlockCollection>
         <BlockLayer Name="COVER" Order="1">
            <Display>True</Display>
         </BlockLayer>
         <BlockLayer Name="TOP" Order="2">
            <Display>True</Display>
         </BlockLayer>
         <BlockLayer Name="INDEX_3" Order="3">
            <Display>True</Display>
         </BlockLayer>
         <BlockLayer Name="INT2" Order="4">
            <Display>True</Display>
         </BlockLayer>
         <BlockLayer Name="BLOCK_TOP" Order="5">
            <Display>False</Display>
         </BlockLayer>
         <BlockLayer Name="INT6" Order="6">
            <Display>False</Display>
         </BlockLayer>
         <BlockLayer Name="INDEX_7" Order="7">
            <Display>False</Display>
         </BlockLayer>
      </BlockCollection>
   </Block>
   <Block Name="SECONDARY">
      <BlockNumber>2</BlockNumber>
    <BlockCollection>
         <BlockLayer Name="COVER" Order="1">
            <Display>False</Display>
         </BlockLayer>
         <BlockLayer Name="TOP" Order="2">
            <Display>False</Display>
         </BlockLayer>
         <BlockLayer Name="INDEX_3" Order="3">
            <Display>False</Display>
         </BlockLayer>
         <BlockLayer Name="INT2" Order="4">
            <Display>False</Display>
         </BlockLayer>
         <BlockLayer Name="BLOCK_TOP" Order="5">
            <Display>True</Display>
         </BlockLayer>
         <BlockLayer Name="INT6" Order="6">
            <Display>True</Display>
         </BlockLayer>
         <BlockLayer Name="INDEX_7" Order="7">
            <Display>True</Display>
         </BlockLayer>
      </BlockCollection>
   </Block>
   <Block Name="TERTIARY">
      <BlockNumber>3</BlockNumber>
      <BlockCollection>
         <BlockLayer Name="COVER" Order="1">
            <Display>True</Display>
         </BlockLayer>
         <BlockLayer Name="TOP" Order="2">
            <Display>True</Display>
         </BlockLayer>
         <BlockLayer Name="INDEX_3" Order="3">
            <Display>True</Display>
         </BlockLayer>
         <BlockLayer Name="INT2" Order="4">
            <Display>True</Display>
         </BlockLayer>
         <BlockLayer Name="BLOCK_TOP" Order="5">
            <Display>False</Display>
         </BlockLayer>
         <BlockLayer Name="INT6" Order="6">
            <Display>False</Display>
         </BlockLayer>
         <BlockLayer Name="INDEX_7" Order="7">
            <Display>False</Display>
         </BlockLayer>
      </BlockCollection>
   </Block>
</Blocks>

I am using the following stylesheet file:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/">
        <Blocks>
            <xsl:apply-templates select="/Master/Data/Block" mode="block"/>
        </Blocks>
    </xsl:template>

    <xsl:template match="/Master/Data/Block" mode="block">
        <Block Name="{@name}" >
            <BlockNumber><xsl:value-of select="position()" /></BlockNumber>
            <BlockCollection>
                <xsl:apply-templates select="/Master/Data/Block/BlockGroup/BlockLayer[not(@sequence = preceding::*/@sequence)]">
                    <xsl:sort select="@sequence" data-type="number"/>
                </xsl:apply-templates>
            </BlockCollection>
        </Block>
    </xsl:template>

    <xsl:template match="/Master/Data/Block/BlockGroup/BlockLayer">
        <BlockLayer Name="{@Name}" Order="{@sequence}">
            <Display>
                <xsl:value-of select="if(count(../../preceding-sibling::Block) = 0) then 'True' else 'False'"/>
            </Display>
        </BlockLayer>
    </xsl:template>

</xsl:stylesheet>

I need the Display element in each output BlockLayer element to display True or False to indicate which of the three input groups it belongs to. So BlockLayers 1, 2, 3, 4 will display True and BlockLayers 5, 6, 7 will display False for the first and third groups.

For the second group, BlockLayers 1, 2, 3, 4 must display False and 5, 6, 7 must display True.

My first attempt uses the expression:

<xsl:value-of select="if(count(../../preceding-sibling::Block) = 0) then 'True' else 'False'"/>

The context is at the BlockLayer level and I need the grandparent of each BlockLayer. This expression clearly can only display the BlockLayers in the first group. How can I check for the presence of a BlockLayer in each input group?

(Hope that's not too much detail...)

Best regards,

Ralph Bryce


Solution

  • I think it is better to use for-each-group on @sequence instead of the comparison on preceding elements, then you also have the group and can compare whether it intersects with the elements in the current Block:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
        xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <xsl:output method="xml" indent="yes"/>
    
        <xsl:template match="/">
            <Blocks>
                <xsl:apply-templates select="/Master/Data/Block"/>
            </Blocks>
        </xsl:template>
    
        <xsl:template match="/Master/Data/Block">
            <Block Name="{@name}">
                <xsl:variable name="Block" select="."/>
                <BlockNumber><xsl:value-of select="position()" /></BlockNumber>
                <BlockCollection>
                    <xsl:for-each-group select="/Master/Data/Block/BlockGroup/BlockLayer" group-by="xs:integer(@sequence)">
                        <xsl:sort select="current-grouping-key()"/>
                        <xsl:apply-templates select=".">
                            <xsl:with-param name="Display" select="boolean(current-group() intersect $Block/BlockGroup/BlockLayer)"/>
                        </xsl:apply-templates>
                    </xsl:for-each-group>
                </BlockCollection>
            </Block>
        </xsl:template>
    
        <xsl:template match="/Master/Data/Block/BlockGroup/BlockLayer">
            <xsl:param name="Display"/>
            <BlockLayer Name="{@Name}" Order="{@sequence}">
                <Display>
                    <xsl:value-of select="$Display"/>
                </Display>
            </BlockLayer>
        </xsl:template>
    
    </xsl:stylesheet>
    

    Online at http://xsltransform.net/93dEHFC, task left is to use True/False instead of true/false.