I have this flat xml. i need to group the contents on h1/title and copy all following para's until next h1/title pattern if exists else add empty para.
Source XML:
<Element>
<h1>
<title>Name1</title>
</h1>
<h1>
<title>Name2</title>
</h1>
<para>Test1</para>
<para>Test2</para>
<h1>
<title>Name3</title>
</h1>
<para>Test3</para>
<para>Test4</para>
</Element>
I want the output like below.
<Element>
<group>
<h1>
<title>Name1</title>
</h1>
<para> </para>
</group>
<group>
<h1>
<title>Name2</title>
</h1>
<para>Test1</para>
<para>Test2</para>
</group>
<group>
<h1>
<title>Name3</title>
</h1>
<para>Test3</para>
<para>Test4</para>
</group>
</Element>
so far I have tried following template, it does not copy following para's.
<xsl:template match="h1">
<group>
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="h1">
<xsl:choose>
<xsl:when test="self::h1">
<group>
<xsl:apply-templates select="current-group()"/>
<xsl:apply-templates select="following-sibling::para[not(following::h1)]"/>
</group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</group>
</xsl:template>
I would suggest to group the children of the Element element and then of course inside of the for-each-group you can simply check whether there is no second group item to add the empty para:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Element">
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="h1">
<xsl:choose>
<xsl:when test="self::h1">
<group>
<xsl:apply-templates select="current-group()"/>
<xsl:if test="not(current-group()[2])">
<para/>
</xsl:if>
</group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:transform>
Online at http://xsltransform.net/naZXpX7.
Related
I would like to group nodes which following-sibling
<doc>
<a>blabla</a>
<b>blabla</b>
<c>text C</c>
<d>text D</d>
<a>bla</a>
<d>text D</d>
<a>bla</a>
</doc>
output desired :
<doc>
<a>blabla</a>
<b>blabla</b>
<c>text C</c>
<GROUP>
<d>text D</d>
<a>bla</a>
<d>text D</d>
<a>bla</a>
</GROUP>
</doc>
I've tried :
<xsl:template match="doc">
<xsl:copy>
<xsl:for-each-group select="*"
group-adjacent="boolean(self::a | self::d)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:element name="GROUP">
<xsl:value-of select="current-group()"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
How can i do to exclude a not preceding-sibling::*[1]=d ?
Thanks a lot.
I've found :
<xsl:template match="doc">
<xsl:copy>
<xsl:for-each-group select="*"
group-adjacent="self::a or self::d
[following-sibling::*[1][self::a or self::d]
or
(preceding-sibling::*[1][self::a or self::d])
]">
<xsl:choose>
<xsl:when test="current-grouping-key() and current-group()[2]">
<xsl:element name="GROUP">
<xsl:copy-of select="current-group()"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
I have an given XML and I want to convert it in new xml and want to aggregate nodes based on status and orderId.
<?xml version="1.0" encoding="utf-8"?>
<OrderStatusUpdate>
<OrderStatusEvents>
<OrderStatusEvent>
<StoreCode>store1</StoreCode>
<OrderId>Order1</OrderId>
<ItemId>Item1</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:37:05</OrderStatusEventTimeStamp>
<StatusName>Cancelled</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>Store1</StoreCode>
<OrderId>Order1</OrderId>
<ItemId>Item2</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:48:35</OrderStatusEventTimeStamp>
<StatusName>Cancelled</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>Store1</StoreCode>
<OrderId>Order1</OrderId>
<ItemId>Item3</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:48:35</OrderStatusEventTimeStamp>
<StatusName>Shipped</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>Store1</StoreCode>
<OrderId>Order2</OrderId>
<ItemId>Item1</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:48:35</OrderStatusEventTimeStamp>
<StatusName>Cancelled</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
</OrderStatusEvents>
</OrderStatusUpdate>
And I want an output like this. Here I am grouping elements based on status and orderId.
<Orders>
<group name="CANCELLED">
<STATUS ID="CANCELLED" DESCRIPTION="Goods Cancelled">
<ORDER ID="Order1">
<ORDER_ITEM item="item1" />
<ORDER_ITEM item="item2" />
</ORDER>
</STATUS>
<STATUS ID="CANCELLED" DESCRIPTION="Goods Cancelled">
<ORDER ID="Order2">
<ORDER_ITEM item="item1" />
</ORDER>
</STATUS>
</group>
<group name="SHIPPED">
<STATUS ID="SHIPPED" DESCRIPTION="Goods SHIPPED">
<ORDER ID="Order1">
<ORDER_ITEM item="item3" />
</ORDER>
</STATUS>
<group>
</Orders>
I am using the following xslt and it is working fine. Is there any way to improve this.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="OrderStatusUpdate/OrderStatusEvents">
<Orders>
<xsl:for-each-group select="OrderStatusEvent" group-by="OrderStatusDetails/OrderStatusDetail/StatusName">
<xsl:variable name="group-name" select="current-grouping-key()" />
<group name="{current-grouping-key()}">
<xsl:for-each-group select="current-group()" group-by="OrderId">
<xsl:variable name="order-id" select="current-grouping-key()" />
<xsl:element name="STATUS">
<xsl:attribute name="ID"><xsl:value-of select="$group-name" /></xsl:attribute>
<xsl:attribute name="DESCRIPTION">Goods <xsl:value-of select="$group-name" /></xsl:attribute>
<xsl:element name="ORDER">
<xsl:attribute name="ID"><xsl:value-of select="$order-id" /></xsl:attribute>
<xsl:for-each select="current-group()">
<xsl:if test="$group-name = 'Shipped'">
<xsl:call-template name="Shipped">
<xsl:with-param name="nodes">
<xsl:copy-of select="." />
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:if test="$group-name = 'Cancelled'">
<xsl:call-template name="Cancelled">
<xsl:with-param name="nodes">
<xsl:copy-of select="." />
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:for-each-group>
</group>
</xsl:for-each-group>
</Orders>
</xsl:template>
<xsl:template name="Shipped">
<xsl:param name="nodes">
</xsl:param>
<xsl:element name="ORDER_ITEM">
<xsl:attribute name="ID"><xsl:value-of select="$nodes/OrderStatusEvent/ItemId" /></xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template name="Cancelled">
<xsl:param name="nodes"></xsl:param>
<xsl:element name="ORDER_ITEM">
<xsl:attribute name="ID"><xsl:value-of select="$nodes/OrderStatusEvent/ItemId" /></xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It is easier to use literal result elements and attribute value templates as long as you don't need to compute element or attribute names at run-time and I don't think you need the two templates and call-template, it suffices to use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="OrderStatusUpdate/OrderStatusEvents">
<Orders>
<xsl:for-each-group select="OrderStatusEvent" group-by="OrderStatusDetails/OrderStatusDetail/StatusName">
<xsl:variable name="group-name" select="current-grouping-key()" />
<group name="{current-grouping-key()}">
<xsl:for-each-group select="current-group()" group-by="OrderId">
<xsl:variable name="order-id" select="current-grouping-key()" />
<STATUS ID="{$group-name}" DESCRIPTION="Goods {$group-name}">
<ORDER ID="{$order-id}">
<xsl:apply-templates select="current-group()"/>
</ORDER>
</STATUS>
</xsl:for-each-group>
</group>
</xsl:for-each-group>
</Orders>
</xsl:template>
<xsl:template match="OrderStatusEvent">
<ORDER_ITEM ID="{ItemId}"/>
</xsl:template>
</xsl:stylesheet>
This is input xml input.xml
<root>
<bodytext>
<remotelink refptid="HKBL1.0001.lohk.CAP65">some text</remotelink>
<remotelink refptid="HKBL1.0001.lohk.CAP199999">some text</remotelink>
</bodytext>
</root>
This is Prop.xml
<?xml version="1.0" encoding="utf-8"?>
<properties>
<code dpsi="0BZG" docid="asdww">HKBL1.0001.lohk.CAP65</code>
<code dpsi="0BZH" docid="navin">HKBL1.0002.aohk.CAP383</code>
<code no="3">345</code>
</properties>
This is desired output
<root>
<bodytext>
<remotelink refptid="HKBL1.0001.lohk.CAP65" dpsi="0BZG" docid="asdww">some text</remotelink>
<remotelink refptid="HKBL1.0001.lohk.CAP199999">some text</remotelink>
</bodytext>
</root>
IF prop.xml code/text matches remotelink/#refptid than copy attribute of prop.xml to remotelink otherwise no changes in remotelink.
This is the XSLT I have written. So far I am not getting result for unmatched condition:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xlsx="http://www.stylusstudio.com/XSLT/XLSX" xmlns:spml="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:saxon="http://saxon.sf.net/" version="2.0">
<xsl:template match="#*|node()" name="root">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="remotelink[#service='DOC-ID']" name="t-remote">
<xsl:variable name="refptid" select="./#refpt"/>
<xsl:variable name="path" select="doc('file:/C:/Users/DalalNS/Desktop/xslt/prop.xml')"/>
<xsl:for-each select="$path/properties/code">
<xsl:choose>
<xsl:when test="./text()=$refptid">
<xsl:element name="remotelink">
<xsl:attribute name="DOC-ID" select="./#docid"/>
<xsl:attribute name="dpsi" select="./#dpsi"/>
<xsl:attribute name="refpt" select="$refptid"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:if test="./#docrefid"/>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="remotelink[#service='DOC-ID']"> will never never be triggered, there's no service attribute on the <remotelink> element. Moreover, you don't retrieve the correct attribute (refpt instead of refptid)
This XSL transformation should do the job:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xlsx="http://www.stylusstudio.com/XSLT/XLSX" xmlns:spml="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:saxon="http://saxon.sf.net/" version="2.0">
<xsl:variable name="props.doc" select="doc('file:/C:/Users/DalalNS/Desktop/xslt/prop.xml')/properties" />
<xsl:template match="#*|node()" name="root">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="remotelink" name="t-remote">
<xsl:variable name="refptid" select="./#refptid"/>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:if test="$props.doc/code[. = $refptid]">
<xsl:apply-templates select="$props.doc/code[. = $refptid]/#*"/>
</xsl:if>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This is the result of the transformation:
<?xml version="1.0" encoding="UTF-8"?><root>
<bodytext>
<remotelink refptid="HKBL1.0001.lohk.CAP65" dpsi="0BZG" docid="asdww">some text</remotelink>
<remotelink refptid="HKBL1.0001.lohk.CAP199999">some text</remotelink>
</bodytext>
</root>
I would simply define a global parameter or variable <xsl:variable name="path" select="doc('file:/C:/Users/DalalNS/Desktop/xslt/prop.xml')"/>, then set up a key <xsl:key name="prop" match="code" use="."/>, and then use that in a template
<xsl:template match="remotelink[key('prop', #refptid, $path)]">
<xsl:copy>
<xsl:copy-of select="key('prop', #refptid, $path)/#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
that, together with your first template, should suffice.
Move elements from one place to another using XSLT 2 (saxon8)
Hi all please help me to produce the following output using XSLT 2.0.
I have this:
<UL>
<ITEM>
aaa
<NL>iii
<ITEM1>111</ITEM1>
<ITEM2>222</ITEM2>
</NL>
</ITEM>
<ITEM>
bbb
<NL>vvv
<ITEM1>333</ITEM1>
<ITEM2>444</ITEM2>
</NL>
</ITEM>
</UL>
I need to produce this
<UL>
<ITEM>
aaa
<ITEM1>111</ITEM1>
</ITEM>
<ITEM>
bbb
<ITEM1>333</ITEM1>
</ITEM>
<NEW>
<NL>iii
<ITEM2>222</ITEM2>
</NL>
<NL>vvv
<ITEM2>444</ITEM2>
</NL>
</NEW>
</UL>
I am trying to get the output using mode option as mentioned below:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="NL"/>
<xsl:template match="ITEM2"/>
<xsl:template match="UL">
<xsl:copy>
<xsl:apply-templates/>
<NEW>
<xsl:apply-templates select="descendant::NL" mode="move"/>
</NEW>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM" mode="move">
<xsl:copy>
<xsl:apply-templates/>
<xsl:apply-templates select="descendant::ITEM2" mode="move1"/>
</xsl:copy>
</xsl:template>
<xsl:template match="NL" mode="move">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM2" mode="move1">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Thanks and regards
Bala
Here is an XSLT 2.0 stylesheet that creates the structure you want, although white space is different, as it is a problem with mixed content to produce the exact output:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="UL">
<xsl:copy>
<xsl:for-each-group select="ITEM" group-by="NL/*/node-name(.)">
<xsl:choose>
<xsl:when test="position() eq 1">
<xsl:for-each select="current-group()">
<ITEM>
<xsl:apply-templates select="text() | NL/*[node-name(.) eq current-grouping-key()]"/>
</ITEM>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<NEW>
<xsl:for-each select="current-group()">
<NL>
<xsl:apply-templates select="NL/(text() | *[node-name(.) eq current-grouping-key()])"/>
</NL>
</xsl:for-each>
</NEW>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="NL/*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I get the result
<UL><ITEM>
aaa
<ITEM1>111</ITEM1>
</ITEM><ITEM>
bbb
<ITEM1>333</ITEM1>
</ITEM><NEW><NL>iii
<ITEM2>222</ITEM2>
</NL><NL>vvv
<ITEM2>444</ITEM2>
</NL></NEW></UL>
When I add instructions to strip white space from the input and indent the output, as in
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="UL">
<xsl:copy>
<xsl:for-each-group select="ITEM" group-by="NL/*/node-name(.)">
<xsl:choose>
<xsl:when test="position() eq 1">
<xsl:for-each select="current-group()">
<ITEM>
<xsl:apply-templates select="text() | NL/*[node-name(.) eq current-grouping-key()]"/>
</ITEM>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<NEW>
<xsl:for-each select="current-group()">
<NL>
<xsl:apply-templates select="NL/(text() | *[node-name(.) eq current-grouping-key()])"/>
</NL>
</xsl:for-each>
</NEW>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="NL/*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
the result is
<UL>
<ITEM>
aaa
<ITEM1>111</ITEM1>
</ITEM>
<ITEM>
bbb
<ITEM1>333</ITEM1>
</ITEM>
<NEW>
<NL>iii
<ITEM2>222</ITEM2>
</NL>
<NL>vvv
<ITEM2>444</ITEM2>
</NL>
</NEW>
</UL>
As an alternative, if we know there are just ITEM1 and ITEM2 we want to split, here is stylesheet using modes:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()" mode="#all">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="NL">
<xsl:apply-templates select="ITEM1"/>
</xsl:template>
<xsl:template match="ITEM2"/>
<xsl:template match="UL">
<xsl:copy>
<xsl:apply-templates/>
<NEW>
<xsl:apply-templates select="descendant::NL" mode="move"/>
</NEW>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM1" mode="move"/>
</xsl:stylesheet>
I have this span element with a class of autbib-pc-bold-italic
<span class="autbib-pc-bold-italic">autbib</span>
I want to create element tags base on the #class attribute value:
My output should be:
<autbib><pc><bold><italic>autbib</italic></bold></pc></autbib>
Here is my xsl templates:
<xsl:template match="span[contains(#class,'autbib')]">
<xsl:call-template name="pbib.loop">
<xsl:with-param name="count" select="count(tokenize(#class, '-'))"/>
<xsl:with-param name="class" select="tokenize(#class, '-')"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="pbib.loop">
<xsl:param name="index" select="1" />
<xsl:param name="count" select="count(tokenize(#class, '-')) + 1"/>
<xsl:param name="class" select="tokenize(#class, '-')"/>
<xsl:element name="{$class[1]}">
<xsl:if test="not($index = $count)">
<xsl:element name="{$class[$index]}">
<xsl:apply-templates/>
</xsl:element>
</xsl:if>
</xsl:element>
<xsl:if test="not($index = $count)">
<xsl:call-template name="pbib.loop">
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
And have this output which is wrong:
<autbib>
<pc>autbib</pc>
<bold>autbib</bold>
<italic>autbib</italic>
</autbib>
I need to have this output:
<autbib>
<pc>
<bold>
<italic>autbib</italic>
</bold>
</pc>
</autbib>
My problem is that I'm not sure where I should place xsl:apply-template so that tags wrap with each other.
Here is my suggestion:
<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 indent="yes"/>
<xsl:template match="span[contains(#class,'autbib')]">
<xsl:param name="classes" select="tokenize(#class, '-')"/>
<xsl:choose>
<xsl:when test="not($classes[1])">
<xsl:apply-templates/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$classes[1]}">
<xsl:apply-templates select=".">
<xsl:with-param name="classes" select="$classes[position() gt 1]"/>
</xsl:apply-templates>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With Saxon 9.4 that transforms the input
<span class="autbib-pc-bold-italic">autbib</span>
into the result
<autbib>
<pc>
<bold>
<italic>autbib</italic>
</bold>
</pc>
</autbib>