XSLT: Using xsl:key in nested XML structures - xslt

EXAMPLE
Here is an example from MSDN. It show how to transform a XML file into HTML using xsl:key.
Example XML (input)
<books>
<book title="XML Today" author="David Perry" release="2016"/>
<book title="XML and Microsoft" author="David Perry" release="2015"/>
<book title="XML Productivity" author="Jim Kim" release="2015"/>
</books>
Example XSL (input)
<xsl:key name="title-search" match="book" use="#author"/>
<xsl:template match="/">
<HTML>
<BODY>
<xsl:for-each select="key('title-search', 'David Perry')">
<DIV>
<xsl:value-of select="#title"/>
</DIV>
</xsl:for-each>
</BODY>
</HTML>
</xsl:template>
Example HTML (output)
<HTML>
<BODY>
<DIV>XML Today</DIV>
<DIV>XML and Microsoft</DIV>
</BODY>
</HTML>
MY PROBLEM
I would like to produce the same HTML output but using a different XML input. How should the corresponding XSL file look like?
My XML (input)
<books>
<book>
<a n="author"><s>David Perry</s></a>
<a n="title"><s>XML Today</s></a>
<a n="release"><i>2016</i></a>
</book>
<book>
<a n="author"><s>David Perry</s></a>
<a n="title"><s>XML and Microsoft</s></a>
<a n="release"><i>2015</i></a>
</book>
<book>
<a n="author"><s>Jim Kim</s></a>
<a n="title"><s>XML Productivity</s></a>
<a n="release"><i>2015</i></a>
</book>
</books>
My XSL (input)
???
My HTML (output)
<HTML>
<BODY>
<DIV>XML Today</DIV>
<DIV>XML and Microsoft</DIV>
</BODY>
</HTML>

In the first example, your key matched book elements by their author attribute, but in the new XML, you want to match them by the a element where the n attribute is "author", so the key looks like this.
<xsl:key name="title-search" match="book" use="a[#n='author']/s"/>
Then, to get the title for the a matched book you would do this...
<xsl:value-of select="a[#n='title']/s"/>
Therefore your XSLT would look like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" />
<xsl:key name="title-search" match="book" use="a[#n='author']/s"/>
<xsl:template match="/">
<HTML>
<BODY>
<xsl:for-each select="key('title-search', 'David Perry')">
<DIV>
<xsl:value-of select="a[#n='title']/s"/>
</DIV>
</xsl:for-each>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
You could actually drop the /s in the expressions here, if the s element was only ever going to be the only element under each a element.
This would work too:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" />
<xsl:key name="title-search" match="book" use="a[#n='author']"/>
<xsl:template match="/">
<HTML>
<BODY>
<xsl:for-each select="key('title-search', 'David Perry')">
<DIV>
<xsl:value-of select="a[#n='title']"/>
</DIV>
</xsl:for-each>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>

This is basic XSLT transformation, you can do something like this:
<xsl:template match="book">
<div>
Author: <xsl:value-of select="a[#n='author']/s" />
title: <xsl:value-of select="a[#n='title']/s" >
release: <xsl:value-of select="a[#n='release']/s" >
</div>
</xsl:template>

Related

XSLT: Iterating through all value of a xsl:key map

