XSL Transformation only for certain element based on attributes - xslt

I have a XML file for which transformation rules should be applied for certain elements only based on its attribute value and the rest should be retained as it is.
<bigdata>
<data>
<Object class="QWE" Name="Country-1/State-1/QWE-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
<Object class="RTY" Name="Country-1/State-1/RTY-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
<Object class="UIO" Name="Country-1/State-1/UIO-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
<Object class="PAS" Name="Country-1/State-1/PAS-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
</data>
The above xml should be converted to below xml snippet where only xml element Object for which class equals QWE should be changed to POST.
ie only first element in must be changed based on its attribute.
Any advices on this would be gratefull
<bigdata>
<data>
<Object class="POST" Name="Country-1/State-1/POST-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
<Object class="RTY" Name="Country-1/State-1/RTY-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
<Object class="UIO" Name="Country-1/State-1/UIO-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
<Object class="PAS" Name="Country-1/State-1/PAS-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
</data>

Try this:
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Object/#class[. = 'QWE' ]">
<xsl:attribute name="class">
<xsl:value-of select="'POST'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Which will generate the following output:
<bigdata>
<data>
<Object class="POST" Name="Country-1/State-1/QWE-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
<Object class="RTY" Name="Country-1/State-1/RTY-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
<Object class="UIO" Name="Country-1/State-1/UIO-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
<Object class="PAS" Name="Country-1/State-1/PAS-1">
<p name="Map">20</p>
<p name="Required">0</p>
<p name="Combined">68</p>
<p name="State">0</p>
</Object>
</data>
</bigdata>
Update if the class value should be start with a string (QWE) and only this part should be replaced. Try this:
<xsl:template match="Object/#class[starts-with(., 'QWE') ]">
<xsl:attribute name="class">
<xsl:value-of select="'POST'"/>
<xsl:value-of select="substring-after(.,'QWE')"/>
</xsl:attribute>
</xsl:template>

Use following XSLT to transform above xml:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#class[parent::Object]">
<xsl:choose>
<xsl:when test=".='QWE'">
<xsl:attribute name="class">
<xsl:value-of select="'POST'"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="class">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
refer this for more info:
XSLT: How to change an attribute value during <xsl:copy>?

Related

XSLT Grouping's : Add parent to set of elements in child elements

Using for-each-group I'm trying to add parent node by the class value of each para element. I tried to apply the grouping but the result is not good, I'm not getting the desired output. I'm confused about using the grouping in this case. Is there any better approach in this case for adding the parent node?
Current XML:
<?xml version="1.0" encoding="utf-8" ?>
<section>
<h1>Some heading</h1>
<section>
<p>normal paragaraph</p>
<p class="list">list 1</p>
<p class="list">list 1</p>
<p>normal paragaraph</p>
<p class="list">list 2</p>
<p class="list">list 2</p>
</section>
<section> ... </section>
</section>
XSLT used:
<xsl:template match="section">
<xsl:for-each-group select="node()" group-by="if (#class='list') then 'list' else 'nolist'">
<xsl:for-each select="current-grouping-key()">
<xsl:choose>
<xsl:when test="current-grouping-key() = 'list'">
<list>
<xsl:apply-templates select="current-group()" />
</list>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:for-each-group>
</xsl:template>
Current Output:
<h1>Some heading</h1>
<p>normal paragaraph</p>
<p>normal paragaraph</p>
<list>
<p class="list">list 1</p>
<p class="list">list 1</p>
<p class="list">list 2</p>
<p class="list">list 2</p>
</list>
<p>normal paragaraph</p>
....
Expected Output:
<section>
<h1>Some heading</h1>
<section>
<p>normal paragaraph</p>
<list>
<p class="list">list 1</p>
<p class="list">list 1</p>
</list>
<p>normal paragaraph</p>
<list>
<p class="list">list 2</p>
<p class="list">list 2</p>
</list>
</section>
<section>...</section>
</section>
<xsl:template match="section">
<section>
<h1>Some Heading</h1>
<section>
<xsl:for-each-group select="section/p" group-by=".">
<xsl:choose>
<xsl:when test="current-grouping-key() != 'normal paragaraph'">
<p>normal paragaraph</p>
<list>
<xsl:for-each select="current-group()">
<p class="list"><xsl:value-of select="current-grouping-key()"/></p>
</xsl:for-each>
</list>
</xsl:when>
</xsl:choose>
</xsl:for-each-group>
</section>
</section>
</xsl:template>
Here is the answer I hope this helps someone, as commented by #marthin honnen, I tried with group-adjacent, got the desired output, this helped.
<xsl:template match="section">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent="if (#class='list') then 'list' else 'nolist'">
<xsl:for-each select="current-grouping-key()">
<xsl:choose>
<xsl:when test="current-grouping-key() = 'list'">
<list>
<xsl:apply-templates select="current-group()" />
</list>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>

