XSL to create folders based on attribute - xslt

I am using XSL to transform XML to KML to be viewed in Google Earth. I would to be able to create folders for each "IT_Type" in the XML sample below
Currently, the XML is being transformed into a folder structure like this:
Point
VSS1
VSS2
Cab1
Cab2
DMS1
DMS2
Line
From:To:
From:To:
From:To:
It needs to be structured into folders like this, with a folder for each IT_Type under the Point and Line parent folders.
Point
VSS
VSS1
VSS2
Cabinet
Cab1
Cab2
DMS
DMS1
DMS2
Line
Handhole
From:To:
Cabinet
From:To:
Other
From:To:
What is the best way to go about setting up the XSL for this? Will performance be an issue on large data sets?
Any advice or code sample is appreciated.
Thank you.
Please see below for sample XML
<Parents>
<Point>
<Row IT_ID="116" IT_Name="VSS1" IT_Type="VSS" GPSLat="43.953000000000" GPSLong="-85.671800000000" />
<Row IT_ID="117" IT_Name="VSS2" IT_Type="VSS" GPSLat="43.966900000000" GPSLong="-85.678900000000" />
<Row IT_ID="122" IT_Name="Cab1" IT_Type="Cabinet" GPSLat="43.903100000000" GPSLong="-85.677100000000" />
<Row IT_ID="123" IT_Name="Cab2" IT_Type="Cabinet" GPSLat="43.913500000000" GPSLong="-85.677300000000" />
<Row IT_ID="254" IT_Name="DMS1" IT_Type="DMS" GPSLat="43.903100000000" GPSLong="-85.677100000000" />
<Row IT_ID="255" IT_Name="DMS2" IT_Type="DMS" GPSLat="43.989400000000" GPSLong="-85.676800000000" />
</Point>
<Line>
<Row LINE_ID="1117" IT_Type="Handhole" FROM_SYSTEM_ID="2127" TO_SYSTEM_ID="1947" FromLat="43.438474034300" FromLong="-83.195331982500" ToLat="43.437072542900" ToLong="-83.193657308800">
<Row2 LINE_ID="1117" CONDUIT_NUMBER="1" CONDUIT_TYPE="Fiber" FIBER_NUMBER="1" FIBER_TYPE="Trunk" STRANDS="96" />
<Row2 LINE_ID="1117" CONDUIT_NUMBER="2" CONDUIT_TYPE="Empty" FIBER_NUMBER="" FIBER_TYPE="" STRANDS="" />
<Row2 LINE_ID="1117" CONDUIT_NUMBER="3" CONDUIT_TYPE="Empty" FIBER_NUMBER="" FIBER_TYPE="" STRANDS="" />
<Row2 LINE_ID="1117" CONDUIT_NUMBER="4" CONDUIT_TYPE="Empty" FIBER_NUMBER="" FIBER_TYPE="" STRANDS="" />
</Row>
<Row LINE_ID="997" IT_Type="Cabinet" FROM_SYSTEM_ID="2011" TO_SYSTEM_ID="2012" FromLat="43.482705558800" FromLong="-83.260130135400" ToLat="43.482694479700" ToLong="-83.260107590500">
<Row2 LINE_ID="997" CONDUIT_NUMBER="1" CONDUIT_TYPE="Other" FIBER_NUMBER="" FIBER_TYPE="" STRANDS="" />
</Row>
<Row LINE_ID="1220" IT_Type="Other" FROM_SYSTEM_ID="2415" TO_SYSTEM_ID="2413" FromLat="43.624664303600" FromLong="-83.086848805700" ToLat="43.624645615600" ToLong="-83.086770805500">
<Row2 LINE_ID="1220" CONDUIT_NUMBER="1" CONDUIT_TYPE="Fiber" FIBER_NUMBER="1" FIBER_TYPE="Dist" STRANDS="12" />
<Row2 LINE_ID="1220" CONDUIT_NUMBER="2" CONDUIT_TYPE="Electric" FIBER_NUMBER="" FIBER_TYPE="" STRANDS="" />
</Row>
</Line>
</Parents>
Please see below for my XSL:
<xsl:template match="Parents">
<Folder>
<name>
Point
</name>
<xsl:for-each select="Point/Row">
<Placemark>
<name>
<xsl:value-of select="#IT_Name"/>
</name>
<description>
<xsl:value-of select="#Location"/>
</description>
<styleUrl>
<xsl:value-of select="concat($hash,#IT_Type)"/>
</styleUrl>
<Point>
<coordinates>
<xsl:value-of select="#GPSLong"/>,
<xsl:value-of select="#GPSLat"/>
</coordinates>
</Point>
</Placemark>
</xsl:for-each>
</Folder>
<Folder>
<name>
Line
</name>
<xsl:for-each select="Line/Row">
<Placemark>
<name>
From: <xsl:value-of select="#FROM_SYSTEM_ID"/> to: <xsl:value-of select="#TO_SYSTEM_ID"/>
</name>
<description>
<xsl:value-of select="#CONDUIT_NUMBER"/>
</description>
<styleUrl>
<xsl:value-of select="concat($hash,#IT_Type)"/>
</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>
<xsl:value-of select="#FromLong"/>,<xsl:value-of select="#FromLat"/>,0 <xsl:value-of select="#ToLong"/>,<xsl:value-of select="#ToLat"/>,0
</coordinates>
</LineString>
</Placemark>
</xsl:for-each>
</Folder>
</xsl:template>