XML
<books>
<book title="XML Today" author="David Perry" release="2016"/>
<book title="XML and Microsoft" author="David Perry" release="2015"/>
<book title="XML Productivity" author="Jim Kim" release="2015"/>
</books>
The following XSL code iterates through all books by David Perry.
XSL
<xsl:key name="title-search" match="book" use="#author"/>
<xsl:template match="/">
<HTML>
<BODY>
<xsl:for-each select="key('title-search', 'David Perry')">
<DIV>
<xsl:value-of select="#title"/>
</DIV>
</xsl:for-each>
</BODY>
</HTML>
</xsl:template>
HTML output
<HTML>
<BODY>
<DIV>XML Today</DIV>
<DIV>XML and Microsoft</DIV>
</BODY>
</HTML>
Now I would like to iterate not only through all books by David Perry but through all books by any author.
How would a corresponding outer loop look like?
Or in other words: How do I iterate through all values of my title-search key.
The output should be something like this:
<HTML>
<BODY>
<H1>David Perry</H1>
<DIV>XML Today</DIV>
<DIV>XML and Microsoft</DIV>
<H1>Jim Kim</H1>
<DIV>XML Productivity</DIV>
</BODY>
</HTML>
This should do the job:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="title-search" match="book" use="#author"/>
<xsl:template match="/books">
<HTML>
<BODY>
<xsl:apply-templates select="book" />
</BODY>
</HTML>
</xsl:template>
<xsl:template match="book">
<xsl:variable name="author" select="#author" />
<xsl:if test="generate-id(.) = generate-id(key('title-search', $author)[1])">
<H1><xsl:value-of select="#author" /></H1>
<xsl:apply-templates select="//book[#author = $author]" mode="titles"/>
</xsl:if>
</xsl:template>
<xsl:template match="book" mode="titles">
<DIV>
<xsl:value-of select="#title"/>
</DIV>
</xsl:template>
</xsl:stylesheet>
It uses a technique called Muenchian grouping. Each element in an XML document implicitly has a unique ID assigned to it by the XSLT processor (it can also be explicitly assigned with the id attribute in the document itself). This part:
generate-id(.) = generate-id(key('title-search', $author)[1])
basically tests if the ID of the current book element is the same as that of the first book element with the same author. The variable $author is taken from the current book, The key is used to look up the <book> elements with that same author, the [1] predicate takes the first one. As a result, the <H1> is only generated for the first occurrence of that specific author, and in that same if element we're then applying the template for listing the books of that author. The mode is used to avoid a clash between these templates. There's no doubt a solution that doesn't use modes, but this works too. You could also do a lot of this with <xsl:for-each> but I made separate templates because XSLT is declarative and works best when treating it as such.
Grouping is a lot easier in XSLT 2, but when stuck with XSLT 1, the Muenchian grouping technique often provides a solution once you grok it.
Try this:
XSLT2:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<HTML>
<BODY>
<xsl:for-each-group select="books/book" group-by="#author">
<H1><xsl:value-of select="current-grouping-key()"/></H1>
<xsl:for-each select="current-group()">
<DIV><xsl:value-of select="#title"/></DIV>
</xsl:for-each>
</xsl:for-each-group>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>

XSLT, I'm studying with my own XSLT example, but it doesn't work as I expected

First, my code is..
<?xml version="1.0" encoding="UTF-8"?>
<mpml>
<problem>
<context>
<p>두 다항식 $A=x^2 - xy + 2y^2$, $B=3x^2 + 2xy - y^2$에 대하여 $A-B$를 계산한 식이 $ax^2 +bxy + cy^2$일 때, 상수 $a+b+c$의 값은?</p>
</context>
<answerlist>
<i>-4</i>
<i>-2</i>
<i>0</i>
<i>2</i>
<i>4</i>
</answerlist>
</problem>
<problem>
<context>
<p>연립방정식 $\begin{cases} x+y+z=30 \\ 2x+3y+4z=93 \\ y=z+3 \end{cases}$의 해를 $x=a$, $y=b$, $z=c$라 할 때, $a-2b+3c$의 값은? (단, $a$, $b$, $c$는 실수.)</p>
</context>
<answerlist>
<i>7</i>
<i>9</i>
<i>11</i>
<i>13</i>
<i>15</i>
</answerlist>
</problem>
</mpml>
and it's xsl code is..
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<h2> Testing.. </h2>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="problem">
<div class="problem">
<xsl:apply-templates select="context"/>
<xsl:apply-templates select="answerlist"/>
</div>
</xsl:template>
<xsl:template match="context">
<div class="context">
</div>
</xsl:template>
<xsl:template match="answerlist">
<div class="answerlist">
test answerlist
</div>
</xsl:template>
</xsl:transform>
In XSLT Tryit editor (thanks to w3school), above XSLT work as I expected. However when I tried this in my server, there is an 'extra content at the end of the document.' error and nothings shown except very first template.
I think the problem is that your XSLT does not produce well-formed XML in output: in the template <xsl:template match="/">, you should wrap content within a single root tag.
E.g.:
<xsl:template match="/">
<body>
<h2> Testing.. </h2>
<xsl:apply-templates/>
</body>
</xsl:template>