How to sort data numbers with for-each group in XSLT

How to sort data numbers with xsl:for-each?
Input XML
<root>
<p content-type="Sta_index2"><bold>6000–6243</bold></p>
<p content-type="Sta_index2"><bold>5000–5158</bold></p>
<p content-type="Sta_index2"><bold>6068(e)</bold></p>
<p content-type="Sta_index2"><bold>6148</bold></p>
<p content-type="Sta_index2"><bold>6200–6206</bold></p>
<p content-type="Sta_index2"><bold>6203(b)</bold></p>
<p content-type="Sta_index2"><bold>480</bold></p>
<p content-type="Sta_index2"><bold>6500</bold></p>
<p content-type="Sta_index2"><bold>6450</bold></p>
<p content-type="Sta_index2"><bold>6500–6592</bold></p>
<p content-type="Sta_index2"><bold>6501(f)(1)</bold></p>
<p content-type="Sta_index2"><bold>6533.5</bold></p>
<p content-type="Sta_index2"><bold>10018.15</bold></p>
<p content-type="Sta_index2"><bold>10018.14</bold></p>
</root>
XSLT
<xsl:template match="root">
<xsl:copy>
<xsl:for-each select="p">
<xsl:sort select="number(bold)" data-type="number" order="ascending"/>
<xsl:text>
</xsl:text>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
Expected Output
<root>
<p content-type="Sta_index2"><bold>480</bold></p>
<p content-type="Sta_index2"><bold>5000–5158</bold></p>
<p content-type="Sta_index2"><bold>6000–6243</bold></p>
<p content-type="Sta_index2"><bold>6068(e)</bold></p>
<p content-type="Sta_index2"><bold>6148</bold></p>
<p content-type="Sta_index2"><bold>6200–6206</bold></p>
<p content-type="Sta_index2"><bold>6203(b)</bold></p>
<p content-type="Sta_index2"><bold>6450</bold></p>
<p content-type="Sta_index2"><bold>6500</bold></p>
<p content-type="Sta_index2"><bold>6500–6592</bold></p>
<p content-type="Sta_index2"><bold>6501(f)(1)</bold></p>
<p content-type="Sta_index2"><bold>6533.5</bold></p>
<p content-type="Sta_index2"><bold>10018.14</bold></p>
<p content-type="Sta_index2"><bold>10018.15</bold></p>
</root>
CODE: https://xsltfiddle.liberty-development.net/3NSTbfj/29
As Sebastien has indicated in a comment, you can try to strip anything after the decimal values you want to use for sorting:
<xsl:template match="root">
<xsl:copy>
<xsl:perform-sort select="p">
<xsl:sort select="bold => replace('[^0-9.].*$', '') => xs:decimal()"/>
</xsl:perform-sort>
</xsl:copy>
</xsl:template>
https://xsltfiddle.liberty-development.net/3NSTbfj/30

Ordering nodes in XSL Transformation

