I have created an XSLT file that converts everything in a Word XML into clean HTML however I am unable to covert nested lists properly.
I saved a word v16.12 file into XML. The Word file contains two lists
Here is the exported Open XML (relating to just the bullets).
<w:body>
<w:p w:rsidR="00875AF6" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 1 Bullet 1 level 1</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 1 Bullet 2 level 1</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="1"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 1 Bullet 3 level 2</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="2"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 1 Bullet 4 level 3</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="2"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 1 Bullet 5 level 3</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="1"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 1 Bullet 6 level 2</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="2"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 1 Bullet 7 level 3</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 1 Bullet 8 level 1</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241"/>
<w:p w:rsidR="00575241" w:rsidRDefault="00575241" w:rsidP="00575241">
<w:r>
<w:t>This is a break</w:t>
</w:r>
</w:p>
<w:p w:rsidR="00575241" w:rsidRDefault="00575241" w:rsidP="00575241"/>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="2"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 2 Bullet 1 level 1</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="1"/>
<w:numId w:val="2"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 2 Bullet 2 level 2</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="2"/>
<w:numId w:val="2"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 2 Bullet 3 level 3</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="2"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 2 Bullet 4 level 1</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="1"/>
<w:numId w:val="2"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 2 Bullet 5 level 2</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="1"/>
<w:numId w:val="2"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 2 Bullet 6 level 2</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="2"/>
<w:numId w:val="2"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 2 Bullet 7 level 3</w:t>
</w:r>
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
<w:bookmarkEnd w:id="0"/>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:numPr>
<w:ilvl w:val="1"/>
<w:numId w:val="2"/>
</w:numPr>
</w:pPr>
<w:r>
<w:t>List 2 Bullet 8 level 2</w:t>
</w:r>
</w:p>
<w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241"/>
<w:sectPr w:rsidR="007A38EC" w:rsidSect="00D678D3">
<w:pgSz w:w="11900" w:h="16840"/>
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
w:header="708" w:footer="708" w:gutter="0"/>
<w:cols w:space="708"/>
<w:docGrid w:linePitch="360"/>
</w:sectPr>
</w:body>
Using XSLT I need to convert the XML into this HTML
<ul>
<li>List 1 Bullet 1 level 1</li>
<li>List 1 Bullet 2 level 1
<ul>
<li>List 1 Bullet 3 level 2
<ul>
<li>List 1 Bullet 4 level 3</li>
<li>List 1 Bullet 5 level 3</li>
</ul>
</li>
<li>List 1 Bullet 6 level 2
<ul>
<li>List 1 Bullet 7 level 3</li>
</ul>
</li>
</ul>
</li>
<li>List 1 Bullet 8 level 1</li>
</ul>
<p>This is a gap</p>
<ul>
<li>List 2 Bullet 1 level 1
<ul>
<li>List 2 Bullet 2 level 2
<ul>
<li>List 2 Bullet 3 level 3</li>
</ul>
</li>
</ul>
</li>
<li>List 2 Bullet 4 level 1
<ul>
<li>List 2 Bullet 5 level 2</li>
<li>List 2 Bullet 6 level 2
<ul>
<li>List 2 Bullet 7 level 3</li>
</ul>
</li>
<li>List 2 Bullet 8 level 2</li>
</ul>
</li>
</ul>
I have researched and the closest I found to was using a function and for-each-group like the below.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mf="http://example.com/mf" version="2.0"
exclude-result-prefixes="xs mf">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="nodes" as="node()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:if test="$nodes">
<list type="ul">
<xsl:for-each-group select="$nodes"
group-adjacent="boolean(self::*[#level = $level])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="mf:group(current-group(), $level + 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</list>
</xsl:if>
</xsl:function>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[#level]">
<item>
<xsl:apply-templates/>
</item>
</xsl:template>
<xsl:template match="test">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent="boolean(self::item)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:sequence select="mf:group(current-group(), 0)"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Unfortunately using functions and the for-each-group is beyond my ability. My question is how would I amend the above XSLT to work with the XML that I am getting from Word?
First off, we'll start with an identity template:
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
Second, we have to match the root node w:body and group the elements using xsl:for-each-group. Afterwards, we'll store the nodes in a variable (firstpass) to further manipulate the nodes later, such as:
<!-- If you want to specify the target node (1 in 22 as you say),
you can adjust the xpath below to match your target node.
-->
<xsl:template match="w:body">
<xsl:variable name="firstPass">
<xsl:for-each-group select="*" group-adjacent="boolean(self::w:p[descendant::w:ilvl])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<!-- the zero (0) was obtained from the value of
w:val attribute of w:ilvl node -->
<xsl:sequence select="mf:group(current-group(), 0)"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:variable>
<xsl:apply-templates select="$firstPass/node()"/>
</xsl:template>
we can adapt the function that you mentioned. We can modify the group-adjacent target nodes to
<xsl:function name="mf:group" as="node()*">
<xsl:param name="nodes" as="node()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:if test="$nodes">
<ul>
<xsl:for-each-group select="$nodes"
group-adjacent="boolean(self::*[descendant::w:ilvl/#w:val = $level])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="mf:group(current-group(), $level + 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</ul>
</xsl:if>
</xsl:function>
The following are the templates needed for the cleanup
<xsl:template match="w:p">
<xsl:apply-templates select="descendant::w:t"/>
</xsl:template>
<xsl:template match="w:p[.='']|w:sectPr"/>
<xsl:template match="w:t">
<xsl:choose>
<xsl:when test="ancestor::w:p[descendant::w:pStyle[#w:val='ListParagraph']]">
<li>
<xsl:apply-templates/>
</li>
</xsl:when>
<xsl:otherwise>
<p>
<xsl:apply-templates/>
</p>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
After that, we still need to insert the <ul> sublevels into parent <li>. To do that, we have to do a second pass of transformation.
We will now then match the nodes present in the firstpass variable
<xsl:template match="li[following-sibling::*[1][name()='ul']]">
<xsl:copy>
<xsl:apply-templates/>
<!-- this will copy the target ul nodes, albeit in a different mode -->
<xsl:apply-templates select="following-sibling::*[1][name()='ul']" mode="transfer"/>
</xsl:copy>
</xsl:template>
<!-- this will delete the target node -->
<xsl:template match="ul[preceding-sibling::*[1][name()='li']]"/>
and an identity template for the other mode
<xsl:template match="#* | node()" mode="transfer">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
The whole stylesheet is as follows:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
xmlns:w="www.wnamespace.com"
version="2.0"
exclude-result-prefixes="xs mf w">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="nodes" as="node()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:if test="$nodes">
<ul>
<xsl:for-each-group select="$nodes"
group-adjacent="boolean(self::*[descendant::w:ilvl/#w:val = $level])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="mf:group(current-group(), $level + 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</ul>
</xsl:if>
</xsl:function>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#* | node()" mode="transfer">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="w:p">
<xsl:apply-templates select="descendant::w:t"/>
</xsl:template>
<xsl:template match="w:p[.='']|w:sectPr"/>
<xsl:template match="w:t">
<xsl:choose>
<xsl:when test="ancestor::w:p[descendant::w:pStyle[#w:val='ListParagraph']]">
<li>
<xsl:apply-templates/>
</li>
</xsl:when>
<xsl:otherwise>
<p>
<xsl:apply-templates/>
</p>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="w:body">
<xsl:variable name="firstPass">
<xsl:for-each-group select="*" group-adjacent="boolean(self::w:p[descendant::w:ilvl])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:sequence select="mf:group(current-group(), 0)"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:variable>
<xsl:apply-templates select="$firstPass/node()"/>
</xsl:template>
<xsl:template match="ul[preceding-sibling::*[1][name()='li']]"/>
</xsl:stylesheet>
See it in action here.
Related
I have an xml that needs to be converted to JSON and am using XSLT to transform it. In one of the element am not required to pass pair name, only values. See below
My XML:
<?xml version="1.0" encoding="UTF-8"?>
<map xmlns="http://www.w3.org/2005/xpath-functions">
<array key="cars">
<map>
<string key="doors">4</string>
<string key="price">6L</string>
</map>
<map>
<string key="doors">5</string>
<string key="price">13L</string>
</map>
</array>
</map>
Using XSL: https://xsltfiddle.liberty-development.net/b4GWVd
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:value-of select="translate(xml-to-json(., map { 'indent' : true() }),'Ø', 'ø')"/>
</xsl:template>
</xsl:stylesheet>
Gives output: Which is correct
{ "cars" :
[
{ "doors" : "4",
"price" : "6L" },
{ "doors" : "5",
"price" : "13L" } ] }
But then I would like to get a JSON with below structure, Without Pair Name(Reason: Its the structure required to be submitted to an API)
{ "cars" :
[
{ "4",
"6L" },
{ "5",
"13L" } ] }
As the XSLT 3.0 spec also provides an implementation of xml-to-json as an XSLT 3.0 package you could use that code and override templates where you want to eliminate the "keys" of JSON objects/XDM map items:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:j="http://www.w3.org/2013/XSLT/xml-to-json"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="#all"
version="3.0">
<xsl:use-package name="http://www.w3.org/2013/XSLT/xml-to-json" package-version="1.0">
<xsl:override>
<!-- Template rule for fn:map elements, representing JSON objects , overridden for content of cars array -->
<xsl:template match="fn:array[#key = 'cars']/fn:map" mode="indent">
<xsl:value-of>
<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
<xsl:value-of select="j:indent($depth)"/>
</xsl:if>
<!--<xsl:apply-templates select="snapshot(#key)" mode="key-attribute"/>
<xsl:text> : </xsl:text>-->
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
<xsl:template match="fn:array[#key = 'cars']/fn:map/*" mode="no-indent">
<xsl:value-of>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>,</xsl:text>
</xsl:if>
<!--<xsl:apply-templates select="snapshot(#key)" mode="key-attribute"/>
<xsl:text>:</xsl:text>-->
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
</xsl:override>
</xsl:use-package>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:value-of select="j:xml-to-json(., map { 'indent' : true() })"/>
</xsl:template>
</xsl:stylesheet>
The above can be run with Saxon 10 HE and higher or Saxon 9.8 PE or EE and later by using the command line -s:your-xml.xml -xsl:above-xslt-xsl -lib:w3c-xml-to-json.xsl where the last option refers to the file https://www.w3.org/TR/xslt-30/xml-to-json.xsl linked from the XSLT 3 spec with one path/errata added, namely the declaration of the default mode with <xsl:mode name="j:xml-to-json"/> so it would look like
<xsl:package
name="http://www.w3.org/2013/XSLT/xml-to-json"
package-version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:j="http://www.w3.org/2013/XSLT/xml-to-json"
exclude-result-prefixes="xs fn j" default-mode="j:xml-to-json" version="3.0">
<xsl:variable name="quot" visibility="private">"</xsl:variable>
<xsl:param name="indent-spaces" select="2"/>
<!-- The static parameter STREAMABLE controls whether the stylesheet is declared as streamable -->
<xsl:param name="STREAMABLE" static="yes" as="xs:boolean" select="true()"/>
<!-- fix for https://github.com/w3c/qtspecs/blob/master/errata/xslt-30/errata.xml#L1154 -->
<xsl:mode name="j:xml-to-json"/>
<xsl:mode name="indent" _streamable="{$STREAMABLE}" visibility="public"/>
<xsl:mode name="no-indent" _streamable="{$STREAMABLE}" visibility="public"/>
<xsl:mode name="key-attribute" streamable="false" on-no-match="fail" visibility="public"/>
<!-- The static parameter VALIDATE controls whether the input, if untyped, should be validated -->
<xsl:param name="VALIDATE" static="yes" as="xs:boolean" select="false()"/>
<xsl:import-schema namespace="http://www.w3.org/2005/xpath-functions" use-when="$VALIDATE"/>
<!-- Entry point: function to convert a supplied XML node to a JSON string -->
<xsl:function name="j:xml-to-json" as="xs:string" visibility="public">
<xsl:param name="input" as="node()"/>
<xsl:sequence select="j:xml-to-json($input, map{})"/>
</xsl:function>
<!-- Entry point: function to convert a supplied XML node to a JSON string, supplying options -->
<xsl:function name="j:xml-to-json" as="xs:string" visibility="public">
<xsl:param name="input" as="node()"/>
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="input" as="node()" use-when="$VALIDATE">
<xsl:copy-of select="$input" validation="strict"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$options('indent') eq true()">
<xsl:apply-templates select="$input" mode="indent">
<xsl:with-param name="fallback" as="(function(element()) as xs:string)?"
select="$options('fallback')" tunnel="yes"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$input" mode="no-indent">
<xsl:with-param name="fallback" as="(function(element()) as xs:string)?"
select="$options('fallback')" tunnel="yes"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<!-- A document node is ignored -->
<xsl:template match="/" mode="indent no-indent">
<xsl:apply-templates mode="#current"/>
</xsl:template>
<!-- Template rule for fn:map elements, representing JSON objects -->
<xsl:template match="fn:map" mode="indent">
<xsl:value-of>
<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
<xsl:value-of select="j:indent($depth)"/>
</xsl:if>
<xsl:apply-templates select="snapshot(#key)" mode="key-attribute"/>
<xsl:text> : </xsl:text>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
<xsl:template match="fn:map" mode="no-indent">
<xsl:value-of>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:apply-templates select="snapshot(#key)" mode="key-attribute"/>
<xsl:text>:</xsl:text>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
<!-- Template rule for fn:array elements, representing JSON arrays -->
<xsl:template match="fn:array" mode="indent">
<xsl:value-of>
<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
<xsl:text>[</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
<xsl:value-of select="j:indent($depth)"/>
</xsl:if>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:value-of>
</xsl:template>
<xsl:template match="fn:array" mode="no-indent">
<xsl:value-of>
<xsl:text>[</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:value-of>
</xsl:template>
<!-- Template rule for fn:string elements in which
special characters are already escaped -->
<xsl:template match="fn:string[#escaped='true']" mode="indent no-indent">
<xsl:sequence select="concat($quot, ., $quot)"/>
</xsl:template>
<!-- Template rule for fn:string elements in which
special characters need to be escaped -->
<xsl:template match="fn:string[not(#escaped='true')]" mode="indent no-indent">
<xsl:sequence select="concat($quot, j:escape(.), $quot)"/>
</xsl:template>
<!-- Template rule for fn:boolean elements -->
<xsl:template match="fn:boolean" mode="indent no-indent">
<xsl:sequence select="xs:string(xs:boolean(.))"/>
</xsl:template>
<!-- Template rule for fn:number elements -->
<xsl:template match="fn:number" mode="indent no-indent">
<xsl:value-of select="xs:string(xs:double(.))"/>
</xsl:template>
<!-- Template rule for JSON null elements -->
<xsl:template match="fn:null" mode="indent no-indent">
<xsl:text>null</xsl:text>
</xsl:template>
<!-- Template rule matching a key within a map where
special characters in the key are already escaped -->
<xsl:template match="fn:*[#key-escaped='true']/#key" mode="key-attribute">
<xsl:value-of select="concat($quot, ., $quot)"/>
</xsl:template>
<!-- Template rule matching a key within a map where
special characters in the key need to be escaped -->
<xsl:template match="fn:*[not(#key-escaped='true')]/#key" mode="key-attribute">
<xsl:value-of select="concat($quot, j:escape(.), $quot)"/>
</xsl:template>
<!-- Template matching "invalid" elements -->
<xsl:template match="*" mode="indent no-indent">
<xsl:param name="fallback" as="(function(element()) as xs:string)?"
tunnel="yes" required="yes"/>
<xsl:choose>
<xsl:when test="exists($fallback)">
<xsl:value-of select="$fallback(snapshot(.))"/>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">>Inc</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Template rule matching (and discarding) whitespace text nodes in the XML -->
<xsl:template match="text()[not(normalize-space())]" mode="indent no-indent"/>
<!-- Function to escape special characters -->
<xsl:function name="j:escape" as="xs:string" visibility="final">
<xsl:param name="in" as="xs:string"/>
<xsl:value-of>
<xsl:for-each select="string-to-codepoints($in)">
<xsl:choose>
<xsl:when test=". gt 65535">
<xsl:value-of select="concat('\u', j:hex4((. - 65536) idiv 1024 + 55296))"/>
<xsl:value-of select="concat('\u', j:hex4((. - 65536) mod 1024 + 56320))"/>
</xsl:when>
<xsl:when test=". = 34">\"</xsl:when>
<xsl:when test=". = 92">\\</xsl:when>
<xsl:when test=". = 08">\b</xsl:when>
<xsl:when test=". = 09">\t</xsl:when>
<xsl:when test=". = 10">\n</xsl:when>
<xsl:when test=". = 12">\f</xsl:when>
<xsl:when test=". = 13">\r</xsl:when>
<xsl:when test=". lt 32 or (. ge 127 and . le 160)">
<xsl:value-of select="concat('\u', j:hex4(.))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="codepoints-to-string(.)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:value-of>
</xsl:function>
<!-- Function to convert a UTF16 codepoint into a string of four hex digits -->
<xsl:function name="j:hex4" as="xs:string" visibility="final">
<xsl:param name="ch" as="xs:integer"/>
<xsl:variable name="hex" select="'0123456789abcdef'"/>
<xsl:value-of>
<xsl:value-of select="substring($hex, $ch idiv 4096 + 1, 1)"/>
<xsl:value-of select="substring($hex, $ch idiv 256 mod 16 + 1, 1)"/>
<xsl:value-of select="substring($hex, $ch idiv 16 mod 16 + 1, 1)"/>
<xsl:value-of select="substring($hex, $ch mod 16 + 1, 1)"/>
</xsl:value-of>
</xsl:function>
<!-- Function to output whitespace indentation based on
the depth of the node supplied as a parameter -->
<xsl:function name="j:indent" as="text()" visibility="public">
<xsl:param name="depth" as="xs:integer"/>
<xsl:value-of select="'
', string-join((1 to ($depth + 1) * $indent-spaces) ! ' ', '')"/>
</xsl:function>
</xsl:package>
To run code with Java code you basically need to use an XsltCompiler created from Processor e.g.
Processor processor = new Processor(true);
XsltCompiler xsltCompiler = processor.newXsltCompiler();
XsltPackage xmlToJsonPackage = xsltCompiler.compilePackage(new StreamSource("w3c-xml-to-json.xsl"));
xsltCompiler.importPackage(xmlToJsonPackage);
XsltExecutable xsltExecutable = xsltCompiler.compile(new StreamSource("sheet.xsl"));
Xslt30Transformer xslt30Transformer = xsltExecutable.load30();
// now run stylesheet with e.g. transform() or applyTemplates()
xslt30Transformer.transform(new StreamSource("input.xml"), xslt30Transformer.newSerializer(System.out));
I am new in XSLT world, thank you in advance for your understending. I need to prepare a xml which will be send to Adobe InDesign server. In the html files, which are my input that I need to transform to xml and send to Adobe InDesign by using XSLT transformation, I have "li" elements that have "span" tags and "i" (italic) tags inside. I would like to treat "i" tags, to be italics in the final xml for InDesign. I tried to match "i" tags by the following xslt:
<xsl:template match="i" mode="process-text">
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/Italic">
<Content>
<xsl:copy-of select="text()"/>
</Content>
</CharacterStyleRange>
</xsl:template>
but without results.
For example, I have the following input:
<li class="MsoNormal" style="mso-list:l0 level2 lfo1;tab-stops:list 1.0in">Systolic dysfunction: an <i>inotropic</i> abnormality, due to myocardial infarction (MI) or dilated or ischemic cardiomyopathy (CM), resulting in diminished systolic emptying (ejection fraction <45%).</li>
I would like to transform it to the following one:
<ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/BL2">
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]">
<Content>Systolic dysfunction: an </Content>
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/Italic">
<Content>inotropic</Content>
</CharacterStyleRange>
<Content> abnormality, due to myocardial infarction (MI) or dilated or ischemic cardiomyopathy (CM), resulting in diminished systolic emptying (ejection fraction <45%).</Content>
<Br/>
</CharacterStyleRange>
</ParagraphStyleRange>
My initial problem is how to split a "li" tag and treat (separately) the text inside, and also treat separately "span" and "i" tags inside "li" by XSLT? Thank you in advance for any help.
UPDATE:
My main template, for "li" elements is:
<xsl:template match="li[not(descendant::p) and not(ancestor::section[#class='references' or #class='References'])]" mode="li-pass1">
<xsl:variable name="depth" select="count(ancestor::li) + 1"/>
<xsl:variable name="listType">
<xsl:choose>
<xsl:when test="parent::ol">
<xsl:value-of select="'NL'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'BL'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/{$listType}{if ($depth eq 1) then '' else $depth}">
<xsl:choose>
<xsl:when test="descendant::i/text()">
<Content>
<xsl:copy-of select="./text() | descendant::span/text() "/>
</Content>
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/Italic">
<Content>
<xsl:copy-of select="descendant::i/text()"/>
</Content>
</CharacterStyleRange>
</xsl:when>
<xsl:otherwise>
<Content>
<xsl:copy-of select="./text() | descendant::span/text() "/>
</Content>
</xsl:otherwise>
</xsl:choose>
</ParagraphStyleRange>
</xsl:template>
This template affects final xml in a wrong way. I got the following result:
<ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/BL">
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]">
<Content>Two potential pathophysiologic conditions lead to the clinical findings of HF, namely systolic and/or diastolic heart dysfunction.
</Content>
</CharacterStyleRange>
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/Italic">
<Content>inotropiccompliance</Content>
</CharacterStyleRange>
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]"/>
</ParagraphStyleRange>
<ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/BL2">
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]">
<Content>Systolic dysfunction: an abnormality, due to myocardial infarction (MI) or dilated or ischemic cardiomyopathy (CM), resulting in diminished systolic emptying (ejection fraction <45%).</Content>
</CharacterStyleRange>
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/Italic">
<Content>inotropic</Content>
</CharacterStyleRange>
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]"/>
</ParagraphStyleRange>
So, you can see, italic elements are in a separate tag, but without other content. Could you please suggest what I need to do?
I would try to write templates mapping each element type to the corresponding result structure and inside use <xsl:apply-templates/> to keep processing up. So the basic approach for that sample would look like
<xsl:template match="li">
<xsl:variable name="depth" select="count(ancestor::li) + 1"/>
<xsl:variable name="listType">
<xsl:choose>
<xsl:when test="parent::ol">
<xsl:value-of select="'NL'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'BL'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/{$listType}{if ($depth eq 1) then '' else $depth}">
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]">
<xsl:apply-templates/>
</CharacterStyleRange>
</ParagraphStyleRange>
</xsl:template>
<xsl:template match="i">
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/Italic">
<Content>
<xsl:apply-templates/>
</Content>
</CharacterStyleRange>
</xsl:template>
<xsl:template match="text()[normalize-space()]">
<Content>
<xsl:value-of select="."/>
</Content>
</xsl:template>
https://xsltfiddle.liberty-development.net/93dFK9Q
That gives
<ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/BL">
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]">
<Content>Systolic dysfunction: an </Content>
<CharacterStyleRange AppliedCharacterStyle="CharacterStyle/Italic">
<Content>
<Content>inotropic</Content>
</Content>
</CharacterStyleRange>
<Content> abnormality, due to myocardial infarction (MI) or dilated or ischemic cardiomyopathy (CM), resulting in diminished systolic emptying (ejection fraction <45%).</Content>
</CharacterStyleRange>
</ParagraphStyleRange>
I might not have captured all details of your needed output format but I hope the sample shows that the key is to use apply-templates to process child nodes with matching templates.
I have a partially working XSLT that groups my elements by columns according to column break. My elements are displayed in one column until a <ColumnBreak> is encountered, which then makes the succeeding items rendered in a new column to the right.
Now I want to extend this XSLT to achieve the concept of column span based on the width of my items, so I followed the sample implementation from this topic. Furthermore, if a region is already occupied in the next column due to the width span of the previous element, the succeeding elements following the ColumnBreak shall be displayed below the already occupied region.
XML:
<?xml version="1.0" encoding="utf-8" ?>
<Group>
<Items>
<Item>
<Display>Item 1</Display>
</Item>
<Item>
<Display>Item 2</Display>
<Width>2</Width>
</Item>
<Item>
<Display>Item 3</Display>
</Item>
<ColumnBreak />
<Item>
<Display>Item 4</Display>
</Item>
<Item>
<Display>Item 5</Display>
</Item>
<ColumnBreak />
<Item>
<Display>Item 6</Display>
</Item>
<Item>
<Display>Item 7</Display>
</Item>
</Items>
</Group>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"
exclude-result-prefixes="msxsl xsi">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Group">
<xsl:apply-templates select="Items"/>
</xsl:template>
<xsl:key name="cell-by-row" match="cell" use="#row" />
<xsl:key name="cell-by-col" match="cell" use="concat(#row, '|', #col)" />
<xsl:template match="Items">
<div>
<xsl:variable name="cells">
<xsl:apply-templates select="*[1]" mode="item-sibling">
<xsl:with-param name="row" select="1"/>
<xsl:with-param name="col" select="1"/>
<xsl:with-param name="index" select="1"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:call-template name="generate-rows">
<xsl:with-param name="cells" select="$cells" />
<xsl:with-param name="current-row" select="1"/>
</xsl:call-template>
</div>
</xsl:template>
<xsl:template name="generate-rows">
<xsl:param name="cells" />
<xsl:param name="current-row" />
<xsl:param name="last-row" />
<xsl:for-each select="exsl:node-set($cells)/cell[count(. | key('cell-by-row', #row)[1]) = 1]">
<xsl:for-each select="key('cell-by-row', #row)[count(. | key('cell-by-col', concat(#row, '|', #col))[1]) = 1]">
<xsl:for-each select="key('cell-by-col', concat(#row, '|', #col))">
<xsl:if test="not(#empty)">
<div row="{#row}" col="{#col}" index="{#index}" width="{#width}">
<xsl:apply-templates select="." mode="item">
</xsl:apply-templates>
</div>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="*" mode="item-sibling">
<xsl:param name="row"/>
<xsl:param name="col"/>
<xsl:param name="index"/>
<xsl:param name="previous-cells" select="some-dummy-node-to-make-this-a-node"/>
<xsl:variable name="width">
<xsl:call-template name="width-template" />
</xsl:variable>
<xsl:variable name="colliding-cell" select="exsl:node-set($previous-cells)/cell[#row = $row and #col < $col and #index > $index and #width + #col > $col]" />
<xsl:choose>
<xsl:when test="$colliding-cell">
<xsl:apply-templates select="self::node()" mode="item-sibling">
<xsl:with-param name="row" select="$row"/>
<xsl:with-param name="col" select="$col"/>
<xsl:with-param name="index" select="$colliding-cell/#index + 1"/>
<xsl:with-param name="previous-cells" select="$previous-cells"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="current-cell">
<cell row="{$row}" col="{$col}" index="{$index}" width="{$width}">
<xsl:copy-of select="exsl:node-set(.)"/>
</cell>
</xsl:variable>
<xsl:variable name="new-cells">
<xsl:copy-of select="$previous-cells"/>
<xsl:copy-of select="$current-cell"/>
</xsl:variable>
<xsl:copy-of select="$current-cell"/>
<xsl:apply-templates select="following-sibling::*[1]" mode="item-sibling">
<xsl:with-param name="row" select="$row"/>
<xsl:with-param name="col" select="$col"/>
<xsl:with-param name="index" select="$index + 1"/>
<xsl:with-param name="previous-cells" select="$new-cells"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="ColumnBreak" mode="item-sibling">
<xsl:param name="row"/>
<xsl:param name="col"/>
<xsl:param name="index"/>
<xsl:param name="previous-cells"/>
<xsl:apply-templates select="following-sibling::*[1]" mode="item-sibling">
<xsl:with-param name="row" select="$row"/>
<xsl:with-param name="col" select="$col + 1"/>
<xsl:with-param name="index" select="1" />
<xsl:with-param name="previous-cells" select="$previous-cells"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template name="width-template">
<xsl:choose>
<xsl:when test="Width">
<xsl:value-of select="Width" />
</xsl:when>
<xsl:otherwise>1</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Item" mode="item">
<xsl:value-of select="Display" />
</xsl:template>
</xsl:stylesheet>
EXPECTED RESULT*:
Scenario 1: Item 2 Width = 2
Scenario 2: Item 2 Width = 2, Item 4 Width = 2
*For illustration purposes, I used HTML table in my scenario screenshots, but basically the items need not to be precisely aligned by row because my elements may vary by height. The ultimate goal is to group them into columns.
Current Output:
My current XSLT produces a raw matrix which shows the column assignment of the element and its corresponding width. How do I convert this matrix to a layout of HTML <div> that will look similar to the expected results above?
My XSLT Fiddle: https://xsltfiddle.liberty-development.net/6pS26mX/1
Thank you!
I'm creating an excel document where certain text within a cell will be formatted in a different colour or bold.
for example my spreadsheet should be like this:
.
The text I want to format has got tags round it in my XML e.g. <strong>. I have written a function that picks up these tags and replaced them with
the colour or bold formatting e.g. <Font html:color="#0000FF"> text </Font> when creating the xml output to open in excel.
The problem is that when the spreadsheet is opened the text isn't coloured but instead surround by the formatting command <Font html:color="#0000FF">:
.
Here is my input XML
<?xml version="1.0" encoding="UTF-8"?>
<analysis>
<datasetList>
<dataset>
<datasetLabel>Subject Level Analysis</datasetLabel>
<datasetName>ADSL</datasetName>
<datasetOrdinal>1</datasetOrdinal>
<datasetStructure>One record per subject</datasetStructure>
<datasetContext>
<datasetClass>
<datasetClass>ADSL</datasetClass>
</datasetClass>
</datasetContext>
<columnList>
<column>
<columnLabel>Analysis Visit</columnLabel>
<columnName>AVISIT</columnName>
<columnDerivationList>
<columnDerivation>
<columnDerivationDescription>Set to collected visit name [EG.VISIT] <keepordrop>Set to 'POST-BASELINE MINIMUM'</keepordrop></columnDerivationDescription>
</columnDerivation>
<columnDerivation>
<columnDerivationDescription>Set to a re-defined visit range based on user-defined input. <strong>Set to 'POST-BASELINE MINIMUM'</strong></columnDerivationDescription>
</columnDerivation>
</columnDerivationList>
</column>
<column>
<columnLabel>Analysis Visit (N)</columnLabel>
<columnName>AVISITN</columnName>
<columnDerivationList>
<columnDerivation>
<columnDerivationDescription>Set to collected <edit>Set to 9997</edit></columnDerivationDescription>
</columnDerivation>
<columnDerivation>
<columnDerivationDescription>Set to a user defined numeric value <select>Set to 9997 wih the analysis visit </select></columnDerivationDescription>
</columnDerivation>
</columnDerivationList>
</column>
</columnList>
</dataset>
</datasetList>
</analysis>
Here is my complete XSLT
<?xml version="1.0" encoding="UTF-8"?>
<?mso-application progid="Excel.Sheet"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns:gdsr="http://somethinghere.com" >
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!--
***************************************************************************************
START OF WORKBOOK LAYOUT
***************************************************************************************
-->
<xsl:template match="/" >
<xsl:processing-instruction name="mso-application">progid="Excel.Sheet"</xsl:processing-instruction>
<ss:Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">
<xsl:call-template name="document-properties"/>
<xsl:call-template name="document-styles"/>
<!-- ****************************************************************************************** -->
<!-- CREATE THE TABS IN THE WORKBOOK -->
<!-- ****************************************************************************************** -->
<xsl:call-template name="getDset"/>
<!-- ****************************************************************************************** -->
</ss:Workbook>
</xsl:template>
<!-- TEMPLATE FOR DATASET -->
<xsl:template name="getDset">
<xsl:for-each select="//dataset">
<ss:Worksheet ss:Name="{.//datasetName}"><!-- Add filters to the columns -->
<Names>
<NamedRange ss:Name="_FilterDatabase" ss:RefersTo="={.//datasetName}!R1C1:R1C2" ss:Hidden="1"/>
</Names>
<ss:Table ss:ExpandedColumnCount="2" ss:ExpandedRowCount="200" x:FullColumns="1" x:FullRows="1" ss:DefaultRowHeight="30">
<Column ss:Width="70"/>
<Column ss:Width="70"/>
<Row>
<Cell ss:StyleID="sHead">
<Data ss:Type="String">
Header1
</Data>
<NamedCell ss:Name="_FilterDatabase"/>
</Cell>
<Cell ss:StyleID="sHead">
<Data ss:Type="String">
Header2
</Data>
<NamedCell ss:Name="_FilterDatabase"/>
</Cell>
</Row>
<xsl:for-each select="columnList/column">
<xsl:if test="columnName !=' ' ">
<Row>
<Cell ss:StyleID="sBody" >
<Data ss:Type="String">
<xsl:value-of select="columnName"/>
</Data>
</Cell>
<!-- SET COLOR OF TEXT WHEN TAGGED -->
<xsl:variable name="columnDerivationDescription">
<xsl:value-of select="columnDerivationList/columnDerivation/columnDerivationDescription"/>
</xsl:variable>
<xsl:variable name="columnDerivationDescription1">
<xsl:value-of select="gdsr:set-font($columnDerivationDescription)"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($columnDerivationDescription1, 'Font')">
<Cell ss:StyleID="sBody">
<ss:Data ss:Type="String" xmlns="http://www.w3.org/TR/REC-html40">
<xsl:for-each select="columnDerivationList/columnDerivation">
<xsl:if test="columnDerivationDescription !=' ' ">
<xsl:call-template name="LFsToBRs">
<xsl:with-param name="input" select="gdsr:set-font(columnDerivationDescription)"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</ss:Data>
</Cell>
</xsl:when>
<xsl:otherwise>
<Cell ss:StyleID="sBody">
<Data ss:Type="String">
<xsl:for-each select="columnDerivationList/columnDerivation">
<xsl:if test="columnDerivationDescription !=' ' ">
<xsl:call-template name="LFsToBRs">
<xsl:with-param name="input" select="columnDerivationDescription"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</Data>
</Cell>
</xsl:otherwise>
</xsl:choose>
<!-- COLOUR TAGGED. NICELY DONE -->
</Row>
</xsl:if>
</xsl:for-each>
</ss:Table>
<xsl:call-template name="worksheet-options"/>
<AutoFilter x:Range="R1C1:R1C2" xmlns="urn:schemas-microsoft-com:office:excel"/>
</ss:Worksheet>
</xsl:for-each>
</xsl:template>
<!--FUNCTIONS-->
<xsl:function name="gdsr:set-font">
<xsl:param name="text"/>
<xsl:variable name="blue-font"><Font html:color="#0000FF"></xsl:variable>
<xsl:variable name="blue-font-end"><xsl:text disable-output-escaping="no"></Font></xsl:text></xsl:variable>
<xsl:variable name="pink-font"><Font html:color="#7030A0"></xsl:variable>
<xsl:variable name="pink-font-end"><xsl:text disable-output-escaping="no"></Font></xsl:text></xsl:variable>
<xsl:variable name="var-1" select="replace($text,'<select>',$blue-font)"/>
<xsl:variable name="var-2" select="replace($var-1,'</select>',$blue-font-end)"/>
<xsl:variable name="var-3" select="replace($var-2,'<strong>','<b>')"/>
<xsl:variable name="var-4" select="replace($var-3,'</strong>','</b>')"/>
<xsl:variable name="var-5" select="replace($var-4,'<keepordrop>',$pink-font)"/>
<xsl:variable name="var-6" select="replace($var-5,'</keepordrop>',$pink-font-end)"/>
<xsl:variable name="var-7" select="replace($var-6,'<edit>',$blue-font)"/>
<xsl:variable name="var-8" select="replace($var-7,'</edit>',$blue-font-end)"/>
<xsl:variable name="var-81" select="replace($var-8,'<select><option>',$blue-font)"/>
<xsl:variable name="var-82" select="replace($var-81,'</option><option>','BLUEFONTENDOP1 | BLUEFONTSTARTOP2')"/>
<xsl:variable name="var-83" select="replace($var-82,'BLUEFONTENDOP1',$blue-font-end)"/>
<xsl:variable name="var-84" select="replace($var-83,'BLUEFONTSTARTOP2',$blue-font)"/>
<xsl:variable name="var-85" select="replace($var-84,'</option></select>',$blue-font-end)"/>
<xsl:variable name="var-9">
<xsl:analyze-string select="$var-85"
regex="({$blue-font})(.*?)({$blue-font-end}|{$blue-font})">
<xsl:matching-substring>
<xsl:choose>
<xsl:when test="regex-group(3)=$blue-font-end">
<xsl:value-of select="regex-group(0)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="regex-group(1)"/>
<xsl:value-of select="regex-group(2)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:variable name="var-10">
<xsl:analyze-string select="$var-9"
regex="({$blue-font-end})(.*?)({$blue-font-end}|{$blue-font})">
<xsl:matching-substring>
<xsl:choose>
<xsl:when test="regex-group(3)=$blue-font">
<xsl:value-of select="regex-group(0)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="regex-group(2)"/>
<xsl:value-of select="regex-group(3)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:value-of select="$var-10"/>
</xsl:function>
<!--
*************************************GENERAL TEMPLATES**************************************************
-->
<xsl:template name="LFsToBRs">
<xsl:param name="input"/>
<xsl:choose>
<xsl:when test="contains($input, '
')">
<xsl:value-of select="substring-before($input, '
')"/>
<xsl:text disable-output-escaping="yes"> </xsl:text>
<xsl:call-template name="LFsToBRs">
<xsl:with-param name="input" select="substring-after($input, '
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$input"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- EXCEL WORKSHEET OPTIONS -->
<xsl:template name="worksheet-options">
<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">
<PageSetup>
<Header x:Margin="0.3"/>
<Footer x:Margin="0.3"/>
<PageMargins x:Bottom="0.75" x:Left="0.7" x:Right="0.7" x:Top="0.75"/>
</PageSetup>
<Selected/>
<FreezePanes/>
<FrozenNoSplit/>
<SplitHorizontal>1</SplitHorizontal>
<TopRowBottomPane>1</TopRowBottomPane>
<ActivePane>2</ActivePane>
<Panes>
<Pane>
<Number>3</Number>
<ActiveRow>18</ActiveRow>
<ActiveCol>2</ActiveCol>
</Pane>
</Panes>
<ProtectObjects>False</ProtectObjects>
<ProtectScenarios>False</ProtectScenarios>
</WorksheetOptions>
</xsl:template>
<!-- EXCEL DOCUMENT PROPERTIES -->
<xsl:template name="document-properties">
<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
<Author>Mason, Huw {MDBZ~Basel}</Author>
<LastAuthor>Mason, Huw {MDBZ~Basel}</LastAuthor>
<Created></Created>
<Company></Company>
<Version>1</Version>
</DocumentProperties>
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
<WindowHeight>14370</WindowHeight>
<WindowWidth>27795</WindowWidth>
<WindowTopX>480</WindowTopX>
<WindowTopY>120</WindowTopY>
<ProtectStructure>False</ProtectStructure>
<ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>
</xsl:template>
<!-- EXCEL DOCUMENT STYLES -->
<xsl:template name="document-styles">
<ss:Styles>
<ss:Style ss:ID="Default" ss:Name="Normal">
<ss:Alignment ss:Vertical="Bottom" ss:WrapText="1"/>
<ss:Borders/>
<ss:Font ss:FontName="Arial" x:Family="Swiss" ss:Size="10" ss:Color="#000000"/>
<ss:Interior/>
<ss:NumberFormat/>
<ss:Protection/>
</ss:Style><!-- **STYLES CREATED TO REF LATER ON e.g. STYLE FOR COL HEADER**-->
<ss:Style ss:ID="sHead">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#FFFFFF"/>
<ss:Interior ss:Color="#538DD5" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sHeadAC">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#000000"/>
<ss:Interior ss:Color="#FFFF66" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sHeadCT">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#FFFFFF"/>
<ss:Interior ss:Color="#FF9900" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sHeadGD">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#DA9694"/>
<ss:Interior ss:Color="#000000" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sHeadPRM">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#FFFFFF"/>
<ss:Interior ss:Color="#92D050" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sHeadALG">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#FFFFFF"/>
<ss:Interior ss:Color="#92D050" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sBody">
<Alignment ss:Horizontal="Left" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<ss:Interior ss:Pattern="Solid"/>
</ss:Style>
<Style ss:ID="SHyperlink" ss:Name="Hyperlink">
<Alignment ss:Horizontal="Left" ss:Vertical="Top" ss:WrapText="1"/>
<Borders>
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="10" ss:Color="#0000FF" ss:Underline="Single"/>
</Style>
</ss:Styles>
</xsl:template>
</xsl:stylesheet>
Here is a sample from my output XML
<Row>
<Cell ss:StyleID="sBody">
<Data ss:Type="String">AVISIT</Data>
</Cell>
<Cell ss:StyleID="sBody">
<ss:Data xmlns="http://www.w3.org/TR/REC-html40" ss:Type="String">Set to collected visit name [EG.VISIT] <Font html:color="#7030A0">Set to 'POST-BASELINE MINIMUM'</Font>Set to a re-defined visit range based on user-defined input. <b>Set to 'POST-BASELINE MINIMUM'</b></ss:Data>
</Cell>
</Row>
<Row>
<Cell ss:StyleID="sBody">
<Data ss:Type="String">AVISITN</Data>
</Cell>
<Cell ss:StyleID="sBody">
<ss:Data xmlns="http://www.w3.org/TR/REC-html40" ss:Type="String">Set to collected <Font html:color="#0000FF">Set to 9997</Font>Set to a user defined numeric value <Font html:color="#0000FF">Set to 9997 wih the analysis visit </Font></ss:Data>
</Cell>
</Row>
I've created an xsl and saved as XML spreadsheet 2003 to check the code and is as expected. When I replace < > with < and > I get the issue I am experiencing with my output
I don't know if is related to using < rather than < when creating the xml. Has anybody had a similar problem and solved it? Or can see where my problem lies?
EDIT:
I,ve made the updates to the XSLT inc. the html namespace, as suggested by Martin. This creates the XML as I would expect and looks fine.
The problem now is that when opened in excel the formatting doesn't display, I get plain black text.
Output without formatting
I've created an example of what it should look and saved as XML spreadsheet 2003, to compare the two and they are the same.
Interestingly: If I copy the code from the excel created example and paste it in to the xslt created XML then open it in excel, the formatting works.
If I copy the code from the xslt created xml and paste it in to my excel created example (that did work fine) the formatting now no longer works.
Excel created example
The XML code of the excel created value
<Row ss:AutoFitHeight="0">
<Cell ss:StyleID="s64"><Data ss:Type="String">AVISIT</Data></Cell>
<Cell ss:StyleID="s65"><ss:Data ss:Type="String"
xmlns="http://www.w3.org/TR/REC-html40">Set to collected visit name [EG.VISIT] <Font
html:Color="#7030A0">Set to 'POST-BASELINE MINIMUM'</Font>Set to a re-defined visit range based on user-defined input. <B>Set to 'POST-BASELINE MINIMUM'</B></ss:Data></Cell>
</Row>
<Row ss:AutoFitHeight="0">
<Cell ss:StyleID="s64"><Data ss:Type="String">AVISITN</Data></Cell>
<Cell ss:StyleID="s65"><ss:Data ss:Type="String"
xmlns="http://www.w3.org/TR/REC-html40">Set to collected <Font
html:Color="#0000FF">Set to 9997</Font>Set to a user defined numeric value<Font
html:Color="#0000FF">Set to 9997 wih the analysis visit</Font></ss:Data></Cell>
</Row>
This is how the xml created by my XSLT looks:
<Row>
<Cell ss:StyleID="sBody">
<Data ss:Type="String">AVISIT</Data>
</Cell>
<Cell ss:StyleID="sBody">
<ss:Data xmlns="http://www.w3.org/TR/REC-html40" ss:Type="String">Set to collected visit name [EG.VISIT] <Font html:Color="#7030A0">Set to 'POST-BASELINE MINIMUM'</Font>Set to a re-defined visit range based on user-defined input. <B>Set to 'POST-BASELINE MINIMUM'</B>
</ss:Data>
</Cell>
</Row>
<Row>
<Cell ss:StyleID="sBody">
<Data ss:Type="String">AVISITN</Data>
</Cell>
<Cell ss:StyleID="sBody">
<ss:Data xmlns="http://www.w3.org/TR/REC-html40" ss:Type="String">Set to collected <Font html:Color="#0000FF">Set to 9997</Font>Set to a user defined numeric value <Font html:Color="#0000FF">Set to 9997 wih the analysis visit </Font>
</ss:Data>
</Cell>
</Row>
So despite the fact that the xml code is the same, depending on how it was created defines whether it works or not.
Question:
Could there be some encoding differences that would explain this behavior?
Here is how you could approach the problem using parse-xml-fragment (https://www.w3.org/TR/xpath-functions-31/#func-parse-xml-fragment, available in Saxon 9.7 (all editions) when using version="3.0" in your XSLT) and then templates to transform the elements you have inside of the escaped markup in the columnDerivationDescription elements:
<xsl:template name="getDset">
<xsl:for-each select="//dataset">
<ss:Worksheet ss:Name="{.//datasetName}"><!-- Add filters to the columns -->
<Names>
<NamedRange ss:Name="_FilterDatabase" ss:RefersTo="={.//datasetName}!R1C1:R1C2" ss:Hidden="1"/>
</Names>
<ss:Table ss:ExpandedColumnCount="2" ss:ExpandedRowCount="200" x:FullColumns="1" x:FullRows="1" ss:DefaultRowHeight="30">
<Column ss:Width="70"/>
<Column ss:Width="70"/>
<Row>
<Cell ss:StyleID="sHead">
<Data ss:Type="String">
Header1
</Data>
<NamedCell ss:Name="_FilterDatabase"/>
</Cell>
<Cell ss:StyleID="sHead">
<Data ss:Type="String">
Header2
</Data>
<NamedCell ss:Name="_FilterDatabase"/>
</Cell>
</Row>
<xsl:for-each select="columnList/column">
<xsl:if test="columnName !=' ' ">
<Row>
<Cell ss:StyleID="sBody" >
<Data ss:Type="String">
<xsl:value-of select="columnName"/>
</Data>
</Cell>
<!-- SET COLOR OF TEXT WHEN TAGGED -->
<Cell ss:StyleID="sBody">
<Data ss:Type="String" xmlns="http://www.w3.org/TR/REC-html40">
<xsl:apply-templates select="columnDerivationList/columnDerivation/columnDerivationDescription"/>
</Data>
</Cell>
<!-- COLOUR TAGGED. NICELY DONE -->
</Row>
</xsl:if>
</xsl:for-each>
</ss:Table>
<xsl:call-template name="worksheet-options"/>
<AutoFilter x:Range="R1C1:R1C2" xmlns="urn:schemas-microsoft-com:office:excel"/>
</ss:Worksheet>
</xsl:for-each>
</xsl:template>
<xsl:template match="columnDerivationDescription">
<xsl:apply-templates select="parse-xml-fragment(.)/node()"/>
</xsl:template>
<xsl:template match="strong">
<b>
<xsl:apply-templates/>
</b>
</xsl:template>
<xsl:template match="edit">
<Font html:color="#0000FF">
<xsl:apply-templates/>
</Font>
</xsl:template>
<!-- add further templates here for the other elements and transformations you need -->
Following is the XML piece of code -
<groups>
<group i=1>
<member t="P.M" c="Y">ABC</member>
<member t="P.L">PQR</member>
<member t="M">XYZ</member>
</group>
<group i=2>
<member t="M" c="Y">ABC</member>
<member t="M">PQR</member>
</group>
<group i=3>
<member t="P.L" c="Y">ABC</member>
<member t="M">PQR</member>
<member t="M">XYZ</member>
</group>
<group i=4>
<member t="M">ABC</member>
<member t="M" c="Y">PQR</member>
</group>
<group i=5>
<member t="M">ABC</member>
<member t="M" c="Y">PQR</member>
<member t="M" c="Y">XYZ</member>
</group>
<group i=6>
<member t="M" dec="Y">ABC</member>
</group>
</groups>
Desired HTML output using XSLT 1.0 -
<U>ABC</U>, P.M, PQR, P.L and XYZ, M
<U>ABC</U> and PQR, MM
<U>ABC</U>, P.L, PQR and XYZ, MM
ABC and <U>PQR</U>, MM
ABC, <U>PQR</U> and <U>XYZ</U>, MM
<U>ABC</U>, M
The partial XSLT solution for the above output is -
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:output method='html' media-type='text/html'/>
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="group">
<p>
<xsl:apply-templates select="#*|node()"/>
</p>
</xsl:template>
<xsl:template match="member">
<xsl:choose>
<xsl:when test='#c = "Y"'>
<u><xsl:value-of select="."/></u>, <xsl:value-of select='#t'/>
</xsl:when>
<xsl:otherwise>
<b><xsl:value-of select="."/></b>, <xsl:value-of select='#t'/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="member[following-sibling::member]">
<xsl:choose>
<xsl:when test='#c = "Y"'>
<u><xsl:value-of select="."/></u>, <xsl:value-of select='#t'/>
<xsl:text> and </xsl:text>
</xsl:when>
<xsl:otherwise>
<b><xsl:value-of select="."/></b>, <xsl:value-of select='#t'/>
<xsl:text> and </xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="member[following-sibling::member[following-sibling::member]]">
<xsl:choose>
<xsl:when test='#c = "Y"'>
<u><xsl:value-of select="."/></u>, <xsl:value-of select='#t'/>
<xsl:text>, </xsl:text>
</xsl:when>
<xsl:otherwise>
<b><xsl:value-of select="."/></b>, <xsl:value-of select='#t'/>
<xsl:text>, </xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The above XSLT is giving the output in correct format as required, but if the <member>
are of same type then how to add this type at the end..?
In answer to your immediate question though, to output the correct number of t elements for a member, you could do something like this:
<xsl:apply-templates select="../member/#t[. = current()/#t]"/>
So, it will output a value for each matching #t attribute.
However, I think you need to read up more on grouping, as Mr. Michael Kay suggested in your last question.
In this case, you are grouping by a group attributeand a member attribute. So, you would define a key, like so
<xsl:key name="members" match="member" use="concat(../#i, '|', #t)"/>
Do note the use of the pipe | to concatenate the two parts of the key. You would need to pick a character that could not occur in either part of the key.
Then, you can get the first element of each group like so
<xsl:apply-templates
select="group/member[generate-id() = generate-id(key('members', concat(../#i, '|', #t))[1])]"
mode="group"/>
And to iterate over all the elements in the group, you can then do this...
<xsl:apply-templates select="key('members', concat(../#i, '|', #t))"/>
So, given the following XML
<groups>
<group i="1">
<member t="P.M" c="Y">ABC</member>
<member t="P.L">PQR</member>
<member t="M">XYZ</member>
</group>
<group i="2">
<member t="M" c="Y">ABC</member>
<member t="M">PQR</member>
</group>
<group i="3">
<member t="P.L" c="Y">ABC</member>
<member t="M">PQR</member>
<member t="M">XYZ</member>
</group>
<group i="4">
<member t="M">ABC</member>
<member t="M" c="Y">PQR</member>
</group>
<group i="5">
<member t="M">ABC</member>
<member t="M" c="Y">PQR</member>
<member t="M" c="Y">XYZ</member>
</group>
<group i="6">
<member t="M" c="Y">ABC</member>
</group>
</groups>
Using the following XSLT....
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="members" match="member" use="concat(../#i, '|', #t)"/>
<xsl:template match="/groups">
<xsl:apply-templates select="group/member[generate-id() = generate-id(key('members', concat(../#i, '|', #t))[1])]" mode="group"/>
</xsl:template>
<xsl:template match="member" mode="group">
<xsl:apply-templates select="key('members', concat(../#i, '|', #t))"/>
<xsl:text>, </xsl:text>
<xsl:apply-templates select="../member/#t[. = current()/#t]"/>
<xsl:variable name="others" select="count(following-sibling::member[#t != current()/#t])" />
<xsl:choose>
<xsl:when test="$others > 1">, </xsl:when>
<xsl:when test="$others = 1"> and </xsl:when>
<xsl:otherwise>
<xsl:text>
</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="member">
<xsl:choose>
<xsl:when test="#c='Y'">
<u>
<xsl:value-of select="."/>
</u>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="others" select="count(following-sibling::member[#t = current()/#t])" />
<xsl:choose>
<xsl:when test="$others > 1">, </xsl:when>
<xsl:when test="$others = 1"> and </xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The following is output....
<u>ABC</u>, P.M, PQR, P.L and XYZ, M
<u>ABC</u> and PQR, MM
<u>ABC</u>, P.L, PQR and XYZ, MM
ABC and <u>PQR</u>, MM
ABC, <u>PQR</u> and <u>XYZ</u>, MMM
<u>ABC</u>, M