catch the first occurrence value

I've the below XML.
<chapter num="1">
<section level="sect2">
<page>22</page>
</section>
<section level="sect3">
<page>23</page>
</section>
</chapter>
here I'm trying to get the first occurrence of <page>.
I'm using the below XSLT.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ntw="Number2Word.uri" exclude-result-prefixes="ntw">
<xsl:output method="html"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="ThisDocument" select="document('')"/>
<xsl:template match="/">
<xsl:text disable-output-escaping="yes"><![CDATA[<!DOCTYPE html>]]></xsl:text>
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="chapter">
<section class="tr_chapter">
<xsl:value-of select="//page[1]/text()"/>
<div class="chapter">
</div>
</section>
</xsl:template>
</xsl:stylesheet>
but the output that I get all the page valyes printed. I only want the first one.
Current output.
<!DOCTYPE html>
<html>
<body>
<section class="tr_chapter">2223
<div class="chapter">
</div>
</section>
</body>
</html>
the page values are printed here after <section class="tr_chapter">, i want only 22 but I'm getting 2223
here I'm using //page[1]/text(), because I'm not sure that the page comes within the section, it is random.
please let me know how I can get only the first page value.
here is the transformation http://xsltransform.net/3NzcBsR
Try:
<xsl:value-of select="(//page)[1]"/>
http://xsltransform.net/3NzcBsR/1
Note that this gets the value of the first page element in the entire document.
If you want to search the contents of the chapter context element in your template for the first page descendant then use <xsl:value-of select="descendant::page[1]"/> or <xsl:value-of select="(.//page)[1]"/>.

insert into cdata text from xml node usning xslt