Source XML:
<?xml version="1.0" encoding="UTF-8"?>
<BigData version="2.1" xmlns="bank.xsd">
<InsideData type="plan" name="testBANK" id="10">
<header>
<log dateTime="2013-07-27T15:52:30"/>
</header>
<object class="BANK" distName="CITY-1/ABC-1/BANK-1" operation="create" timeStamp="2013-07-27T15:48:20"/>
<object class="BranchItemPeriod" distName="CITY-1/ABC-1/BANK-1/Branch-1/BranchItem-1/BranchItemPeriod-1" operation="create" timeStamp="2013-07-27T15:51:25">
<p name="Week">0</p>
<p name="interval">10</p>
</object>
<object class="BranchItemPeriod" distName="CITY-1/ABC-1/BANK-1/Branch-1/BranchItem-2/BranchItemPeriod-2" operation="update" timeStamp="2013-07-27T15:51:25">
<p name="Week">0</p>
<p name="interval">10</p>
</object>
<object class="Branch" distName="CITY-1/ABC-1/BANK-1/Branch-1" operation="create" timeStamp="2013-07-27T15:48:31"/>
<object class="BranchItem" distName="CITY-1/ABC-1/BANK-1/Branch-1/BranchItem-1" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="BranchItem" distName="CITY-1/ABC-1/BANK-1/Branch-1/BranchItem-2" operation="update" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="Sleep" distName="CITY-1/ABC-1/Sleep-1" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="Dance" distName="CITY-1/ABC-1/Dance-5" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
</InsideData>
</BigData>
Transformation XSL :
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:x="bank.xsd" exclude-result-prefixes="x">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="x:object[#class = 'BANK' ]">
</xsl:template>
<xsl:template match="x:object[#class = 'Branch' ]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="distName">
<xsl:value-of select="concat(substring-before( #distName,'BANK-1/' ), substring-after( #distName, 'BANK-1/'))"/>
</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="x:object[#class = 'BranchItem' ]">
<xsl:variable name="branchItem" select="."/>
<xsl:choose>
<xsl:when test="$branchItem/#operation='update' and not(contains($branchItem/#distName, 'JOBS_CREATED_USING_NE_LOCAL_UI'))">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="operation">delete</xsl:attribute>
<xsl:attribute name="distName">
<xsl:value-of select="concat(substring-before( #distName,'BANK-1/' ), substring-after( #distName, 'BANK-1/'))"/>
</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="operation">create</xsl:attribute>
<xsl:attribute name="distName">
<xsl:value-of select="concat(substring-before( #distName,'BANK-1/' ), substring-after( #distName, 'BANK-1/'))"/>
</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="distName">
<xsl:value-of select="concat(substring-before( #distName,'BANK-1/' ), substring-after( #distName, 'BANK-1/'))"/>
</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="x:object[#class = 'BranchItemPeriod' ]">
<xsl:variable name="branchPeiod" select="."/>
<xsl:choose>
<xsl:when test="$branchPeiod/#operation='update' and not(contains($branchPeiod/#distName, 'JOBS_CREATED_USING_NE_LOCAL_UI'))">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="operation">create</xsl:attribute>
<xsl:attribute name="distName">
<xsl:value-of select="concat(substring-before( #distName,'BANK-1/' ), substring-after( #distName, 'BANK-1/'))"/>
</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="distName">
<xsl:value-of select="concat(substring-before( #distName,'BANK-1/' ), substring-after( #distName, 'BANK-1/'))"/>
</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output XML :
<?xml version="1.0" encoding="UTF-8"?>
<BigData xmlns="bank.xsd" version="2.1">
<InsideData type="plan" name="testBANK" id="10">
<header>
<log dateTime="2013-07-27T15:52:30"/>
</header>
<object class="BranchItemPeriod" distName="CITY-1/ABC-1/Branch-1/BranchItem-1/BranchItemPeriod-1" operation="create" timeStamp="2013-07-27T15:51:25">
<p name="Week">0</p>
<p name="interval">10</p>
</object>
<object class="BranchItemPeriod" distName="CITY-1/ABC-1/Branch-1/BranchItem-2/BranchItemPeriod-2" operation="create" timeStamp="2013-07-27T15:51:25">
<p name="Week">0</p>
<p name="interval">10</p>
</object>
<object class="Branch" distName="CITY-1/ABC-1/Branch-1" operation="create" timeStamp="2013-07-27T15:48:31"/>
<object class="BranchItem" distName="CITY-1/ABC-1/Branch-1/BranchItem-1" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="BranchItem" distName="CITY-1/ABC-1/Branch-1/BranchItem-2" operation="delete" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="BranchItem" distName="CITY-1/ABC-1/Branch-1/BranchItem-2" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="Sleep" distName="CITY-1/ABC-1/Sleep-1" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="Dance" distName="CITY-1/ABC-1/Dance-5" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
</InsideData>
</BigData>
Desired OUTPUT XML:
<?xml version="1.0" encoding="UTF-8"?>
<BigData xmlns="bank.xsd" version="2.1">
<InsideData type="plan" name="testBANK" id="10">
<header>
<log dateTime="2013-07-27T15:52:30"/>
</header>
<object class="Branch" distName="CITY-1/ABC-1/Branch-1" operation="create" timeStamp="2013-07-27T15:48:31"/>
<object class="BranchItem" distName="CITY-1/ABC-1/Branch-1/BranchItem-1" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="BranchItemPeriod" distName="CITY-1/ABC-1/Branch-1/BranchItem-1/BranchItemPeriod-1" operation="create" timeStamp="2013-07-27T15:51:25">
<p name="Week">0</p>
<p name="interval">10</p>
</object>
<object class="BranchItem" distName="CITY-1/ABC-1/Branch-1/BranchItem-2" operation="delete" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="BranchItem" distName="CITY-1/ABC-1/Branch-1/BranchItem-2" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="BranchItemPeriod" distName="CITY-1/ABC-1/Branch-1/BranchItem-2/BranchItemPeriod-2" operation="create" timeStamp="2013-07-27T15:51:25">
<p name="Week">0</p>
<p name="interval">10</p>
</object>
<object class="Sleep" distName="CITY-1/ABC-1/Sleep-1" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
<object class="Dance" distName="CITY-1/ABC-1/Dance-5" operation="create" timeStamp="2013-07-27T15:50:42">
<p name="openDate">2013-07-27</p>
<p name="closeDate">2013-07-29</p>
</object>
</InsideData>
</BigData>
I could achieve most of desired output except for few...
I want the output to be sorted based on distName attribute of object nodes.
I want the sort to happen only to certain child nodes with class names as Branch , BranchItem , BranchItemPeriod.
Here i try for update with delete and create operation, so i want also to maintain the order of delete and create too which i do in present transformation logic or else can it so happen that i sort all first based on above criteria and apply the other transformation logic.
Any suggestion or help is highly appreciated.
I think what you need here is a template that matches the InsideData element, where you can then select the child object elements in the order you require.
You would first start by outputting the non-"object" elements, assuming these always come before the object elements.
<xsl:apply-templates select="#*|node()[not(self::x:object)]"/>
Then you would select the object elements with the class attribute you desire, sorting in the order you require too:
<xsl:apply-templates select="x:object[#class='Branch' or #class='BranchItem' or #class='BranchItemPeriod']">
<xsl:sort select="#distName"/>
</xsl:apply-templates>
Finally, you would output the object elements which have the the class attributes.
<xsl:apply-templates select="x:object[not(#class='Branch' or #class='BranchItem' or #class='BranchItemPeriod')]"/>
Try adding this template to your XSLT to see how you get on:
<xsl:template match="x:InsideData">
<xsl:copy>
<xsl:apply-templates select="#*|node()[not(self::x:object)]"/>
<xsl:apply-templates select="x:object[#class='Branch' or #class='BranchItem' or #class='BranchItemPeriod']">
<xsl:sort select="#distName"/>
</xsl:apply-templates>
<xsl:apply-templates select="x:object[not(#class='Branch' or #class='BranchItem' or #class='BranchItemPeriod')]"/>
</xsl:copy>
</xsl:template>

