I have the following XML
<data>
<records>
<record name="A record">
<info>A1</info>
<info>A2</info>
</record>
<record name="B record"/>
<record name="C record">
<info>C1</info>
</record>
</records>
</data>
how can I transform into following output, the problem is how can I count between on record, and record/info?
<div id="1">
<p>A record</p>
<span id="1">A1</span>
<span id="2">A2</span>
</div>
<div id="2">
<p>C record</p>
<span id="3">C1</span>
</div>
Solution 1. Fine grained traversal. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="records">
<xsl:apply-templates select="*[1]"/>
</xsl:template>
<xsl:template match="record"/>
<xsl:template match="record[node()]">
<xsl:param name="pRecordNum" select="1"/>
<xsl:param name="pInfoNum" select="1"/>
<div id="{$pRecordNum}">
<xsl:apply-templates select="#*|*[1]">
<xsl:with-param name="pInfoNum" select="$pInfoNum"/>
</xsl:apply-templates>
</div>
<xsl:apply-templates select="following-sibling::record[node()][1]">
<xsl:with-param name="pRecordNum" select="$pRecordNum +1"/>
<xsl:with-param name="pInfoNum" select="$pInfoNum + count(info)"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="info">
<xsl:param name="pInfoNum"/>
<span id="{$pInfoNum}">
<xsl:value-of select="."/>
</span>
<xsl:apply-templates select="following-sibling::info[1]">
<xsl:with-param name="pInfoNum" select="$pInfoNum +1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#name">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
Output:
<div id="1">
<p>A record</p>
<span id="1">A1</span>
<span id="2">A2</span>
</div>
<div id="2">
<p>C record</p>
<span id="3">C1</span>
</div>
Solution 2: preceding axe. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="record"/>
<xsl:template match="record[node()]">
<div id="{count(preceding-sibling::record[node()])+1}">
<xsl:apply-templates select="#*|*"/>
</div>
</xsl:template>
<xsl:template match="info">
<span id="{count(preceding::info)+1}">
<xsl:value-of select="."/>
</span>
</xsl:template>
<xsl:template match="#name">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
Solution 3: With fn:position() and preceding axe. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="records">
<xsl:apply-templates select="record[node()]"/>
</xsl:template>
<xsl:template match="record">
<div id="{position()}">
<xsl:apply-templates select="#*"/>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="info">
<span id="{count(preceding::info)+1}">
<xsl:value-of select="."/>
</span>
</xsl:template>
<xsl:template match="#name">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
Note: You need a explict pull style.
Edit: Missed any level numbering for span/#id.
There is a short way to do this in XSLT. Use the <xsl:number> instruction:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record[info]">
<xsl:variable name="vPos">
<xsl:number count="record[info]"/>
</xsl:variable>
<div id="{$vPos}">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="info">
<xsl:variable name="vPos">
<xsl:number from="/" level="any" count="info" />
</xsl:variable>
<span id="{$vPos}"><xsl:apply-templates/></span>
</xsl:template>
<xsl:template match="record[not(info)]"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<data>
<records>
<record name="A record">
<info>A1</info>
<info>A2</info>
</record>
<record name="B record"/>
<record name="C record">
<info>C1</info>
</record>
</records>
</data>
the wanted, correct result is produced:
<data>
<records>
<div id="1">
<span id="1">A1</span>
<span id="2">A2</span>
</div>
<div id="2">
<span id="3">C1</span>
</div>
</records>
</data>
Related
I need to format some xml data with the following structure
<list>
<item>
Test
</item>
<item>
Testt
</item>
<or-item>
TestOr
</or-item>
<or-item>
TestOrr
</or-item>
<item>
Testtt
</item>
<or-item>
TestOrrr
</or-item>
<item>
Testttt
</item>
</list>
with xsl:number the or-item must be formatted with the second level count on that position. I know it would be better to structure the or-item inside that item but the data is given like that.
I need a way to count the or-item next to the current or-item to calculate the numbering for xsl:number
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.1"
xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output encoding="UTF-8" method="html" indent="yes"/>
<xsl:template match="list">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number count="item"/>
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number value="count(//or-item)" format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
</xsl:stylesheet>
Edit
I am using XSLT 1.1 with xsltproc on linux but 2.0 whould be possible if neccessary
As the target format is HTML, it seems you could rely on creating the appropriate nested HTML ordered lists by using xsl:for-each-group and group-starting-with="item":
<xsl:template match="list">
<ol>
<xsl:for-each-group select="*" group-starting-with="item">
<li>
<xsl:value-of select="."/>
<xsl:where-populated>
<ol>
<xsl:apply-templates select="tail(current-group())"/>
</ol>
</xsl:where-populated>
</li>
</xsl:for-each-group>
</ol>
</xsl:template>
<xsl:template match="or-item">
<li>
<xsl:value-of select="."/>
</li>
</xsl:template>
https://xsltfiddle.liberty-development.net/ejivJrM
That example uses some XSLT/XPath 3 stuff like were-populated and tail but in case that XSLT 2 compatility is needed then it could be replaced by <xsl:if test="subsequence(current-group(), 2)"><ol><xsl:apply-templates select="subsequence(current-group(), 2)"/></xsl:if>.
And of course the use of HTML ordered lists is not necessary, if needed/wanted you could just transform the input to nested divs with the used grouping approach and then in a second step use format-number as you seem to want to do:
<xsl:template match="list">
<xsl:variable name="nested-list">
<xsl:for-each-group select="*" group-starting-with="item">
<xsl:copy>
<xsl:value-of select="."/>
<xsl:copy-of select="tail(current-group())"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:variable>
<div>
<xsl:apply-templates select="$nested-list"/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number/>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
https://xsltfiddle.liberty-development.net/ejivJrM/1
You can produce the expected output by simply adjusting the xsl:number instruction:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/list">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number/>
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number level="any" from="item" format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
</xsl:stylesheet>
I have the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<Test xmlns="urn:hl7-org:v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:lab="urn:oid:7.6">
<section>
<id extension="something" root="testRoot"/>
<text>
<paragraph styleCode="somestyle">Soemthing</paragraph>
<table>
<thead>
<tr>
<th styleCode="styled">Style</th>
<th>Stuff</th>
</tr>
</thead>
</table>
</text>
</section>
</Test>
and this is the xslt:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:c="urn:hl7-org:v3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
exclude-result-prefixes="#all">
<xsl:template match="*">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match = "c:section/ c:text">
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<xsl:apply-templates/>
</div>
</text>
</xsl:template>
</xsl:stylesheet>
This is the output:
<?xml version="1.0" encoding="UTF-8"?><Test>
<section>
<id extension="something" root="testRoot"/>
<text><status value="generated"/><div xmlns="http://www.w3.org/1999/xhtml">
<paragraph xmlns="" styleCode="somestyle">Soemthing</paragraph>
<table xmlns="">
<thead>
<tr>
<th styleCode="styled">Style</th>
<th>Stuff</th>
</tr>
</thead>
</table>
</div></text>
</section>
</Test>
As you might expect, I don't want any of those empty namespaces like in paragraph and table. I want to get rid of them so that those elements are just as they are without any namespaces but the div element should retain the namespace.
EDIT: I also would like to get rid of the attributes (including their values) for all elements under text. So that final output looks like this:
<?xml version="1.0" encoding="UTF-8"?><Test>
<section>
<id extension="something" root="testRoot"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<paragraph>Soemthing</paragraph>
<table>
<thead>
<tr>
<th>Style</th>
<th>Stuff</th>
</tr>
</thead>
</table>
</div>
</text>
</section>
</Test>
I am guessing (!) you want to do:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:c="urn:hl7-org:v3"
exclude-result-prefixes="c">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="*" mode="xhtml" >
<xsl:element name="{local-name(.)}" namespace="http://www.w3.org/1999/xhtml">
<xsl:apply-templates select="#* | node()" mode="xhtml"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*" mode="#all">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="c:section/c:text">
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<xsl:apply-templates mode="xhtml"/>
</div>
</text>
</xsl:template>
</xsl:stylesheet>
This will place all descendants of div into the same xhtml namespace as their ancestor - resulting in:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<section>
<id extension="something" root="testRoot"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<paragraph styleCode="somestyle">Soemthing</paragraph>
<table>
<thead>
<tr>
<th styleCode="styled">Style</th>
<th>Stuff</th>
</tr>
</thead>
</table>
</div>
</text>
</section>
</Test>
Note that this is not valid XHTML markup.
Edit
I also would like to get rid of the attributes (including their
values) for all elements under text.
Well, then don't apply templates to them:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:c="urn:hl7-org:v3"
exclude-result-prefixes="c">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="*" mode="xhtml" >
<xsl:element name="{local-name(.)}" namespace="http://www.w3.org/1999/xhtml">
<xsl:apply-templates mode="xhtml"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="c:section/c:text">
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<xsl:apply-templates mode="xhtml"/>
</div>
</text>
</xsl:template>
</xsl:stylesheet>
I need nesting of list in the following manner, I have to create n-1 list wrappers, where n is the value of #virtual-nesting:
XML Doc:
<l virtual-nesting="3">
<li>
<lilabel/>
<p>
<text>Data used:</text>
</p>
</li>
</l>
Output Required:
<list>
<listitem>
<bodytext>
<list>
<listitem>
<label/>
<bodytext>
<p>
<text>Data used:</text>
</p>
</bodytext>
</listitem>
</list>
</bodytext>
</listitem>
</list>
XSLT Itried. As I am new to this:
<xsl:template match="l">
<xsl:choose>
<xsl:when test="#virtual-nesting">
<xsl:variable name="virtual"><xsl:value-of select="#virtual-nesting"/></xsl:variable>
<xsl:if test="$virtual>0">
<xsl:apply-templates select="generate-id(following-sibling::node()),#virtual-nesting-1"/>
</xsl:if>
<xsl:apply-templates select="li"/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="list">
<xsl:apply-templates select="li"/> <!-- Template for list and lilabel is already created -->
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Kindly guide me through this.
Recursion is the key to creating nested structures in XSLT.
The following uses a recursive <xsl:template match="l"> with a $level parameter. This parameter defaults to #virtual-nesting - 1 and after that is decremented with every recursive step.
The template takes two different paths, depending on its value.
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="l">
<xsl:param name="level" select="#virtual-nesting - 1" />
<list>
<listitem>
<xsl:if test="$level <= 1">
<xsl:apply-templates />
</xsl:if>
<xsl:if test="$level > 1">
<bodytext>
<xsl:apply-templates select=".">
<xsl:with-param name="level" select="$level - 1" />
</xsl:apply-templates>
</bodytext>
</xsl:if>
</listitem>
</list>
</xsl:template>
<xsl:template match="li">
<label>
<xsl:apply-templates select="lilabel/node()" />
</label>
<bodytext>
<xsl:apply-templates select="node()[not(self::lilabel)]" />
</bodytext>
</xsl:template>
</xsl:transform>
Result:
<list>
<listitem>
<bodytext>
<list>
<listitem>
<label/>
<bodytext>
<p>
<text>Data used:</text>
</p>
</bodytext>
</listitem>
</list>
</bodytext>
</listitem>
</list>
my templates (XSLT 1.0)
<xsl:template name="doo">
<xsl:variable name="nodelist">
<root>
<a size="12" number="11">
<sex>male</sex>
Hulk
</a>
<a size="12" number="11">
<sex>male</sex>
Steven XXXXXXXXXXX
</a>
</root>
</variable>
<xsl:call-template name="findString">
<xsl:with-param name="content1" select="$nodelist"></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="findString">
<xsl:param name="content1" select="."></xsl:param>
<!-- here i need to search the text() XXXXXXXXXXX from $content1 and replace them-->
</xsl:template>
is this possible like
for each node in Tree Fragment from myvariable
if node/text()='xxxxxxxx'
do something
With xslt version=1.0 you can use a extension "not-set".
<xsl:call-template name="findString">
<xsl:with-param name="content1" select="exsl:node-set($nodelist)"></xsl:with-param>
</xsl:call-template>
To make it woke you have to add following lines.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
version="1.0">
Update:
Based on solution from Mads Hansen
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:template name="doo">
<xsl:variable name="nodelist">
<root>
<a size="12" number="11">
<sex>male</sex>
Hulk
</a>
<a size="12" number="11">
<sex>male</sex>
Steven XXXXXXXXXXX
</a>
</root>
</xsl:variable>
aaa
<xsl:call-template name="findString">
<xsl:with-param name="content1" select="exsl:node-set($nodelist)"></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="doo" />
</xsl:template>
<xsl:template name="findString">
<xsl:param name="content1" select="."></xsl:param>
<!-- here i need to search the text() XXXXXXXXXXX from $content1 and replace them-->
<xsl:apply-templates mode="mytext" select="$content1"/>
</xsl:template>
<!--Identity template will copy all matched nodes and apply-templates-->
<xsl:template match="#*|node()" mode="mytext">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="mytext"/>
</xsl:copy>
</xsl:template>
<!--Specialized template to match on text nodes that contain the "findString" value-->
<xsl:template match="text()[contains(.,'XXXXXXXXXXX')]" mode="mytext">
<xsl:variable name="findString" select="'XXXXXXXXXXX'"/>
<xsl:variable name="replaceString" select="'YYYYYYYYYYYY'"/>
<xsl:value-of select="concat(substring-before(., $findString),
$replaceString,
substring-after(., $findString))"/>
</xsl:template>
</xsl:stylesheet>
XSLT available is 1.0.
The XML and XSLT below is for building a dropdown navigation menu for nested categories. The level of categories may vary.
Sample XML:
<data>
<categories-nav>
<section id="11" handle="categories-1">Categories 1</section>
<entry id="65">
<name handle="air-rifles">Air Rifles</name>
<subcategories field-id="50" subsection-id="12" items="2">
<item id="66" quantity="1">
<name handle="rifles">Rifles</name>
<active>Yes</active>
<subcategories field-id="57" subsection-id="13" items="2">
<item id="67" quantity="1">
<name handle="b2-series">B2 Series</name>
<active>Yes</active>
</item>
<item id="112" quantity="1">
<name handle="junior-supergrade">Junior Supergrade</name>
<active>Yes</active>
</item>
</subcategories>
</item>
<item id="111" quantity="1">
<name handle="accessories">Accessories</name>
<active>Yes</active>
<subcategories field-id="57" subsection-id="13" items="0" />
</item>
</subcategories>
</entry>
<entry id="118">
<name handle="pistols">Pistols</name>
</entry>
<entry id="58">
<name handle="bb-softair-guns">BB Softair Guns</name>
</entry>
</categories-nav>
</data>
My current XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="categories-nav-entries" mode="navigation">
<li class="{name/#handle}-{#id}">
<xsl:value-of select="name"/>
<xsl:apply-templates select="subcategories" mode="navigation"/>
</li>
</xsl:template>
<!-- level 1 -->
<xsl:template match="/data/categories-nav" mode="navigation">
<ul>
<xsl:apply-templates select="entry" mode="navigation"/>
</ul>
</xsl:template>
<xsl:template match="/data/categories-nav/entry" mode="navigation">
<xsl:call-template name="categories-nav-entries" mode="navigation"/>
</xsl:template>
<!-- level 2 -->
<xsl:template match="/data/categories-nav/entry/subcategories" mode="navigation">
<ul>
<xsl:apply-templates select="item" mode="navigation"/>
</ul>
</xsl:template>
<xsl:template match="/data/categories-nav/entry/subcategories/item" mode="navigation">
<xsl:call-template name="categories-nav-entries" mode="navigation"/>
</xsl:template>
<!-- level 3 -->
<xsl:template match="/data/categories-nav/entry/subcategories/item/subcategories" mode="navigation">
<ul>
<xsl:apply-templates select="item" mode="navigation"/>
</ul>
</xsl:template>
<xsl:template match="/data/categories-nav/entry/subcategories/item/subcategories/item" mode="navigation">
<xsl:call-template name="categories-nav-entries" mode="navigation"/>
</xsl:template>
</xsl:stylesheet>
The only difference between the matches for the three different levels is repeated subcategories and item nodes.
I've successfully got the lis in their own named template, but is there a way I can avoid matching the three levels separately?
Also, it seems that because I'm using a mode on the initial match, I also have to use that mode on all subsequent matches - is that correct?
Edit: here's what I came up with after using relative paths as #michael's answer:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="categories-nav-list">
<li class="{name/#handle}">
<xsl:value-of select="name"/>
<xsl:apply-templates select="subcategories[#items > 0]"/>
</li>
</xsl:template>
<xsl:template match="categories-nav">
<ul>
<xsl:apply-templates select="entry"/>
</ul>
</xsl:template>
<xsl:template match="categories-nav/entry">
<xsl:call-template name="categories-nav-list"/>
</xsl:template>
<xsl:template match="categories-nav//subcategories[item/active='Yes']">
<ul>
<xsl:apply-templates select="item"/>
</ul>
</xsl:template>
<xsl:template match="categories-nav//subcategories/item[active='Yes']" priority="1">
<xsl:call-template name="categories-nav-list"/>
</xsl:template>
<xsl:template match="categories-nav//subcategories/item" priority="0"/>
</xsl:stylesheet>
You don't need to put absolute paths in each #match attribute.
Why not something like this:
<xsl:template match="subcategories">
<ul>
<xsl:apply-templates select="item"/>
</ul>
</xsl:template>
<xsl:template match="item">
<xsl:call-template name="categories-nav-entries"/>
</xsl:template>