My scenario is that we have to check for a div tag in the cdata of a body element. If div is present we have to insert the the text from node2 into the div tag.
This is my input xml:
<?xml version="1.0" encoding="utf-8"?>
<root>
<node1>abc</node1>
<node2> needs to replace inside cdata div</node2>
<body> <![CDATA[
<p>some text some textabcabcabcabc</p>
<div class="marginBottom_4px">
</div>
<p>some text some textabcabc</P>
]]>
</body>
</root>
The out put xml would be:
<?xml version="1.0" encoding="utf-8"?>
<div class="marginBottom_10px">
abc
</div>
<div class="marginBottom_5px">
<p>some text some textabcabcabcabc</p>
<div class="marginBottom_4px">
needs to replace inside cdata div
</div>
<p>some text some textabcabc</P>
</div>
My transform is:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:value-of disable-output-escaping="yes" select ="$firstnode"/>
<xsl:text disable-output-escaping="yes"><![CDATA[ <div class="marginBottom_10px">
]]>
</xsl:text>
<xsl:value-of disable-output-escaping ="yes" select="root/body"/>
<xsl:text disable-output-escaping="yes"><![CDATA[
</div>
]]>
</xsl:text>
</xsl:template>
<xsl:variable name="firstnode">
<xsl:text disable-output-escaping="yes"><![CDATA[
<div class="marginBottom_10px">
]]>
</xsl:text>
<xsl:value-of disable-output-escaping ="yes" select="root/node1"/>
<xsl:text disable-output-escaping="yes"><![CDATA[
</div>
]]>
</xsl:text>
</xsl:variable>
</xsl:stylesheet>
I am able to produce the out put. but my xml is very complex like below:
<?xml version="1.0" encoding="utf-8" ?>
<ComplexXML>
<environment>
couple of nodes..
</environment>
<document>
nodes
</document>
<element cd="dsjdhfjk" input="abc.xml" mode="" >
<cd position="1">
<attributes>
<type>dummy text</type>
<title>dummy text</title>
</attributes>
<content>
<node2>
<![CDATA[
needs to replace inside cdata div
]]>
</node2>
<body>
<![CDATA[
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,
when an unknown printer took a galley of type and scrambled it to make a type
specimen book </p>
<div class="marginBottom_4px">
</div>
<p>Lorem Ipsum is simply dummy text of her including versions of Lorem Ipsum. </p>
]]>
</body>
<abt >
<![CDATA[
text from abt node
]]>
</abt>
</content>
</cd>
</element>
</ComplexXML>
In the above xml I have to check for the abt node.If data is there in abt node the out should be like below:
<?xml version="1.0" encoding="UTF-8"?>
<div>
text from abt node
<div class="marginBottom_5px">
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,
when an unknown printer took a galley of type and scrambled it to make a type
specimen book </p>
**<div class="marginBottom_4px">
</div>** I need to remove this div tag and place the node2 content here.
<p>Lorem Ipsum is simply dummy text of her including versions of Lorem Ipsum. </p>
</div>
</div>
Sorry to bother you..I am very new to xslt..I am in learning stage only..Can you please guide me..
The following XSLT 1.0 stylesheet produces what I believe the desired output is, based upon the example output and comments. It relies upon the text in the CDATA of the input document being well-formed, and leverages disable-output-escaping:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="node1">
<div class="marginBottom_10px">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="node2" />
<xsl:template match="body">
<div class="marginBottom_5px">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="body/text()[contains(.,'<div') and contains(.,'</div>')]">
<xsl:value-of
disable-output-escaping="yes"
select="substring-before(.,'</div')" />
<xsl:value-of select="../../node2"/>
<xsl:value-of
disable-output-escaping="yes"
select="substring-after(.,substring-before(.,'</div'))" />
</xsl:template>
</xsl:stylesheet>
When applied against the example input, produces:
<?xml version="1.0" encoding="UTF-8"?>
<div class="marginBottom_10px">abc</div>
<div class="marginBottom_5px">
<p>some text some textabcabcabcabc</p>
<div class="marginBottom_4px">
needs to replace inside cdata div</div>
<p>some text some textabcabc</P>
</div>
Note: the output is not well-formed. There is no document element. You would likely want to create a template for the <root> to create a <div> or other containing element.
This version handles the other input format and generates what I believe the desired output is:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<!--rely on built-in templates,
which apply-templates to child nodes of elements
and get value-of for text() -->
<xsl:template match="content">
<xsl:choose>
<!-- if <abt> has a value, do the following -->
<xsl:when test="normalize-space(abt)">
<div>
<!-- apply templates to <abt>,
built-in template will copy text to output-->
<xsl:apply-templates select="abt"/>
<!-- apply templates to <body>, template defined below will handle it -->
<xsl:apply-templates select="body"/>
</div>
</xsl:when>
<xsl:otherwise>
<!--process child nodes -->
<xsl:apply-templates />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="node1">
<div class="marginBottom_10px">
<xsl:apply-templates/>
</div>
</xsl:template>
<!--empty template ensures that no content produced when templates applied to <node2>-->
<xsl:template match="node2" />
<xsl:template match="body">
<div class="marginBottom_5px">
<xsl:apply-templates/>
</div>
</xsl:template>
<!--Template for handling body/text() when <abt> does not have a value-->
<xsl:template match="*[not(normalize-space(abt))]/body/text()[contains(.,'<div') and contains(.,'</div>')]">
<!--get the value of content preceding "</div"-->
<xsl:value-of
disable-output-escaping="yes"
select="substring-before(.,'</div')" />
<!--get the value of <node2> -->
<xsl:value-of select="../../node2"/>
<!--get the value of content starting at "</div" -->
<xsl:value-of
disable-output-escaping="yes"
select="substring-after(.,substring-before(.,'</div'))" />
</xsl:template>
<!--Template for handling body/text() when <abt> does have a value -->
<xsl:template match="*[normalize-space(abt)]/body/text()[contains(.,'<div') and contains(.,'</div>')]">
<!--get the value preceding "<div" -->
<xsl:value-of
disable-output-escaping="yes"
select="substring-before(.,'>div')" />
<xsl:value-of select="../../node2"/>
<!--get the value following "</div>" -->
<xsl:value-of
disable-output-escaping="yes"
select="substring-after(.,'</div>')" />
</xsl:template>
</xsl:stylesheet>

