I have entries for an index such as the following:
<entry name="a" page="1" />
<entry name="b" page="3" />
<entry name="b" page="4" />
<entry name="b" page="6" />
<entry name="c" page="7" />
now I'd like to get something like
<index name="a" pages="1 />
<index name="b" pages="3-4, 6" />
<index name="c" pages="7" />
Is there some ready to use function for that?
I was thinking of a two pass solution, first put the index entries in a format such as
<index name="a" pages="1 />
<index name="b" pages="3 4 6" />
<index name="c" pages="7" />
and then turn "3 4 6" into "3-4, 6". The first step is easy:
<xsl:for-each-group select="index" group-adjacent="#name">
<xsl:element name="index">
<xsl:attribute name="name" select="#name"/>
<xsl:attribute name="pages" select="current-group()/#page"/>
</xsl:element>
</xsl:for-each-group>
(the attribute 'pages' here could be something different, of course)
Now comes the difficult step, how should I iterate through the list? There must be some clever solution as to check if the next following sibling/#page (I could put the pages into a sequence) is the current #page + 1.
Here is a sample:
<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:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="entries">
<xsl:for-each-group select="entry" group-by="#name">
<index name="{current-grouping-key()}">
<xsl:attribute name="pages">
<xsl:for-each-group select="current-group()/xs:integer(#page)" group-by="position() - .">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
</xsl:if>
<xsl:value-of select="if (current-group()[2])
then (current-group()[1], current-group()[last()])
else ."
separator="-"/>
</xsl:for-each-group>
</xsl:attribute>
</index>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
With the input being
<entries>
<entry name="a" page="1" />
<entry name="b" page="3" />
<entry name="b" page="4" />
<entry name="b" page="6" />
<entry name="c" page="7" />
</entries>
I get the result
<index name="a" pages="1"/>
<index name="b" pages="3-4, 6"/>
<index name="c" pages="7"/>
Here is another solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="entries">
<xsl:for-each-group select="entry" group-by="#name">
<index name="{current-grouping-key()}">
<xsl:attribute name="pages">
<xsl:variable name="vAtribVals" as="xs:integer*">
<xsl:perform-sort select="current-group()/#page/xs:integer(.)">
<xsl:sort data-type="number"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:sequence select=
"$vAtribVals[1],
for $k in 2 to count($vAtribVals)
return
if($vAtribVals[$k] - $vAtribVals[$k -1] ne 1)
then concat(',', $vAtribVals[$k])
else
if($vAtribVals[$k+1] - $vAtribVals[$k] ne 1)
then concat('-', $vAtribVals[$k])
else()
"/>
</xsl:attribute>
</index>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<entries>
<entry name="a" page="1" />
<entry name="b" page="3" />
<entry name="b" page="4" />
<entry name="b" page="6" />
<entry name="c" page="7" />
</entries>
the wanted, correct result is produced:
<index name="a" pages="1"/>
<index name="b" pages="3-4,6"/>
<index name="c" pages="7"/>
Do note:
Thepage attributes are sorted before further processing, and this allows correct processing even if the entry elements do not occur in sorted order in the source XML document.
The processing doesn't depend on relationship between integer value and position, which makes it applicable in other cases, where the attribute values aren't integers.
Related
I am trying to figure out what is wrong with my xsl file. Here is my sample xml file:
<?xml version="1.0" encoding="UTF-8"?>
<session xmlns="http://winscp.net/schema/session/1.0" name="user#host" start="2015-12-30T14:55:40.156Z">
<ls>
<destination value="/test/folder" />
<files>
<file>
<filename value="folder1" />
<type value="d" />
<modification value="2015-03-04T21:09:25.000Z" />
<permissions value="rwxrw-rw-" />
</file>
<file>
<filename value="folder2" />
<type value="d" />
<modification value="2015-03-04T21:08:47.000Z" />
<permissions value="rwxrw-rw-" />
</file>
<file>
<filename value="folder3" />
<type value="d" />
<modification value="2015-03-04T21:08:57.000Z" />
<permissions value="rwxrw-rw-" />
</file>
<file>
<filename value="folder4" />
<type value="d" />
<modification value="2015-03-04T21:09:14.000Z" />
<permissions value="rwxrw-rw-" />
</file>
<file>
<filename value="file1.txt" />
<type value="-" />
<size value="113" />
<modification value="2015-12-30T14:55:07.000Z" />
<permissions value="rw-rw-rw-" />
</file>
</files>
<result success="true" />
</ls>
</session>
This is my xsl file:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:template match="/">
<xsl:text>filename,filedatetime,filetype</xsl:text>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="file"/>
<xsl:text>-eof-</xsl:text>
</xsl:template>
<xsl:template match="file">
<xsl:text>"</xsl:text>
<xsl:value-of select="filename/#value" />
<xsl:text>","</xsl:text>
<xsl:value-of select="modification/#value" />
<xsl:text>","</xsl:text>
<xsl:value-of select="type/#value" />
<xsl:text>"</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
The output that I am getting is:
filename,filedatetime,filetype
-eof-
I cannot seem to figure out how to get the values of "filename", "modification" and "type" elements. Any help would be appreciated.
The line <xsl:apply-templates select="file"/> will only match immediate children of the root node of type file and there aren't any of those.
If you want all file children at any depth you could use <xsl:apply-templates select="//file"/> instead.
The XML has a default namespace, so the elements in your XSL won't match unless their namespaces match too.
The following seems to work (note the definition and use of the xx namespace):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xx="http://winscp.net/schema/session/1.0">
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:template match="/">
<xsl:text>filename,filedatetime,filetype</xsl:text>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="//xx:file" />
<xsl:text>-eof-</xsl:text>
</xsl:template>
<xsl:template match="xx:file">
<xsl:text>"</xsl:text>
<xsl:value-of select="xx:filename/#value" />
<xsl:text>","</xsl:text>
<xsl:value-of select="xx:modification/#value" />
<xsl:text>","</xsl:text>
<xsl:value-of select="xx:type/#value" />
<xsl:text>"</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
I have this XML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
<xsl:param name="navigation-xml">
<item id="home" title-en="Services" title-de="Leistungen" />
<item id="company" title-en="Company" title-de="Unternehmen" />
<item id="references" title-en="References" title-de="Referenzen" />
</xsl:param>
<xsl:param name="navigation" select="exsl:node-set($navigation-xml)/*" />
<xsl:param name="navigation-id" />
<xsl:template name="title">
<xsl:apply-templates select="$navigation" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:if test="$navigation-id = #id">
<xsl:choose>
<xsl:when test="$current-language = 'de'">
<xsl:value-of select="#title-de" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#title-en" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
How can I refactor the last 12 lines, so that the attribute name (either #title-de or #title-en) gets determined dynamically rather than in the (silly) way I did it in?
Thanks for any help.
You could write
<xsl:template name="title">
<xsl:apply-templates select="$navigation" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:if test="$navigation-id = #id">
<xsl:choose>
<xsl:when test="$current-language = 'de'">
<xsl:value-of select="#title-de" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#title-en" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
as
<xsl:template name="title">
<xsl:apply-templates select="$navigation[$navigation-id = #id]" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:value-of select="#*[local-name() = concat('title-', $current-language)]" />
</xsl:template>
IMHO, your problem starts much earlier. If you define your navigation-xml parameter as:
<xsl:param name="navigation-xml">
<item id="home">
<title lang="en">Services</title>
<title lang="de">Leistungen</title>
</item>
<item id="company">
<title lang="en">Company</title>
<title lang="de">Unternehmen</title>
</item>
<item id="references">
<title lang="en">References</title>
<title lang="de">Referenzen</title>
</item>
</xsl:param>
you will be able to address its individual nodes much more conveniently and elegantly.
I think I'm missing something obvious here but here goes. I have the below xml and I need to group the KEY nodes of the matched instances together. This is specified by the match attribute and it can contain more than one item number. There can be any number of ITEM nodes and any number of KEY nodes. Also, there is no limit to the depth of the ITEM nodes. And, the matched instances need not be under the same parent. I'm also limited to XSLT 1.0 and the Microsoft parser.
<?xml version="1.0" encoding="utf-8" ?>
<ITEM number='1'>
<ITEM number='2'>
<ITEM number='3' match='5,11'>
<KEY name='key1' value='x' />
<KEY name='key2' value='y' />
<KEY name='key3' value='z' />
<ITEM number ='4' />
</ITEM>
<ITEM number='5' match='3,11'>
<KEY name='key1' value='x' />
<KEY name='key2' value='y' />
<KEY name='key3' value='z' />
</ITEM>
<ITEM number='6' match='10'>
<KEY name='key1' value='x' />
<KEY name='key2' value='y' />
<KEY name='key4' value='a' />
</ITEM>
<ITEM number='7' />
<ITEM number='8'>
<KEY name='key1' value='x' />
</ITEM>
</ITEM>
<ITEM number='9'>
<ITEM number='10' match='6'>
<KEY name='key1' value='x' />
<KEY name='key3' value='z' />
<KEY name='key5' value='b' />
</ITEM>
</ITEM>
<ITEM number='11' match='3,5'>
<KEY name='key2' value='y' />
<KEY name='key3' value='z' />
</ITEM>
</ITEM>
My expected result would look something like this...
<?xml version="1.0" encoding="utf-8" ?>
<Result>
<Group number="1" />
<Group number="2" />
<Group number="3,5,11">
<KEY name='key1' value='x' />
<KEY name='key2' value='y' />
<KEY name='key3' value='z' />
</Group>
<Group number="4" />
<Group number="6,10">
<KEY name='key1' value='x' />
<KEY name='key2' value='y' />
<KEY name='key3' value='z' />
<KEY name='key4' value='a' />
<KEY name='key5' value='b' />
</Group>
<Group number="7" />
<Group number="8">
<KEY name='key1' value='x' />
</Group>
<Group number="9" />
</Result>
What I actually get is...
<?xml version="1.0" encoding="utf-8"?>
<Result>
<Group number="1" />
<Group number="2" />
<Group number="3,5,11">
<KEY name="key1" value="x" />
<KEY name="key2" value="y" />
<KEY name="key3" value="z" />
</Group>
<Group number="4" />
<Group number="6,10">
<KEY name="key4" value="a" />
<KEY name="key5" value="b" />
</Group>
<Group number="7" />
<Group number="8" />
<Group number="9" />
</Result>
I'm using a key and it looks like once I access that particular value from the key function, I cannot access it again. Group number 6,10 should contain all 5 keys but is missing the first 3 which are already present in group number 3,5. Similarly for group number 8, it should contain 1 key. I've used recursion to skip over the matched instances but I don't think there is any issue over there, it seems to be related to the key functionality. I've attached my xslt below, please take a look and tell me what I'm doing wrong. Any tips for performance improvements are also appreciated :)
<?xml version="1.0" encoding="utf-8"?>
<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">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kKeyByName" match="KEY" use="#name" />
<xsl:template name="ProcessItem">
<!--pItemsList - node set containing items that need to be processed-->
<xsl:param name="pItemsList" />
<!--pProcessedList - string containing processed item numbers in the format |1|2|3|-->
<xsl:param name="pProcessedList" />
<xsl:variable name="vCurrItem" select="$pItemsList[1]" />
<!--Recursion exit condition - check if we have a valid Item-->
<xsl:if test="$vCurrItem">
<xsl:variable name="vNum" select="$vCurrItem/#number" />
<!--Skip processed instances-->
<xsl:if test="not(contains($pProcessedList, concat('|', $vNum, '|')))">
<xsl:element name="Group">
<!--If the item is matched with another item, only the distinct keys of the 2 should be displayed-->
<xsl:choose>
<xsl:when test="$vCurrItem/#match">
<xsl:attribute name="number">
<xsl:value-of select="concat($vNum, ',', $vCurrItem/#match)" />
</xsl:attribute>
<xsl:for-each select="(//ITEM[#number=$vNum or #match=$vNum]/KEY)[generate-id(.)=generate-id(key('kKeyByName', #name)[1])]">
<xsl:apply-templates select="." />
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="number">
<xsl:value-of select="$vNum" />
</xsl:attribute>
<xsl:apply-templates select="KEY" />
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:if>
<!--Append processed instances to list to pass on in recursive function-->
<xsl:variable name="vNewList">
<xsl:value-of select="$pProcessedList" />
<xsl:value-of select="concat($vNum, '|')" />
<xsl:if test="$vCurrItem/#match">
<xsl:value-of select="concat($vCurrItem/#match, '|')" />
</xsl:if>
</xsl:variable>
<!--Call template recursively to process the rest of the instances-->
<xsl:call-template name="ProcessItem">
<xsl:with-param name="pItemsList" select="$pItemsList[position() > 1]" />
<xsl:with-param name="pProcessedList" select="$vNewList" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="KEY">
<xsl:copy>
<xsl:copy-of select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:element name="Result">
<xsl:call-template name="ProcessItem">
<xsl:with-param name="pItemsList" select="//ITEM" />
<xsl:with-param name="pProcessedList" select="'|'" />
</xsl:call-template>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
IF there is only one match or none to each item you can give the following xslt a try:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="kItemNr" match="ITEM" use="#number" />
<xsl:key name="kNumberKey" match="KEY" use="concat(../#number, '|', #name )" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM">
<xsl:if test="not(preceding::ITEM[#number = current()/#match])" >
<Group>
<xsl:attribute name="number">
<xsl:value-of select="#number"/>
<xsl:if test="#match" >
<xsl:text>,</xsl:text>
<xsl:value-of select="#match"/>
</xsl:if>
</xsl:attribute>
<xsl:variable name="itemNr" select="#number"/>
<xsl:apply-templates select="KEY | key('kItemNr',#match )/KEY[
not (key('kNumberKey', concat($itemNr, '|', #name) ) )] ">
<xsl:sort select="#name"/>
</xsl:apply-templates>
</Group>
</xsl:if>
</xsl:template>
<xsl:template match="/" >
<Result>
<xsl:for-each select="//ITEM[count(. | key('kItemNr',number ) ) = 1 ]" >
<xsl:apply-templates select="." />
</xsl:for-each>
</Result>
</xsl:template>
</xsl:stylesheet>
Which will generate the following output:
<?xml version="1.0"?>
<Result>
<Group number="1"/>
<Group number="2"/>
<Group number="3,5">
<KEY name="key1" value="x"/>
<KEY name="key2" value="y"/>
<KEY name="key3" value="z"/>
</Group>
<Group number="4"/>
<Group number="6,10">
<KEY name="key1" value="x"/>
<KEY name="key2" value="y"/>
<KEY name="key3" value="z"/>
<KEY name="key4" value="a"/>
<KEY name="key5" value="b"/>
</Group>
<Group number="7"/>
<Group number="8">
<KEY name="key1" value="x"/>
</Group>
<Group number="9"/>
</Result>
Update because of changed request:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kItemNr" match="ITEM" use="#number" />
<xsl:template match="#*|node()">
<xsl:copy >
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM">
<xsl:variable name="matchStr" select=" concat(',', current()/#match, ',')"/>
<xsl:if test="not(preceding::ITEM[ contains($matchStr, concat(',', #number, ',') )])" >
<Group>
<xsl:attribute name="number">
<xsl:value-of select="#number"/>
<xsl:if test="#match" >
<xsl:text>,</xsl:text>
<xsl:value-of select="#match"/>
</xsl:if>
</xsl:attribute>
<xsl:apply-templates select="(KEY |
//ITEM[
contains( $matchStr, concat(',', #number, ',') )
]/KEY[
not((preceding::ITEM[
contains( $matchStr, concat(',', #number, ',') )
] | current() )/KEY/#name = #name)
]) ">
<xsl:sort select="#name"/>
</xsl:apply-templates>
</Group>
</xsl:if>
</xsl:template>
<xsl:template match="/" >
<Result>
<xsl:for-each select="//ITEM[count(. | key('kItemNr',number ) ) = 1 ]" >
<xsl:apply-templates select="." />
</xsl:for-each>
</Result>
</xsl:template>
</xsl:stylesheet>
This may be quite slow for bigger input data but any way.
Given siblings, some of which are <row> elements and some not, like this,
<h />
<row id='v' />
<a />
<b />
<row id='w' />
<d />
<row id='x' />
<row id='y' />
<f />
<r />
<row id='z' />
using xslt 1.0, I need to process them in order but to group the non-row ones together as I go, like this,
<notRow>
<h />
</notRow>
<row id='v' />
<notRow>
<a />
<b />
</notRow>
<row id='w' />
<notRow>
<d />
<row id='x' />
<row id='y' />
<notRow>
<f />
<r />
</notRow>
<row id='z' />
The first and last may or may not be <row> elements.
How?
It can be as short and simple as this (no need of calling templates several times, xsl:for-each, xsl:if). Here is the complete 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="kFollowing" match="*/*[not(self::row)]"
use="concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template priority="2" match=
"*/*[not(self::row)
and
(preceding-sibling::*[1][self::row]
or not(preceding-sibling::*)
)]">
<notRow>
<xsl:copy-of select=
"key('kFollowing', concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
))"/>
</notRow>
</xsl:template>
<xsl:template match="*/*[not(self::row)]"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML (wrapped into a single top element to make it well-formed):
<t>
<h />
<row id='v' />
<a />
<b />
<row id='w' />
<d />
<row id='x' />
<row id='y' />
<f />
<r />
<row id='z' />
</t>
the wanted, correct result is produced:
<t>
<notRow>
<h/>
</notRow>
<row id="v"/>
<notRow>
<a/>
<b/>
</notRow>
<row id="w"/>
<notRow>
<d/>
</notRow>
<row id="x"/>
<row id="y"/>
<notRow>
<f/>
<r/>
</notRow>
<row id="z"/>
</t>
Update:
The OP has expressed an additional requirement that nodes need be processed by matching templates -- not just copied.
This requires only minimal change:
<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="kFollowing" match="*/*[not(self::row)]"
use="concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template priority="2" match=
"*/*[not(self::row)
and
(preceding-sibling::*[1][self::row]
or not(preceding-sibling::*)
)]">
<notRow>
<xsl:apply-templates mode="group" select=
"key('kFollowing', concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
))"/>
</notRow>
</xsl:template>
<!-- This template can be replaced with whatever processing needed -->
<xsl:template match="*" mode="group">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="*/*[not(self::row)]"/>
</xsl:stylesheet>
The template that operates in mode "group" should be substituted with template(s) that implement the exact wanted processing. In this case it copies the matched element -- but in the real application any wanted processing would go here.
You might be able to do a trick with a key to group each non-row element by its preceding row (if there is one), or its parent element if not:
<xsl:key name="elementsFollowingRow"
match="*[not(self::row)]"
use="generate-id( (.. | preceding-sibling::row )[last()])" />
and define a named template to put in a notRow if the current element has any associated elements according to the key
<xsl:template name="addNotRow">
<xsl:if test="key('elementsFollowingRow', generate-id())">
<notRow>
<xsl:copy-of select="key('elementsFollowingRow', generate-id())" />
</notRow>
</xsl:if>
</xsl:template>
Then in the template where you're matching the parent element (the one that contains all these row and non-row elements you can do
<xsl:call-template name="addNotRow" />
<xsl:for-each select="row">
<xsl:copy-of select="." />
<xsl:call-template name="addNotRow" />
</xsl:for-each>
The first call-template outside the for-each will deal with any notRow that is required before the first row, and the call inside the for-each will put in any notRow required after the row in question.
This isn't pretty, but it works.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="t">
<xsl:if test="row[1]/preceding-sibling::*">
<notRow>
<xsl:for-each select="row[1]/preceding-sibling::*" >
<xsl:copy />
</xsl:for-each>
</notRow>
</xsl:if>
<xsl:for-each select="row">
<xsl:copy-of select="."/>
<xsl:if test="following-sibling::row[1]/preceding-sibling::*[generate-id(preceding-sibling::row[1])=generate-id(current())]">
<notRow>
<xsl:for-each select="following-sibling::row[1]/preceding-sibling::*[generate-id(preceding-sibling::row[1])=generate-id(current())]">
<xsl:copy />
</xsl:for-each>
</notRow>
</xsl:if>
</xsl:for-each>
<xsl:if test="row[last()]/following-sibling::*">
<notRow>
<xsl:for-each select="row[last()]/following-sibling::*" >
<xsl:copy />
</xsl:for-each>
</notRow>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
On this XML source
<t>
<h />
<i />
<row id='v' />
<a />
<b />
<row id='w' />
<d />
<row id='x' />
<row id='y' />
<f />
<r />
<row id='z' />
<i />
</t>
it returns the correct result:
<notRow>
<h/>
<i/>
</notRow>
<row id="v"/>
<notRow>
<a/>
<b/>
</notRow>
<row id="w"/>
<notRow>
<d/>
</notRow>
<row id="x"/>
<row id="y"/>
<notRow>
<f/>
<r/>
</notRow>
<row id="z"/>
<notRow>
<i/>
</notRow>
but it does seem there should be something simpler.
I'm trying to recursively flatten / normalize a structure below with no luck.
<models>
<model name="AAA" root="true">
<items>
<item name="a"/>
<item name="b"/>
</items>
<submodels>
<submodel ref="BBB"/>
<submodel ref="CCC" />
</submodels>
</model>
<model name="BBB">
<items>
<item name="c"/>
<item name="d"/>
</items>
<submodels>
<submodel ref="CCC" />
</submodels>
</model>
<model name="CCC">
<item name="e" />
</model>
</models>
The expected result is the following:
/AAA
/AAA/a
/AAA/b
/AAA/BBB
/AAA/BBB/c
/AAA/BBB/d
/AAA/BBB/CCC
/AAA/BBB/CCC/e
/AAA/CCC
/AAA/CCC/e
I have tried using in a recursive way. But the main issue is that several models can refer to a single model. Eg. AAA -> CCC and BBB -> CCC.
This short and simple transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kmodelByName" match="model" use="#name"/>
<xsl:key name="ksubmodelByRef" match="submodel" use="#ref"/>
<xsl:template match="/*">
<xsl:apply-templates select="model[not(key('ksubmodelByRef', #name))]"/>
</xsl:template>
<xsl:template match="model|item">
<xsl:param name="pPath"/>
<xsl:value-of select="concat('
', $pPath, '/', #name)"/>
<xsl:apply-templates select="item|*/item|*/submodel">
<xsl:with-param name="pPath" select="concat($pPath, '/', #name)"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="submodel">
<xsl:param name="pPath"/>
<xsl:apply-templates select="key('kmodelByName', #ref)">
<xsl:with-param name="pPath" select="$pPath"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<models>
<model name="AAA" root="true">
<items>
<item name="a"/>
<item name="b"/>
</items>
<submodels>
<submodel ref="BBB"/>
<submodel ref="CCC" />
</submodels>
</model>
<model name="BBB">
<items>
<item name="c"/>
<item name="d"/>
</items>
<submodels>
<submodel ref="CCC" />
</submodels>
</model>
<model name="CCC">
<item name="e"/>
</model>
</models>
produces the wanted, correct result:
/AAA
/AAA/a
/AAA/b
/AAA/BBB
/AAA/BBB/c
/AAA/BBB/d
/AAA/BBB/CCC
/AAA/BBB/CCC/e
/AAA/CCC
/AAA/CCC/e
Explanation:
Proper use of keys makes the transformation short, easy to express and efficient.
Proper use of templates.
Proper use of parameter - passing to templates.
Firstly, I suspect you want to math the model that is marked as a root
<xsl:apply-templates select="model[#root='true']" />
Then you would have a template to match model elements, but one that takes a parameter that contains the current 'path' to the parent model
<xsl:template match="model">
<xsl:param name="path" />
You could output the full path like so
<xsl:variable name="newpath" select="concat($path, '/', #name)" />
<xsl:value-of select="concat($newpath, '
')" />
You could then output the items but passing in the new path as a parameter
<xsl:apply-templates select="items/item|item">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
To match the sub-models, ideally a key could be used
<xsl:key name="models" match="model" use="#name" />
Then you could match the sub-models like so
<xsl:apply-templates select="key('models', submodels/submodel/#ref)">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
This would recursively match the same model template.
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes"/>
<xsl:key name="models" match="model" use="#name" />
<xsl:template match="/models">
<xsl:apply-templates select="model[#root='true']" />
</xsl:template>
<xsl:template match="model">
<xsl:param name="path" />
<xsl:variable name="newpath" select="concat($path, '/', #name)" />
<xsl:value-of select="concat($newpath, '
')" />
<xsl:apply-templates select="items/item|item">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
<xsl:apply-templates select="key('models', submodels/submodel/#ref)">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item">
<xsl:param name="path" />
<xsl:value-of select="concat($path, '/', #name, '
')" />
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
/AAA
/AAA/a
/AAA/b
/AAA/BBB
/AAA/BBB/c
/AAA/BBB/d
/AAA/BBB/CCC
/AAA/BBB/CCC/e
/AAA/CCC
/AAA/CCC/e
I'm not sure what your rules of transformation are. This is a bit of a guess, but the following XSLT 1.0 style-sheet does transform your supplied input to the expected output.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="models/model[#root='true']">
<xsl:with-param name="base" select="''" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="model|item">
<xsl:param name="base" />
<xsl:variable name="new-base" select="concat($base,'/',#name)" />
<xsl:value-of select="concat($new-base,'
')" />
<xsl:apply-templates select="items/item | item | submodels/submodel/#ref">
<xsl:with-param name="base" select="$new-base" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#ref">
<xsl:param name="base" />
<xsl:apply-templates select="../../../../model[#name=current()]">
<xsl:with-param name="base" select="$base" />
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
There is probably an easier way to do this, but I was able to get the output you wanted with the following xslt:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" exclude-result-prefixes="exsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common">
<xsl:variable name="root" select="/models/model[#root = 'true']/#name"/>
<xsl:template match="models">
<xsl:for-each select="model">
<xsl:variable name="savedNode" select="."/>
<xsl:variable name="precedingValue">
<xsl:call-template name="preceding">
<xsl:with-param name="modelName" select="#name"/>
</xsl:call-template>
</xsl:variable>
<xsl:if test="not(exsl:node-set($precedingValue)/preceding)">
<xsl:call-template name="model">
<xsl:with-param name="node" select="$savedNode"/>
</xsl:call-template>
</xsl:if>
<xsl:for-each select="exsl:node-set($precedingValue)/preceding">
<xsl:sort select="position()" data-type="number" order="descending"/>
<xsl:variable name="precedingTmp" select="normalize-space(.)"/>
<xsl:variable name="normalizedPreceding" select="translate($precedingTmp, ' ', '')"/>
<xsl:call-template name="model">
<xsl:with-param name="node" select="$savedNode"/>
<xsl:with-param name="preceding" select="$normalizedPreceding"/>
</xsl:call-template>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template name="model">
<xsl:param name="node"/>
<xsl:param name="preceding"/>
\<xsl:value-of select="$preceding"/><xsl:value-of select="$node/#name"/>
<xsl:for-each select="$node/items/item">
\<xsl:value-of select="$preceding"/><xsl:value-of select="$node/#name"/>\<xsl:value-of select="#name"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="preceding">
<xsl:param name="modelName"/>
<xsl:for-each select="preceding::model">
<xsl:for-each select="submodels/submodel">
<xsl:choose>
<xsl:when test="contains(#ref, $modelName)">
<preceding>
<xsl:call-template name="preceding">
<xsl:with-param name="modelName" select="$modelName"></xsl:with-param>
</xsl:call-template>
<xsl:value-of select="../../#name"/>\
</preceding>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="items">
<xsl:for-each select="item">
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Input
<models>
<model name="AAA" root="true">
<items>
<item name="a"/>
<item name="b"/>
</items>
<submodels>
<submodel ref="BBB"/>
<submodel ref="CCC" />
</submodels>
</model>
<model name="BBB">
<items>
<item name="c"/>
<item name="d"/>
</items>
<submodels>
<submodel ref="CCC" />
</submodels>
</model>
<model name="CCC">
<items>
<item name="e"/>
</items>
</model>
</models>
Output
\AAA
\AAA\a
\AAA\b
\AAA\BBB
\AAA\BBB\c
\AAA\BBB\d
\AAA\BBB\CCC
\AAA\BBB\CCC\e
\AAA\CCC
\AAA\CCC\e
I hope it helps.