Sorry for having completely revised your initial template, but the common way to do this (in XSLT 1.0) is by applying Meunchian's method on a multi-level grouping. In your specific case, you can create a xsl:key based on concatenation of #IT_Type and the parent element of Row.
For instance, this XSLT 1.0 (tested under Saxon 6.5)
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kIT_Type"
match="Row"
use="concat(
name(parent::node()),#IT_Type
)"/>
<xsl:template match="Parents/*">
<Folder>
<name><xsl:value-of select="name()"/></name>
<xsl:apply-templates select="Row[
generate-id(.)
=
generate-id(key('kIT_Type',
concat(name(parent::node()),#IT_Type))[1])
]"/>
</Folder>
</xsl:template>
<xsl:template match="Row">
<Folder>
<name><xsl:value-of select="#IT_Type"/></name>
<xsl:apply-templates select="key('kIT_Type',
concat(name(parent::node()),#IT_Type))"
mode="placemark"/>
</Folder>
</xsl:template>
<xsl:template match="Row[parent::Point]" mode="placemark">
<Placemark>
<name>
<xsl:value-of select="#IT_Name"/>
</name>
<description>
<!--xsl:value-of select="#Location"/-->
</description>
<styleUrl>
<!--xsl:value-of select="concat($hash,#IT_Type)"/-->
</styleUrl>
<Point>
<coordinates>
<xsl:value-of select="#GPSLong"/>,
<xsl:value-of select="#GPSLat"/>
</coordinates>
</Point>
</Placemark>
</xsl:template>
<xsl:template match="Row[parent::Line]" mode="placemark">
<Placemark>
<name>
From: <xsl:value-of select="#FROM_SYSTEM_ID"/> to: <xsl:value-of select="#TO_SYSTEM_ID"/>
</name>
<description>
<xsl:value-of select="#CONDUIT_NUMBER"/>
</description>
<styleUrl>
<!-- xsl:value-of select="concat($hash,#IT_Type)"/-->
</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>
<xsl:value-of select="#FromLong"/>,<xsl:value-of select="#FromLat"/>,0 <xsl:value-of select="#ToLong"/>,<xsl:value-of select="#ToLat"/>,0
</coordinates>
</LineString>
</Placemark>
</xsl:template>
<xsl:template match="Row2"/>
</xsl:stylesheet>
Applied on your input, produces the RTF:
<Folder>
<name>Point</name>
<Folder>
<name>VSS</name>
<Placemark>
<name>VSS1</name>
<description/>
<styleUrl/>
<Point>
<coordinates>-85.671800000000,
43.953000000000</coordinates>
</Point>
</Placemark>
<Placemark>
<name>VSS2</name>
<description/>
<styleUrl/>
<Point>
<coordinates>-85.678900000000,
43.966900000000</coordinates>
</Point>
</Placemark>
</Folder>
<Folder>
<name>Cabinet</name>
<Placemark>
<name>Cab1</name>
<description/>
<styleUrl/>
<Point>
<coordinates>-85.677100000000,
43.903100000000</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Cab2</name>
<description/>
<styleUrl/>
<Point>
<coordinates>-85.677300000000,
43.913500000000</coordinates>
</Point>
</Placemark>
</Folder>
<Folder>
<name>DMS</name>
<Placemark>
<name>DMS1</name>
<description/>
<styleUrl/>
<Point>
<coordinates>-85.677100000000,
43.903100000000</coordinates>
</Point>
</Placemark>
<Placemark>
<name>DMS2</name>
<description/>
<styleUrl/>
<Point>
<coordinates>-85.676800000000,
43.989400000000</coordinates>
</Point>
</Placemark>
</Folder>
</Folder>
<Folder>
<name>Line</name>
<Folder>
<name>Handhole</name>
<Placemark>
<name>
From: 2127 to: 1947</name>
<description/>
<styleUrl/>
<LineString>
<tessellate>1</tessellate>
<coordinates>-83.195331982500,43.438474034300,0 -83.193657308800,43.437072542900,0
</coordinates>
</LineString>
</Placemark>
</Folder>
<Folder>
<name>Cabinet</name>
<Placemark>
<name>
From: 2011 to: 2012</name>
<description/>
<styleUrl/>
<LineString>
<tessellate>1</tessellate>
<coordinates>-83.260130135400,43.482705558800,0 -83.260107590500,43.482694479700,0
</coordinates>
</LineString>
</Placemark>
</Folder>
<Folder>
<name>Other</name>
<Placemark>
<name>
From: 2415 to: 2413</name>
<description/>
<styleUrl/>
<LineString>
<tessellate>1</tessellate>
<coordinates>-83.086848805700,43.624664303600,0 -83.086770805500,43.624645615600,0
</coordinates>
</LineString>
</Placemark>
</Folder>
</Folder>

Related

XSLT - Grouping Elements after Index value

I'm writing a XSLT that needs to output certain data elements but into fixed length containers. While typically the source data will contain fewer elements than the fixed length, there is potential for the source data to contain more elements than the fixed length. When the latter occurs, I need to output the elements individually to a certain index, then group the remaining siblings based on a property so I can use concatenation and summation on the groups. I'm able to achieve all of this with the exception of the grouping of siblings after a given index value.
Below is a simplified version of what I'm trying to achieve. Assume the fixed length of the container is 5 and the only Type values to be outputted are 1 and 2, omitting 3.
Sample input XML
<?xml version="1.0" encoding="utf-8"?>
<Company>
<Locations>
<Location>
<Id>1</Id>
<Shoppers>
<Shopper>
<Products>
<Product>
<Amount>5.00</Amount>
<Id>1</Id>
<Name>A</Name>
<Type>1</Type>
</Product>
<Product>
<Amount>10.00</Amount>
<Id>1</Id>
<Name>B</Name>
<Type>2</Type>
</Product>
<Product>
<Amount>15.00</Amount>
<Id>1</Id>
<Name>C</Name>
<Type>3</Type>
</Product>
<Product>
<Amount>20.00</Amount>
<Id>1</Id>
<Name>D</Name>
<Type>1</Type>
</Product>
</Products>
</Shopper>
<Shopper>
<Products>
<Product>
<Amount>25.00</Amount>
<Id>1</Id>
<Name>E</Name>
<Type>2</Type>
</Product>
<Product>
<Amount>30.00</Amount>
<Id>1</Id>
<Name>F</Name>
<Type>1</Type>
</Product>
<Product>
<Amount>35.00</Amount>
<Id>1</Id>
<Name>G</Name>
<Type>2</Type>
</Product>
<Product>
<Amount>40.00</Amount>
<Id>1</Id>
<Name>H</Name>
<Type>1</Type>
</Product>
</Products>
</Shopper>
</Shoppers>
</Location>
<Location>
<Id>2</Id>
<Shoppers>
<Shopper>
<Products>
<Product>
<Amount>5.00</Amount>
<Id>2</Id>
<Name>I</Name>
<Type>A</Type>
</Product>
<Product>
<Amount>10.00</Amount>
<Id>2</Id>
<Name>J</Name>
<Type>B</Type>
</Product>
</Products>
</Shopper>
<Shopper>
<Products>
<Product>
<Amount>25.00</Amount>
<Id>2</Id>
<Name>K</Name>
<Type>A</Type>
</Product>
<Product>
<Amount>30.00</Amount>
<Id>2</Id>
<Name>L</Name>
<Type>B</Type>
</Product>
<Product>
<Amount>35.00</Amount>
<Id>2</Id>
<Name>M</Name>
<Type>B</Type>
</Product>
</Products>
</Shopper>
</Shoppers>
</Location>
</Locations>
</Company>
XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl customCode" xmlns:customCode="urn:customCode">
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:key name="product-distinct" match="Product" use="concat(Id,Type)"/>
<xsl:template match="/">
<Locations>
<xsl:for-each select="Company/Locations/Location">
<xsl:variable name="id" select="Id"/>
<!--Ideally, I would like to use a variable as to not repeat the same XPath throughout the code to simplify any necessary changes-->
<xsl:variable name="productList" select="Shoppers/Shopper/Products/Product[Type != '3']"/>
<Location>
<Products>
<xsl:choose>
<xsl:when test="count($productList) > 5">
<xsl:choose>
<xsl:when test="count($productList[generate-id() = generate-id(key('product-distinct', concat($id,Type))[1])]) = 2">
<xsl:for-each select="$productList[position() < 4]">
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="Amount"/>
<xsl:with-param name="productName" select="Name"/>
<xsl:with-param name="type" select="Type"/>
</xsl:call-template>
</xsl:for-each>
<xsl:choose>
<xsl:when test="count($productList[position() > 3][generate-id() = generate-id(key('product-distinct', concat($id,Type))[1])]) = 2">
<xsl:for-each select="$productList[position() > 3][generate-id() = generate-id(key('product-distinct', concat($id,Type))[1])]">
<xsl:variable name="keyGroup" select="key('product-distinct', concat($id,Type))"/>
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="sum($keyGroup/Amount)"/>
<xsl:with-param name="productName" select="$keyGroup"/>
<xsl:with-param name="type" select="$keyGroup/Type"/>
</xsl:call-template>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="$productList[4]/Amount"/>
<xsl:with-param name="productName" select="$productList[4]/Name"/>
<xsl:with-param name="type" select="$productList[4]/Type"/>
</xsl:call-template>
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="sum($productList[position() > 4]/Amount)"/>
<xsl:with-param name="productName">
<xsl:call-template name="CombineProductNames">
<xsl:with-param name="products" select="$productList[position() > 4]"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="type" select="$productList[5]/Type"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$productList[position() < 5]">
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="Amount"/>
<xsl:with-param name="productName" select="Name"/>
<xsl:with-param name="type" select="Type"/>
</xsl:call-template>
</xsl:for-each>
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="sum($productList[position() > 4]/Amount)"/>
<xsl:with-param name="productName">
<xsl:call-template name="CombineProductNames">
<xsl:with-param name="products" select="$productList[position() > 4]"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="type" select="$productList[5]/Type"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$productList">
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="Amount"/>
<xsl:with-param name="productName" select="Name"/>
<xsl:with-param name="type" select="Type"/>
</xsl:call-template>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</Products>
</Location>
</xsl:for-each>
</Locations>
</xsl:template>
<xsl:template name="Product">
<xsl:param name="amount"/>
<xsl:param name="productName"/>
<xsl:param name="type"/>
<Product>
<xsl:attribute name="Amount">
<xsl:value-of select="format-number($amount, '0.00')"/>
</xsl:attribute>
<xsl:attribute name="Name">
<xsl:value-of select="$productName"/>
</xsl:attribute>
<xsl:attribute name="Type">
<xsl:value-of select="$type"/>
</xsl:attribute>
</Product>
</xsl:template>
<xsl:template name="CombineProductNames">
<xsl:param name="products"/>
<xsl:for-each select="$products/Name">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Current Output
<?xml version="1.0" encoding="utf-8"?>
<Locations>
<Location>
<Products>
<Product Amount="5.00" Name="A" Type="1" />
<Product Amount="10.00" Name="B" Type="2" />
<Product Amount="20.00" Name="D" Type="1" />
<Product Amount="25.00" Name="E" Type="2" />
<Product Amount="105.00" Name="F, G, H" Type="1" />
</Products>
</Location>
<Location>
<Products>
<Product Amount="5.00" Name="I" Type="A" />
<Product Amount="10.00" Name="J" Type="B" />
<Product Amount="25.00" Name="K" Type="A" />
<Product Amount="30.00" Name="L" Type="B" />
<Product Amount="35.00" Name="M" Type="B" />
</Products>
</Location>
</Locations>
Expected Output
<?xml version="1.0" encoding="utf-8"?>
<Locations>
<Location>
<Products>
<Product Amount="5.00" Name="A" Type="1" />
<Product Amount="10.00" Name="B" Type="2" />
<Product Amount="20.00" Name="D" Type="1" />
<Product Amount="60.00" Name="E, G" Type="2" />
<Product Amount="70.00" Name="F, H" Type="1" />
</Products>
</Location>
<Location>
<Products>
<Product Amount="5.00" Name="I" Type="A" />
<Product Amount="10.00" Name="J" Type="B" />
<Product Amount="25.00" Name="K" Type="A" />
<Product Amount="30.00" Name="L" Type="B" />
<Product Amount="35.00" Name="M" Type="B" />
</Products>
</Location>
</Locations>

XSLT - Problems changing the node context in conjunction with the identity pattern

I have a given source XML document with a structure like this:
<?xml version="1.0" encoding="UTF-8"?>
<sample>
<definition>
<variable>
<name>object01_ID_138368350261919620</name>
<value>NUL</value>
</variable>
<variable>
<name>param01_ID_138368350261919621</name>
<value>10</value>
</variable>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
</definition>
<override>
<assignment>
<name>object01_ID_138368350261919620</name>
<path>module01/object01</path>
</assignment>
<assignment>
<name>param01_ID_138368350261919621</name>
<path>module01/object01/param01</path>
</assignment>
<assignment>
<name>param02_ID_138368350261919622</name>
<path>module01/object01/param02</path>
</assignment>
</override>
</sample>
The characteristic of the source XML is:
Each <assignment> element within the <override> element corresponds to exactly one <variable> element within the <definition> element. This 1:1 relationship is established by the content of their <name> element.
The requirements of the transformation and the target XML are:
Depending on the pattern of the content of the <path> elements, within the <assignment> elements in an <override> element, I like to add a new <assignment> element. Important to note is, that an <assignment> element is the leading information. Therefore always at first, a new <assignment> element with its <path> and <name> content has to be created and in conjunction with that a corresponding new <variable> element with the same <name> content and a specific <value> content has to be created and inserted at last position in the <definition> element. For example, for adding param03, the right result should look like:
<?xml version="1.0" encoding="UTF-8"?>
<sample>
<definition>
<variable>
<name>param00_ID_138368350261919620</name>
<value>NUL</value>
</variable>
<variable>
<name>param01_ID_138368350261919621</name>
<value>10</value>
</variable>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
<variable>
<name>Param03_ID_138368350261919623</name>
<value>1000</value>
</variable>
</definition>
<override>
<assignment>
<name>param00_ID_138368350261919620</name>
<path>module01/object01</path>
</assignment>
<assignment>
<name>param01_ID_138368350261919621</name>
<path>module01/object01/param01</path>
</assignment>
<assignment>
<name>param02_ID_138368350261919622</name>
<path>module01/object01/param02</path>
</assignment>
<assignment>
<name>Param03_ID_138368350261919623</name>
<xpath>module01/object01/param03</xpath>
</assignment>
</override>
</sample>
My XSL 2.0 stylesheet for transformation:
For identity transformation, I have choosen to use the fine-grained control identity rule, recommended by [Dimitre Novatchev]. Applying the processing param03 template, I create a new <assignment> element with its specific <path> and <name> content. Within that template, I like to change the node context by using for-each, to the <definition> element and add at last position a new <variable> element with the corresponding <name> content and a specific <value> content. This stylesheet has been tested with Saxon HE 9.5.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="fo xs fn">
<!--
global declarations ==========================================================
-->
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- randomid here is just a fake for sake of simplification -->
<xsl:variable name="randomid" select="138368350261919623"/>
<!--
template - identity ==========================================================
-->
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<!--
template - variable assignment ===============================================
-->
<xsl:template name="variable_assignment">
<xsl:param name="value_node_name"/>
<xsl:param name="value_node_path"/>
<xsl:message select="'processing: variable assignment'"/>
<xsl:message select="concat('applying name: ', $value_node_name)"/>
<xsl:message select="concat('applying path: ', $value_node_path)"/>
<xsl:call-template name="identity"/>
<assignment>
<name>
<xsl:value-of select="$value_node_name"/>
</name>
<xpath>
<xsl:value-of select="$value_node_path"/>
</xpath>
</assignment>
</xsl:template>
<!--
template - processing param03 =============================================
-->
<xsl:template match="/sample/override[not(assignment
/path[matches(text(), '.*/object01/param03$')])]
/assignment[path[matches(text(), '.*/object01$')]]">
<!-- setting params -->
<xsl:param name="value_node_name_target">
<xsl:value-of select="concat('Param03_ID', '_', $randomid)"/>
</xsl:param>
<xsl:param name="value_node_path_target">
<xsl:value-of select="concat(./path, '/param03')"/>
</xsl:param>
<xsl:param name="value_node_value_target" select="'1000'"/>
<!-- processing variable assignment -->
<xsl:call-template name="variable_assignment">
<xsl:with-param name="value_node_name" select="$value_node_name_target"/>
<xsl:with-param name="value_node_path" select="$value_node_path_target"/>
</xsl:call-template>
<!-- processing variable definition -->
<xsl:for-each select="/sample/definition/*[position()=last()]">
<xsl:message select="'processing: variable definition'"/>
<xsl:message select="concat('Here we are: ', .)"/>
<xsl:message select="concat('applying name: ', $value_node_name_target)"/>
<xsl:message select="concat('applying value: ', $value_node_value_target)"/>
<xsl:call-template name="identity"/>
<variable>
<name>
<xsl:value-of select="$value_node_name_target"/>
</name>
<value>
<xsl:value-of select="$value_node_value_target"/>
</value>
</variable>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The resulting wrong XML is:
<?xml version="1.0" encoding="UTF-8"?>
<sample>
<definition>
<variable>
<name>object01_ID_138368350261919620</name>
<value>NUL</value>
</variable>
<variable>
<name>param01_ID_138368350261919621</name>
<value>10</value>
</variable>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
</definition>
<override>
<assignment>
<name>object01_ID_138368350261919620</name>
<path>module01/object01</path>
</assignment>
<assignment>
<name>param01_ID_138368350261919621</name>
<path>module01/object01/param01</path>
</assignment>
<assignment>
<name>param02_ID_138368350261919622</name>
<path>module01/object01/param02</path>
</assignment>
<assignment>
<name>Param03_ID_138368350261919623</name>
<xpath>module01/object01/param03</xpath>
</assignment>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
<variable>
<name>Param03_ID_138368350261919623</name>
<value>1000</value>
</variable>
</override>
</sample>
The problems I got are:
The node context becomes not changed. The new <variable> element becomes added at last position into the <override> element, instead into the <definition> element, as wanted.
Additionally the last <variable> element from <definition> element becomes copied into the <override> element. That is not what I want.
Help needed!
I really would appreciate if somebody could advice me, in which way I would have to adapt my XSLT in order to get rid of the problems and the right behavior as delineated above.
Many thanks.
The XSLT 2.0 proposed by you, adapted by me:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="fo xs fn">
<!--
global declarations ==========================================================
-->
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- baserandom here is just a fake for sake of simplification -->
<xsl:param name="baserandom" select="138368350261919623"/>
<!--MOVED PARAMS FROM ORIGINAL TEMPLATE HERE SO THEY CAN BE USED BY MULTIPLE TEMPLATES -->
<!--xsl:param name="value_node_path"-->
<!--I LEFT THE PREDICATE BECAUSE IT APPEARS THAT THERE COULD BE MORE THAN ONE override ELEMENT.-->
<!--xsl:value-of select="concat(/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
/assignment[1]/path, '/param03')"/>
</xsl:param>
<xsl:param name="value_node_value" select="'1000'"/-->
<!--
template - identity ==========================================================
-->
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<!--
template - definition ========================================================
-->
<!--REPLACES THE xsl:for-each THAT PROCESSES THE VARIABLE DEFINITION-->
<xsl:template match="definition/*[last()]">
<xsl:param name="value_node_name"/>
<xsl:param name="value_node_value"/>
<xsl:call-template name="identity"/>
<xsl:if test="$value_node_name">
<xsl:message select="'processing: variable definition'"/>
<xsl:message select="concat('Here we are: ', .)"/>
<xsl:message select="concat('applying name: ', $value_node_name)"/>
<xsl:message select="concat('applying value: ', $value_node_value)"/>
<variable>
<name>
<xsl:value-of select="$value_node_name"/>
</name>
<value>
<xsl:value-of select="$value_node_value"/>
</value>
</variable>
</xsl:if>
</xsl:template>
<!--
template - processing param03 =============================================
-->
<xsl:template match="/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
/assignment[path[matches(text(), '.*/object01$')]]">
<!-- name -->
<xsl:param name="value_node_name">
<xsl:value-of select="concat('param03_ID', '_', $baserandom)"/>
</xsl:param>
<!-- path -->
<xsl:param name="value_node_path">
<xsl:value-of select="concat(./path, '/param03')"/>
</xsl:param>
<!-- value -->
<xsl:param name="value_node_value" select="'1000'"/>
<!-- processing definition -->
<xsl:apply-templates select="/sample/definition/*[last()]">
<xsl:with-param name="value_node_name" select="$value_node_name"/>
<xsl:with-param name="value_node_value" select="$value_node_value"/>
</xsl:apply-templates>
<!-- processing assignment -->
<xsl:message select="'processing: variable assignment'"/>
<xsl:message select="concat('applying name: ', $value_node_name)"/>
<xsl:message select="concat('applying path: ', $value_node_path)"/>
<xsl:call-template name="identity"/>
<assignment>
<name>
<xsl:value-of select="$value_node_name"/>
</name>
<path>
<xsl:value-of select="$value_node_path"/>
</path>
</assignment>
</xsl:template>
</xsl:stylesheet>
The resulting XML (still wrong):
<?xml version="1.0" encoding="UTF-8"?>
<sample>
<definition>
<variable>
<name>object01_ID_138368350261919620</name>
<value>NUL</value>
</variable>
<variable>
<name>param01_ID_138368350261919621</name>
<value>10</value>
</variable>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
</definition>
<override>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
<variable>
<name>param03_ID_138368350261919623</name>
<value>1000</value>
</variable>
<assignment>
<name>object01_ID_138368350261919620</name>
<path>module01/object01</path>
</assignment>
<assignment>
<name>param01_ID_138368350261919621</name>
<path>module01/object01/param01</path>
</assignment>
<assignment>
<name>param02_ID_138368350261919622</name>
<path>module01/object01/param02</path>
</assignment>
<assignment>
<name>param03_ID_138368350261919623</name>
<path>module01/object01/param03</path>
</assignment>
</override>
</sample>
The issue you are having is that xsl:for-each isn't changing the output context. It's only changing the iteration context.
You are still outputting in the context of assignment (template match) when you're iterating over xsl:for-each select="/sample/definition/*[position()=last()].
You'll need to output the new variable from the context of definition.
I've modified your XSLT to produce what you want. It might not be the final solution, but should get you closer. I added comments (all uppercase) to try to explain what I changed. Please let me know if there are questions.
Modified XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="fo xs fn">
<!--
global declarations ==========================================================
-->
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- randomid here is just a fake for sake of simplification -->
<xsl:param name="randomid" select="138368350261919623"/>
<!--MOVED PARAMS FROM ORIGINAL TEMPLATE HERE SO THEY CAN BE USED BY MULTIPLE TEMPLATES -->
<xsl:param name="value_node_name_target">
<xsl:value-of select="concat('Param03_ID', '_', $randomid)"/>
</xsl:param>
<xsl:param name="value_node_path_target">
<!--I LEFT THE PREDICATE BECAUSE IT APPEARS THAT THERE COULD BE MORE THAN ONE override ELEMENT.-->
<xsl:value-of select="concat(/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
/assignment[1]/path, '/param03')"/>
</xsl:param>
<xsl:param name="value_node_value_target" select="'1000'"/>
<!--
template - identity ==========================================================
-->
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<!--
template - variable assignment ===============================================
-->
<!--MOVED CODE FROM THIS TEMPLATE INTO THE assignment TEMPLATE-->
<!--REPLACES THE xsl:for-each THAT PROCESSES THE VARIABLE DEFINITION-->
<xsl:template match="definition[../override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
/assignment[path[matches(text(), '.*/object01$')]]]/*[last()]">
<xsl:message select="'processing: variable definition'"/>
<xsl:message select="concat('Here we are: ', .)"/>
<xsl:message select="concat('applying name: ', $value_node_name_target)"/>
<xsl:message select="concat('applying value: ', $value_node_value_target)"/>
<xsl:call-template name="identity"/>
<variable>
<name>
<xsl:value-of select="$value_node_name_target"/>
</name>
<value>
<xsl:value-of select="$value_node_value_target"/>
</value>
</variable>
</xsl:template>
<!--
template - processing param03 =============================================
-->
<xsl:template match="/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
/assignment[path[matches(text(), '.*/object01$')]]">
<!-- setting params -->
<!--MOVED TEMPLATE PARAMS TO GLOBAL PARAMS-->
<!-- processing variable assignment -->
<!--REPLACED UNNECESSARY xsl:call-template WITH ACTUAL CODE-->
<xsl:message select="'processing: variable assignment'"/>
<xsl:message select="concat('applying name: ', $value_node_name_target)"/>
<xsl:message select="concat('applying path: ', $value_node_path_target)"/>
<xsl:call-template name="identity"/>
<assignment>
<name>
<xsl:value-of select="$value_node_name_target"/>
</name>
<!--CHANGED FROM xpath TO path (APPEARED TO BE A TYPO)-->
<path>
<xsl:value-of select="$value_node_path_target"/>
</path>
</assignment> <!-- processing variable definition -->
<!--THIS IS NOW DONE BY A SEPARATE MATCHING TEMPLATE-->
</xsl:template>
</xsl:stylesheet>
Output
<sample>
<definition>
<variable>
<name>object01_ID_138368350261919620</name>
<value>NUL</value>
</variable>
<variable>
<name>param01_ID_138368350261919621</name>
<value>10</value>
</variable>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
<variable>
<name>Param03_ID_138368350261919623</name>
<value>1000</value>
</variable>
</definition>
<override>
<assignment>
<name>object01_ID_138368350261919620</name>
<path>module01/object01</path>
</assignment>
<assignment>
<name>param01_ID_138368350261919621</name>
<path>module01/object01/param01</path>
</assignment>
<assignment>
<name>param02_ID_138368350261919622</name>
<path>module01/object01/param02</path>
</assignment>
<assignment>
<name>Param03_ID_138368350261919623</name>
<path>module01/object01/param03</path>
</assignment>
</override>
</sample>