How to Import stylesheets in xslt conditionally?

Is there any way to import stylesheets after checking some conditions?
Like,if the value of variable $a="1" then import 1.xsl or else import 2.xsl.
Hi All, Is there any way to import
stylesheets after checking some
conditions?
Like,if the value of variable $a="1"
then import 1.xsl or else import
2.xsl.
No, the <xsl:import> directive is only compile-time.
In XSLT 2.0 one can use the use-when attribute for a limited conditional compilation.
For example:
<xsl:import href="module-A.xsl"
use-when="system-property('xsl:vendor')='vendor-A'"/>
The limitations of the use-when attribute are that there is no dynamic context when the attribute is evaluated -- in particular that means that there are no in-scope variables defined.
A non-XSLT solution is to dynamically change the href attribute of the <xsl:import> declaration before the transformation is invoked:
Parse the xsl stylesheet as an XML file
Evaluate the condition that determines which stylesheet should be imported.
Set the value of the href attribute of the <xsl:import> declaration to the URI of the dynamically determined stylesheet-to-be-imported.
Invoke the transformation with the in-memory xsl stylesheet that was just modified.
I know this post is old, but I want to share my opinion.
Each display could use one template instead of two. The value display will be change with a VB application.
breakfast_menu.xml:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="conditionDisplay.xsl" ?>
<data>
<breakfast_menu>
<food>
<name>Belgian Waffles</name>
<price>$5.95</price>
<description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
<calories>650</calories>
</food>
<food>
<name>Strawberry Belgian Waffles</name>
<price>$7.95</price>
<description>Light Belgian waffles covered with strawberries and whipped cream</description>
<calories>900</calories>
</food>
<food>
<name>Homestyle Breakfast</name>
<price>$6.95</price>
<description>Two eggs, bacon or sausage, toast, and our ever-popular hash browns</description>
<calories>950</calories>
</food>
</breakfast_menu>
<display>1</display>
</data>
In this file, I imported my displays and with a condition I tell the template what I need.
conditionDisplay.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:import href="display1.xsl"/>
<xsl:import href="display2.xsl"/>
<xsl:template match="/">
<xsl:variable name="display"><xsl:value-of select= "data/display"/></xsl:variable>
<xsl:choose>
<xsl:when test="$display='1'">
<xsl:call-template name="display1" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="display2 />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
display1.xsl:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="display1">
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
<xsl:for-each select="data/breakfast_menu/food">
<div style="background-color:teal;color:white;padding:4px">
<span style="font-weight:bold"><xsl:value-of select="name"/> - </span>
<xsl:value-of select="price"/>
</div>
<div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
<p>
<xsl:value-of select="description"/>
<span style="font-style:italic"> (<xsl:value-of select="calories"/> calories per serving)</span>
</p>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
display2.xsl:
<?xml version="1.0" encoding="UTF-8"?>futur
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="display2">
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<body style="font-family:Arial;font-size:12pt;background-color:#222222">
<xsl:for-each select="data/breakfast_menu/food">
<div style="background-color:teal;color:white;padding:4px">
<span style="font-weight:bold"><xsl:value-of select="name"/> - </span>
<xsl:value-of select="price"/>
</div>
<div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
<p>
<xsl:value-of select="description"/>
<span style="font-style:italic"> (<xsl:value-of select="calories"/> calories per serving)</span>
</p>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
I genuinely apologize for my terrible English. It will be better to the next post and I hope will help someone as I think it's not the best solution.