creating a wrapper element for multiple elements with different names and different #class attribute values

I'm have the following flat XML-Structure
<div class="section-level-1">
<!-- other elements -->
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
<!-- other elements -->
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<misc-element>...</misc-element>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
</div>
The order of the these elements is always the same (para -> figure-caption-german -> figure-caption-english), however I can't exclude that it will be interrupted by other elements (here the misc-element).
I want to wrap these three elements inside a single element
<div class="section-level-1">
<!-- other elements -->
<div class="figure">
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
</div>
<!-- other elements -->
<div class="figure">
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
</div>
</div>
The interrupting element(s) don't need to be preserved and can be deleted.
What I have so far
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<!-- Html Ninja Pattern -->
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="* | #* | text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="body//#*">
<xsl:attribute name="{name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<!-- Modify certain elements -->
<xsl:template match="" priority="1">
<!-- do something -->
</xsl:template>
As a basic pattern I draw on the "Html Ninja Technique" (http://getsymphony.com/learn/articles/view/html-ninja-technique/) since it allows me to tackle only those particular elements I need to transform while sending all other elements to the output tree unchanged.
So far everything worked fine, but now I really seemed to hit a road block. I'm not even sure I can accomplish the desired task by relying on the "Html Ninja Technique".
Any help or indication would be highly appreciated.
Best regards and thank you, Matthias Einbrodt
It's a little involved, but I think this should do it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*" name="Copy">
<xsl:element name="{name()}">
<xsl:apply-templates select="* | #* | text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="div[starts-with(#class, 'section-level')]">
<xsl:copy>
<xsl:apply-templates select="#*" />
<!-- Apply templates to paras and anything with no preceding sibling
or with a figure-caption-english preceding sibling-->
<xsl:apply-templates select="p[#class = 'para'] |
*[not(preceding-sibling::*) or
preceding-sibling::*[1][self::p]
[#class = 'figure-caption-english']
]"
mode="iter"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[#class = 'para']" mode="iter">
<div class="figure">
<xsl:call-template name="Copy" />
<!-- Apply templates to the next english and german figure captions -->
<xsl:apply-templates
select="following-sibling::p[#class = 'figure-caption-german'][1] |
following-sibling::p[#class = 'figure-caption-english'][1]" />
</div>
</xsl:template>
<xsl:template match="*" mode="iter">
<xsl:call-template name="Copy" />
<xsl:apply-templates
select="following-sibling::*[1]
[not(self::p[#class = 'para'])]"
mode="iter"/>
</xsl:template>
</xsl:stylesheet>
When applied to this sample data:
<div class="section-level-1">
<!-- other elements -->
<div>hello</div>
<div>hello</div>
<div>hello</div>
<div>hello</div>
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
<!-- other elements -->
<div>hello</div>
<div>hello</div>
<div>hello</div>
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<misc-element>...</misc-element>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
<div>hello</div>
<div>hello</div>
<div>hello</div>
</div>
It produces:
<div class="section-level-1">
<div>hello</div>
<div>hello</div>
<div>hello</div>
<div>hello</div>
<div class="figure">
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
</div>
<div>hello</div>
<div>hello</div>
<div>hello</div>
<div class="figure">
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
</div>
<div>hello</div>
<div>hello</div>
<div>hello</div>
</div>
Here's another approach. This one does involve iterating over the child elements of the div, but also makes use of an xsl:key to group the relevant p elements.
Firstly, define a key to group your 'figure-caption' elements by the first most preceding 'para' element:
<xsl:key name="para"
match="p[starts-with(#class, 'figure-caption')]"
use="generate-id(preceding-sibling::p[#class='para'][1])"/>
Then, you start off by matching the div element, and selecting the first element
<xsl:template match="div">
<div>
<xsl:apply-templates select="node()[1]" mode="iterate"/>
</div>
</xsl:template>
The mode iterate is used to indicate the templates that will recursively match their following sibling. You would need firstly need a template to match the 'para' element, where you can use the key to group the relevant elements
<xsl:template match="p[#class='para']" mode="iterate">
<div class="figure">
<xsl:apply-templates select=".|key('para', generate-id())" mode="group"/>
</div>
(The mode group here will be used to indicate that for the grouped elements the matching template will just output them, but not carry on processing at the next sibling. You could use xsl:copy-of here alternatively)
And within this template, you then carry on the iteration by selecting the node after the last element in the group
<xsl:apply-templates
select="key('para', generate-id())[last()]/following-sibling::node()[1]" mode="iterate"/>
Other elements within the iteration can then be matched with a more generic template to copy them, and continue at the next sibling
<xsl:template match="node()" mode="iterate">
<xsl:call-template name="identity"/>
<xsl:apply-templates select="following-sibling::node()[1]" mode="iterate"/>
</xsl:template>
identity here will call the identity template.
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="para" match="p[starts-with(#class, 'figure-caption')]" use="generate-id(preceding-sibling::p[#class='para'][1])"/>
<xsl:template match="div">
<div>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="node()[1]" mode="iterate"/>
</div>
</xsl:template>
<xsl:template match="p[#class='para']" mode="iterate">
<div class="figure">
<xsl:apply-templates select=".|key('para', generate-id())" mode="group"/>
</div>
<xsl:apply-templates select="key('para', generate-id())[last()]/following-sibling::node()[1]" mode="iterate"/>
</xsl:template>
<xsl:template match="node()" mode="group">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="node()" mode="iterate">
<xsl:call-template name="identity"/>
<xsl:apply-templates select="following-sibling::node()[1]" mode="iterate"/>
</xsl:template>
<xsl:template match="#*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<div class="section-level-1">
<!-- other elements -->
<div class="figure">
<p class="para">
<img src="..." alt="..." title="..."/>
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..."/>
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..."/>
</p>
</div>
<!-- other elements -->
<div class="figure">
<p class="para">
<img src="..." alt="..." title="..."/>
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..."/>
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..."/>
</p>
</div>
</div>
One advantage of this approach is that you can throw other languages, other than english and german into the mix, and it should still work, and the order of the languages would not matter either. (Of course, you might want to ignore other languages, in which case it wouldn't work!)
A simple XSLT 2.0 solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pClasses" select=
"'para', 'figure-caption-german', 'figure-caption-english'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each-group select="p[#class=$pClasses]"
group-starting-with="p[#class eq $pClasses[1]]">
<div class="figure">
<xsl:apply-templates select="current-group()"/>
</div>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<div class="section-level-1">
<!-- other elements -->
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
<!-- other elements -->
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<misc-element>...</misc-element>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
</div>
the wanted, correct result is produced:
<div class="section-level-1">
<div class="figure">
<p class="para">
<img src="..." alt="..." title="..."/>
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..."/>
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..."/>
</p>
</div>
<div class="figure">
<p class="para">
<img src="..." alt="..." title="..."/>
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..."/>
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..."/>
</p>
</div>
</div>
Based upon the solution of JLRishe I got inspired to implement a multi-pass solution to the problem using different template-modes.
Given the following flat XML-Structure
<div class="section-level-1">
<!-- other elements -->
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
<!-- other elements -->
<p class="para">
<img src="..." alt="..." title="..." />
</p>
<p class="figure-caption-german">
<img src="..." alt="..." title="..." />
</p>
<misc-element>...</misc-element>
<p class="figure-caption-english">
<img src="..." alt="..." title="..." />
</p>
</div>
I applied the following approach.
<xsl:template match="/">
<xsl:variable name="pass0">
<xsl:apply-templates mode="pass0" />
</xsl:variable>
<xsl:variable name="pass1">
<xsl:for-each select="$pass0">
<xsl:apply-templates mode="pass1" />
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="$pass1" />
</xsl:template>
<!--###############
### Pass 0 ####
###############-->
<xsl:template match="*" mode="pass0">
<xsl:element name="{name()}">
<xsl:apply-templates select="* | #* | text()" mode="pass0"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*" mode="pass0">
<xsl:attribute name="{name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<!-- wraps figures and their associated captions within <div class="figure"> element -->
<xsl:template match="p[#class = 'para'][img]" mode="pass0" priority="1">
<div class="figure">
<xsl:copy-of select="./img" />
<xsl:apply-templates
select="following-sibling::p[#class = 'figure-caption-german'][1] |
following-sibling::p[#class = 'figure-caption-english'][1]"
mode="fig- captions-pass0" />
</div>
</xsl:template>
<xsl:template match="*" mode="fig-captions-pass0" priority="1">
<xsl:copy-of select="." />
</xsl:template>
<!--###############
### Pass 1 ####
###############-->
<xsl:template match="*" mode="pass1">
<xsl:element name="{name()}">
<xsl:apply-templates select="* | #* | text()" mode="pass1"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*" mode="pass1">
<xsl:attribute name="{name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<!-- removes all elements with figure captions that don't reside within <div class="figure"> element and all other unnecessary elements -->
<xsl:template match="
p[#class = 'figure-caption-german'][not(parent::div[#class = 'figure'])] |
p[#class = 'figure-caption-english'][not(parent::div[#class = 'figure'])] |
misc-element"
mode="pass1" priority="1" />
As a result I get the desired output
<div class="section-level-1">
<p class="para">normal paragraph etc.</p>
<p class="para">normal paragraph etc.</p>
<p class="para">normal paragraph etc.</p>
<div class="figure">
<img src="..." alt="..." title="..."></img>
<p class="figure-caption-german">
figure caption in german
</p>
<p class="figure-caption-english">
figure caption in english
</p>
</div>
<p class="para">normal paragraph etc.</p>
<p class="para">normal paragraph etc.</p>
<p class="para">normal paragraph etc.</p>
<div class="figure">
<img src="..." alt="..." title="..."></img>
<p class="figure-caption-german">
figure caption in german
</p>
<p class="figure-caption-english">
figure caption in english
</p>
</div>
</div>

Apply a template between two specific nodes in XSLT

I had a problem in xsl:for-each-group and it was very nicely solved in here. Now I have some other problem. I have like this as input.
<?xml version="1.0" encoding="UTF-8"?>
<body>
<p name ="section">this is section</p>
<p name="h-title" other="main">Introduction</p>
<p name="h1-title " other="other-h1">XSLT and XQuery</p>
<p name="h2-title" other=" other-h2">XSLT</p>
<p name="">
<p1 name="bold"> XSLT is used to write stylesheets.</p1>
</p>
<p name="h2-title " name="other-h2">XQuery</p>
<p name="">
<p1 name="bold"> XQuery is used to query XML databases.</p1>
</p>
<p name="h3-title" name="other-h3">XQuery and stylesheets</p>
<p name="">
<p1 name="bold"> XQuery is used to query XML databases.</p1>
</p>
<p name ="section">this is section</p>
<p name="h1-title " other="other-h1">XSLT and XQuery</p>
<p name="h2-title " other=" other-h2">XSLT</p>
<p name ="section">this is section</section>
<p name="h1-title " other="other-h1">XSLT and XQuery</p>
<p name="h2-title " other=" other-h2">XSLT</p>
</body>
Now my wanted output is this
<?xml version="1.0" encoding="UTF-8"?>
<body>
<p name="h-title " other="main">Introduction</p>
<section>
<p name ="section">this is section</p>
<h1>
<p name="h1-title " other="other-h1"> XSLT and XQuery </p>
<h2>
<p name="h2-title " other="other-h2">XSLT</p>
<p name="">
<p1 name="bold">XSLT is used to write stylesheets.
</p1>
</p>
</h2>
<h2>
<p name="h2-title " other="other-h2"> XQuery is used to query XMLdatabases
</p>
<p name="">
<p name="bold"> XQuery is used to query XML databases.</p>
</p>
<h3>
<p name="h3-title " name="other-h3">XQuery and stylesheets</p>
<p name="">
<p1 name="bold"> XQuery is used to query XML databases.</p1>
</p>
</h3>
</h2>
</h1>
</section>
<section>
<p name ="section">this is section</p>
<h1>
<p name="h1-title " other="other-h1">XSLT and XQuery</p>
<h2>
<p name="h2"-title other=" other-h2">XSLT</p>
</h2>
</h1>
</section>
<section>
<p name ="section">this is section</p>
<h1>
<p name="h1-title " other="other-h1">XSLT and XQuery</p>
<h2>
<p name="h2"-title other=" other-h2">XSLT</p>
</h2>
</h1>
</section>
</body>
My used stylesheet(not working properly)
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:param name="prefix" as="xs:string" select="'h'"/>
<xsl:param name="suffix" as="xs:string" select="'-title'"/>
<xsl:output method="html" version="4.0" indent="yes"/>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="items" as="node()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$items" group-starting-with="p[#name = concat($prefix,$level, $suffix)]">
<xsl:choose>
<xsl:when test="not(self::p[#name = concat($prefix, $level, $suffix)])">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="h{$level}">
<xsl:apply-templates select="."/>
<xsl:sequence select="mf:group(current-group() except ., $level + 1)"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body" name ="myTemplate">
<xsl:copy>
<xsl:sequence select="mf:group(*, 1)"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body[#name='section']">
<section>
<xsl:call-template name="myTemplate"/>
</section>
</xsl:template>
</xsl:stylesheet>
But this is not correct. Things like <p name ="section">this is section</p> appear widely. not only that, there are few others like this. If someone please show me how to handle with section I will be able to do that for others too. Please tell me how to do this correctly.
ADDED
<body>
<intro>
<p></p>
<p></p>
</intro>
<section>
<para>
</para>
<h1></h2>
<h2></h2>
</section>
<section>
<para>
</para>
<h1></h2>
<h2></h2>
</section>
<sumary>
</summary>
</body>
what I did
<xsl:for-each-group select="*" group-starting-with="p[#name = 'intro']">
<intro>
<xsl:apply-templates select="current()"/>
</intro>
</xsl:for-each-group>
I think you mainly want another grouping step; the stylesheet
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:param name="prefix" as="xs:string" select="'h'"/>
<xsl:param name="suffix" as="xs:string" select="'-title'"/>
<xsl:output method="html" version="4.0" indent="yes"/>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="items" as="node()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$items" group-starting-with="p[#name = concat($prefix, $level, $suffix)]">
<xsl:choose>
<xsl:when test="not(self::p[#name = concat($prefix, $level, $suffix)])">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="h{$level}">
<xsl:apply-templates select="."/>
<xsl:sequence select="mf:group(current-group() except ., $level + 1)"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body">
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="p[#name = 'section']">
<section>
<xsl:sequence select="mf:group(current-group(), 1)"/>
</section>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
transforms the corrected input
<?xml version="1.0" encoding="UTF-8"?>
<body>
<p name ="section">this is section</p>
<p name="h-title" other="main">Introduction</p>
<p name="h1-title" other="other-h1">XSLT and XQuery</p>
<p name="h2-title" other=" other-h2">XSLT</p>
<p name="">
<p1 name="bold"> XSLT is used to write stylesheets.</p1>
</p>
<p name="h2-title" other="other-h2">XQuery</p>
<p name="">
<p1 name="bold"> XQuery is used to query XML databases.</p1>
</p>
<p name="h3-title" other="other-h3">XQuery and stylesheets</p>
<p name="">
<p1 name="bold"> XQuery is used to query XML databases.</p1>
</p>
<p name ="section">this is section</p>
<p name="h1-title" other="other-h1">XSLT and XQuery</p>
<p name="h2-title" other=" other-h2">XSLT</p>
<p name ="section">this is section</p>
<p name="h1-title" other="other-h1">XSLT and XQuery</p>
<p name="h2-title" other=" other-h2">XSLT</p>
</body>
into the result
<body>
<section>
<p name="section">this is section</p>
<p name="h-title" other="main">Introduction</p>
<h1>
<p name="h1-title" other="other-h1">XSLT and XQuery</p>
<h2>
<p name="h2-title" other=" other-h2">XSLT</p>
<p name="">
<p1 name="bold"> XSLT is used to write stylesheets.</p1>
</p>
</h2>
<h2>
<p name="h2-title" other="other-h2">XQuery</p>
<p name="">
<p1 name="bold"> XQuery is used to query XML databases.</p1>
</p>
<h3>
<p name="h3-title" other="other-h3">XQuery and stylesheets</p>
<p name="">
<p1 name="bold"> XQuery is used to query XML databases.</p1>
</p>
</h3>
</h2>
</h1>
</section>
<section>
<p name="section">this is section</p>
<h1>
<p name="h1-title" other="other-h1">XSLT and XQuery</p>
<h2>
<p name="h2-title" other=" other-h2">XSLT</p>
</h2>
</h1>
</section>
<section>
<p name="section">this is section</p>
<h1>
<p name="h1-title" other="other-h1">XSLT and XQuery</p>
<h2>
<p name="h2-title" other=" other-h2">XSLT</p>
</h2>
</h1>
</section>
</body>
That has the structure you posted I think, with the exception of the <p name="h-title" other="main">Introduction</p> element being inside a section while your posted example moved it to the top. I am not sure what are the rules for doing that so I have not tried to implement that. Please clarify whether you simply want to move that single element to the top and not apply the grouping to it or whether there are more complex rules to exempt certain elements.
[edit]In case you simply want to move all p[#name = 't-title'] to the top and not group them then the following adaption of above stylesheet should do the job:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:param name="prefix" as="xs:string" select="'h'"/>
<xsl:param name="suffix" as="xs:string" select="'-title'"/>
<xsl:output method="html" version="4.0" indent="yes"/>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="items" as="node()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$items" group-starting-with="p[#name = concat($prefix, $level, $suffix)]">
<xsl:choose>
<xsl:when test="not(self::p[#name = concat($prefix, $level, $suffix)])">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="h{$level}">
<xsl:apply-templates select="."/>
<xsl:sequence select="mf:group(current-group() except ., $level + 1)"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body">
<xsl:copy>
<xsl:apply-templates select="p[#name = 'h-title']"/>
<xsl:for-each-group select="* except p[#name = 'h-title']" group-starting-with="p[#name = 'section']">
<section>
<xsl:sequence select="mf:group(current-group(), 1)"/>
</section>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>