XSLT 1.0 Grouping with multiple elements with same name

I have an XML that looks like -
<resultset>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2A:level3A</Hierarchy>
<Hierarchy>level1B:level2B:level3B</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning1</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2A:level3A</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning2</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2B:level3C</Hierarchy>
</ITEM>
</content>
</hit>
</resultset>
Note that there are multiple hierarchy elements which is a concatenated string of level1:level2:level3
I am looking to transform this into something like this -
<TREE>
<LEVELS>
<LEVEL1 name="level1A">
<LEVEL2 name="level2A">
<LEVEL3 name="level3A">
<ITEM Name="Office Cleaning"/>
<ITEM Name="Office Cleaning1"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1B">
<LEVEL2 name="level2B">
<LEVEL3 name="level3B">
<ITEM Name="Office Cleaning"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1A">
<LEVEL2 name="level2B">
<LEVEL3 name="level3C">
<ITEM Name="Office Cleaning2"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
</LEVELS>
</TREE>
Basically each item has multiple hierachy associated with it. I need to group them together.
I got only as far as -
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:autn="http://schemas.autonomy.com/aci/">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:key name="HIERARCHYLEVELS" match="resultset/hit/content/ITEM" use="HIERARCHY" />
<xsl:template match="/">
<TREE>
<xsl:for-each select="resultset/hit/content/ITEM[generate-id()=generate-id(key('HIERARCHYLEVELS', HIERARCHY)[1])]">
<xsl:for-each select="HIERARCHY">
<xsl:variable name="level" select="HIERARCHY"/>
<HIERARCHY name="{$level}" >
<xsl:variable name="name" select="TITLE"/>
<ITEM name="{$name}"/>
</HIERARCHY>
</xsl:for-each>
</xsl:for-each>
</TREE>
</xsl:template>
</xsl:stylesheet>
But the problem is I only get the first matching hierarchy tag. For e.g. I dont get to see "Office cleaning1".
What can I do to make sure all hierarchy elements are considered? I still need to split it into various levels.
This transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kItemByHier" match="ITEM" use="Hierarchy"/>
<xsl:key name="kHierByVal" match="Hierarchy" use="."/>
<xsl:template match="/*">
<xsl:apply-templates select=
"*/*/*/Hierarchy[generate-id()=generate-id(key('kHierByVal',.)[1])]"/>
</xsl:template>
<xsl:template match="Hierarchy">
<xsl:call-template name="makeTree">
<xsl:with-param name="pHier" select="string()"/>
<xsl:with-param name="pItems" select="key('kItemByHier', .)"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="makeTree">
<xsl:param name="pHier"/>
<xsl:param name="pDepth" select="1"/>
<xsl:param name="pItems" select="/.."/>
<xsl:choose>
<xsl:when test="not($pHier)">
<xsl:for-each select="$pItems">
<ITEM name="{TITLE}"/>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:element name="LEVEL{$pDepth}">
<xsl:attribute name="name">
<xsl:value-of select="substring-before(concat($pHier,':'), ':')"/>
</xsl:attribute>
<xsl:call-template name="makeTree">
<xsl:with-param name="pHier"
select="substring-after($pHier,':')"/>
<xsl:with-param name="pDepth" select="$pDepth+1"/>
<xsl:with-param name="pItems" select="$pItems"/>
</xsl:call-template>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<resultset>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2A:level3A</Hierarchy>
<Hierarchy>level1B:level2B:level3B</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning1</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2A:level3A</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning2</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2B:level3C</Hierarchy>
</ITEM>
</content>
</hit>
</resultset>
produces the wanted, correct result:
<LEVEL1 name="level1A">
<LEVEL2 name="level2A">
<LEVEL3 name="level3A">
<ITEM name="Office Cleaning"/>
<ITEM name="Office Cleaning1"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1B">
<LEVEL2 name="level2B">
<LEVEL3 name="level3B">
<ITEM name="Office Cleaning"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1A">
<LEVEL2 name="level2B">
<LEVEL3 name="level3C">
<ITEM name="Office Cleaning2"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
For interest, here is a draft effort at a solution. It is close, but not quiet right, as you can see from the output, as it uses different grouping rules. I am still trying to understand the required grouping rules. I will update if I get a better understanding.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="xsl exsl">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:variable name="phase-1-output">
<xsl:apply-templates select="/" mode="phase-1" />
</xsl:variable>
<xsl:variable name="phase-2-output">
<xsl:apply-templates select="exsl:node-set($phase-1-output)" mode="phase-2" />
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select="$phase-2-output" />
</xsl:template>
<!--================ Phase 1 ===============================-->
<xsl:template match="/" mode="phase-1">
<t>
<xsl:apply-templates select="*/*/*/ITEM/Hierarchy" mode="phase-1" />
</t>
</xsl:template>
<xsl:template match="Hierarchy" mode="phase-1">
<xsl:call-template name="analyze-hierarchy">
<xsl:with-param name="levels" select="." />
<xsl:with-param name="item" select="../TITLE" />
</xsl:call-template>
</xsl:template>
<xsl:template name="analyze-hierarchy"><!-- phase-1 -->
<xsl:param name="levels" />
<xsl:param name="item" />
<xsl:variable name="level" select="substring-before(concat($levels,':'),':')" />
<xsl:variable name="e-level" select="
translate(
substring($level,1,string-length($level) - 1),
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
<xsl:choose>
<xsl:when test="$level">
<xsl:element name="{$e-level}">
<xsl:attribute name="name"><xsl:value-of select="$level" /></xsl:attribute>
<xsl:call-template name="analyze-hierarchy">
<xsl:with-param name="levels" select="substring-after($levels,':')" />
<xsl:with-param name="item" select="$item" />
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<ITEM Name="{$item}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--================ Phase 2 ===============================-->
<xsl:key name="kLevel"
match="*[starts-with(name(),'LEVEL')]"
use="concat(generate-id(..),'|',#name)" />
<xsl:template match="/" mode="phase-2">
<TREE>
<LEVELS>
<xsl:variable name="t" select="concat(generate-id(t),'|')" />
<xsl:apply-templates select="t/LEVEL1[
generate-id() = generate-id( key('kLevel',concat($t,#name))[1])
]" mode="phase-2-head" />
</LEVELS>
</TREE>
</xsl:template>
<xsl:template match="*[starts-with(name(),'LEVEL')]" mode="phase-2-head">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates select="key('kLevel',concat(generate-id(..),'|',#name))" mode="phase-2" />
<xsl:copy-of select="ITEM" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(),'LEVEL')]" mode="phase-2">
<xsl:variable name="p" select="concat(generate-id(.),'|')" />
<xsl:apply-templates select="*[starts-with(name(),'LEVEL')][
generate-id() = generate-id( key('kLevel',concat($p,#name))[1])
]" mode="phase-2-head" />
</xsl:template>
</xsl:stylesheet>
...with sample input produces this (not quiet correct output)...
<TREE>
<LEVELS>
<LEVEL1 name="level1A">
<LEVEL2 name="level2A">
<LEVEL3 name="level3A">
<ITEM Name="Office Cleaning" />
</LEVEL3>
</LEVEL2>
<LEVEL2 name="level2A">
<LEVEL3 name="level3A">
<ITEM Name="Office Cleaning1" />
</LEVEL3>
</LEVEL2>
<LEVEL2 name="level2B">
<LEVEL3 name="level3C">
<ITEM Name="Office Cleaning2" />
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1B">
<LEVEL2 name="level2B">
<LEVEL3 name="level3B">
<ITEM Name="Office Cleaning" />
</LEVEL3>
</LEVEL2>
</LEVEL1>
</LEVELS>
</TREE>
UPDATE
Ok, round 2. I copied Dimitre's grouping rule, which is all or nothing on the content of the Hierarchy element. This solution produces the expected output for the sample input. Note that in contrast to Dimitre's <xsl:element name="LEVEL{$pDepth}"> method, I have derived the LEVEL1 style element names from the Hierarchy steps. I am not sure if this is correct.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="kLevel" match="Hierarchy" use="." />
<xsl:template match="/">
<TREE>
<LEVELS>
<xsl:apply-templates select="*/*/*/ITEM/Hierarchy[
generate-id() = generate-id( key('kLevel',.)[1])
]" mode="group" />
</LEVELS>
</TREE>
</xsl:template>
<xsl:template match="Hierarchy" mode="group">
<xsl:call-template name="analyze-hierarchy">
<xsl:with-param name="key" select="." />
<xsl:with-param name="levels" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="analyze-hierarchy">
<xsl:param name="key" />
<xsl:param name="levels" />
<xsl:variable name="level" select="substring-before(concat($levels,':'),':')" />
<xsl:variable name="e-level" select="
translate(
substring($level,1,string-length($level) - 1),
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
<xsl:choose>
<xsl:when test="$level">
<xsl:element name="{$e-level}">
<xsl:attribute name="name"><xsl:value-of select="$level" /></xsl:attribute>
<xsl:call-template name="analyze-hierarchy">
<xsl:with-param name="key" select="$key" />
<xsl:with-param name="levels" select="substring-after($levels,':')" />
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="key('kLevel',$key)" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Hierarchy">
<ITEM Name="{../TITLE}" />
</xsl:template>
</xsl:stylesheet>

Extreme XSLT XML Flattening

I have the following input XML file:
<root>
<a>
<b>1</b>
</a>
<c>
<d>
<e>2</e>
<f>3</f> or <e>3</e>
</d>
<g h="4"/>
<i>
<j>
<k>
<l m="5" n="6" o="7" />
<l m="8" n="9" o="0" />
</k>
</j>
</i>
</c>
</root>
I would like to use XSLT to transform it into the follow outputs:
OUTPUT 1
<root>
<row b="1" e="2" f="3" h="4" m="5" n="6" o="7" />
<row b="1" e="2" f="3" h="4" m="8" n="9" o="0" />
<root>
OUTPUT 2
<root>
<row b="1" e="2" h="4" m="5" n="6" o="7" />
<row b="1" e="2" h="4" m="8" n="9" o="0" />
<row b="1" e="3" h="4" m="5" n="6" o="7" />
<row b="1" e="3" h="4" m="8" n="9" o="0" />
<root>
Can anyone help my XSLT isn't very strong. Thanks.
It will be easier if you let the occurrence of <e> determine the outer loop constructing your <row>s and have a inner loop iterating over all <l>s.
Try something like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="root">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="e">
<!-- store values of 'e' and 'f' in params -->
<xsl:param name="value_of_e" select="." />
<xsl:param name="value_of_f" select="ancestor::d[1]/f" />
<!-- iterate over all 'l's -->
<xsl:for-each select="//l">
<xsl:element name="row">
<xsl:attribute name="b">
<xsl:value-of select="//b" />
</xsl:attribute>
<xsl:attribute name="e">
<xsl:value-of select="$value_of_e" />
</xsl:attribute>
<!-- only include 'f' if it has a value -->
<xsl:if test="$value_of_f != ''">
<xsl:attribute name="f">
<xsl:value-of select="$value_of_f" />
</xsl:attribute>
</xsl:if>
<xsl:attribute name="h">
<xsl:value-of select="ancestor::c[1]/g/#h" />
</xsl:attribute>
<xsl:attribute name="m">
<xsl:value-of select="./#m" />
</xsl:attribute>
<xsl:attribute name="n">
<xsl:value-of select="./#n" />
</xsl:attribute>
<xsl:attribute name="o">
<xsl:value-of select="./#o" />
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:template>
<xsl:template match="b" />
<xsl:template match="f" />
<xsl:template match="g" />
</xsl:stylesheet>

XSL conditional formatting

I am using xsl to transform xml to kml format. I would like to add conditional logic to the xsl to switch the styleUrl based on part of an attribute value. The attribute name is FROM_SYSTEM_ID. The format of the attribute value is "A-123-CAM-1" where "CAM" is part of the string to determine which style definition to use (in this case CAM stands for Camera, CAB stands for Cabinet, etc).
How can I parse this attribute to perform the needed style definition switch?
Following is my xsl template:
<xsl:template match="Line">
<Folder>
<name>
Lines
<!--<xsl:value-of select="#name"/>-->
</name>
<xsl:for-each select="Row">
<Placemark>
<name>
<xsl:value-of select="#FROM_SYSTEM_ID"/>
</name>
<description>
<xsl:value-of select="#TO_SYSTEM_ID"/>
</description>
<styleUrl>#msn_open-diamond00</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>
<xsl:value-of select="#FromLong"/>,<xsl:value-of select="#FromLat"/>,0 <xsl:value-of select="#ToLong"/>,<xsl:value-of select="#ToLat"/>,0
</coordinates>
</LineString>
</Placemark>
</xsl:for-each>
</Folder>
</xsl:template>
Following is a sample of the XML:
<Line>
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAB-1"
TO_SYSTEM_ID="A-123-CAM-3" FromLat="42.624948852000"
FromLong="-83.107221652500"
ToLat="42.624940325900" ToLong="-83.107353167000" />
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAM-1"
TO_SYSTEM_ID="A-123-HH-16" FromLat="42.641662528600"
FromLong="-83.151500129600"
ToLat="42.641709802200" ToLong="-83.151552587600" />
<!-- additional rows here -->
</Line>
You can extract the CAM or CAB portion of the FROM_SYSTEM_ID attribute using a combination of substring-after and substring-before:
<xsl:value-of select="
substring-before(
substring-after(
substring-after(#FROM_SYSTEM_ID, '-'), '-'), '-')"/>
Putting this together with your stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Line">
<Folder>
<name>
Lines
<!--<xsl:value-of select="#name"/>-->
</name>
<xsl:for-each select="Row">
<Placemark>
<name>
<xsl:value-of select="#FROM_SYSTEM_ID"/>
</name>
<description>
<xsl:value-of select="#TO_SYSTEM_ID"/>
</description>
<styleUrl>
<xsl:value-of select="
substring-before(
substring-after(
substring-after(#FROM_SYSTEM_ID, '-'), '-'), '-')"/>
</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>
<xsl:value-of select="#FromLong"/>,<xsl:value-of select="#FromLat"/>,0 <xsl:value-of select="#ToLong"/>,<xsl:value-of select="#ToLat"/>,0
</coordinates>
</LineString>
</Placemark>
</xsl:for-each>
</Folder>
</xsl:template>
</xsl:stylesheet>
Applied to this input:
<Line>
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAB-1"
TO_SYSTEM_ID="A-123-CAM-3" FromLat="42.624948852000"
FromLong="-83.107221652500"
ToLat="42.624940325900" ToLong="-83.107353167000" />
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAM-1"
TO_SYSTEM_ID="A-123-HH-16" FromLat="42.641662528600"
FromLong="-83.151500129600"
ToLat="42.641709802200" ToLong="-83.151552587600" />
</Line>
Produces the following result:
<Folder>
<name>Lines</name>
<Placemark>
<name>A-123-CAB-1</name>
<description>A-123-CAM-3</description>
<styleUrl>CAB</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>-83.107221652500,42.624948852000,0
-83.107353167000,42.624940325900,0
</coordinates>
</LineString>
</Placemark>
<Placemark>
<name>A-123-CAM-1</name>
<description>A-123-HH-16</description>
<styleUrl>CAM</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>-83.151500129600,42.641662528600,0
-83.151552587600,42.641709802200,0
</coordinates>
</LineString>
</Placemark>
</Folder>