XSLT calling template inside of an HTML - html

If someone would be kind enough to tell me why the following <xsl:call-template name="Log"> won't work?
XML File:
<?xml version="1.0" encoding="UTF-8"?>
<TextMessages>
<Message>[Step]</Message>
<Message>Step ID: 1</Message>
<Message>Description</Message>
</TextMessages>
XSLT File:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection ></h2>
<p>
<xsl:call-template name="Log">
</xsl:call-template>
</p>
</body>
</html>
</xsl:template>
<xsl:template name ="Log">
<xsl:variable name="break"><br/></xsl:variable>
<xsl:for-each select="TextMessages">
<p>
<xsl:value-of select="."/>
<xsl:value-of select="$break"/>
</p>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The wanted output:
My CD Collection >
[Step]<br/>
Step ID: 1<br/>
Description<br/>
The real problem has also parameters which I call with parameters, but not sure if that is the problem.
<p>
<xsl:call-template name="Log">
<xsl:with-param name="testId" select="#testId" />
</xsl:call-template>
</p>
<xsl:template name ="Log">
<xsl:param name="testId" />
<xsl:variable name="break"><br/></xsl:variable>
<xsl:for-each select="/t:TestRun/t:Results/t:UnitTestResult[#testId=$testId]/t:Output/t:TextMessages/t:Message">
<p>
<xsl:value-of select="."/>
<xsl:value-of select="$break"/>
</p>
</xsl:for-each>
</xsl:template>

If i am guessing correctly, you want to change:
<xsl:template name ="Log">
<xsl:variable name="break"><br/></xsl:variable>
<xsl:for-each select="TextMessages">
<p>
<xsl:value-of select="."/>
<xsl:value-of select="$break"/>
</p>
</xsl:for-each>
</xsl:template>
to:
<xsl:template name="Log">
<xsl:for-each select="TextMessages/Message">
<p>
<xsl:value-of select="."/>
</p>
<hr/>
</xsl:for-each>
</xsl:template>
Note:
You should never have to use a hack like <br/> to output HTML or XML valid markup;
I am not sure why you need to call a named template here, instead of simply including the xsl:for-each in the first template, or just applying templates to the Message elements.

Adding the line
<xsl:output method="text" version="1.0" encoding="UTF-8" />
to your XSLT file directly after the <xsl:stylesheet... > line will you the (partly desired) result of
My CD Collection >
[Step]
Step ID: 1
Description
With "partly" I do refer to the first line - which you explicitly want to be output by including <h2>My CD Collection ></h2> in your XSLT - but not mention in your output text.

Related

Handle excluded element in a foreach

In first template, I am intentionally excluding an element ('milk') because the parsed data map is relatively flat and I would like to use XSLT to categorize and structure the data. The aim is to process the excluded element ('milk') in the second template. The both templates works running them one at a time. Running the templates together will not show the result of the excluded element ('milk') which should set another attribute name and attribute value.
JSON:
<data>
{
"storage": {
"pencils": 12,
"milk": 8,
"rulers": 4
}
}
</data>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform
version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:storage="http://www.exammple.com/1"
xmlns:office="http://www.exammple.com/2"
xmlns:item="http://www.exammple.com/3"
expand-text="yes">
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-skip"/>
<!-- Parse JSON to XML -->
<xsl:template match="data">
<storage:one>
<xsl:apply-templates select="json-to-xml(.)"/>
</storage:one>
</xsl:template>
<!-- Print map -->
<!-- <xsl:template match="*[#key = 'storage']"> <xsl:copy-of select=".."/> </xsl:template> -->
<xsl:template match="*[#key='storage']">
<xsl:for-each select="*[not(#key='milk')]">
<xsl:element name="item:{#key}">
<xsl:attribute name="office">plant-1</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each>
</xsl:template>
<xsl:template match="*[#key='milk']">
<xsl:for-each select=".">
<xsl:element name="item:{#key}">
<xsl:attribute name="beverage">plant-2</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:transform>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<storage:one xmlns:item="http://www.exammple.com/3"
xmlns:office="http://www.exammple.com/2"
xmlns:storage="http://www.exammple.com/1">
<item:pencils office="plant-1">12</item:pencils>
<item:rulers office="plant-1">4</item:rulers>
</storage:one>
Wanted result:
<?xml version="1.0" encoding="UTF-8"?>
<storage:one xmlns:item="http://www.exammple.com/3"
xmlns:office="http://www.exammple.com/2"
xmlns:storage="http://www.exammple.com/1">
<item:pencils office="plant-1">12</item:pencils>
<item:rulers office="plant-1">4</item:rulers>
<item:milk beverage="plant-2">8</item:milk>
</storage:one>
Your second template is never matched, because it is never reached. All elements are processed by <xsl:template match="*[#key='storage']"> - which doesn't have an <xsl:apply-templates ...> to reach further templates.
Your first template does not recurse into its children. So add an <xsl:apply-templates select="*" /> to the end of the first template:
<xsl:template match="*[#key='storage']">
<xsl:for-each select="*[not(#key='milk')]">
<xsl:element name="item:{#key}">
<xsl:attribute name="office">plant-1</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each>
<xsl:apply-templates select="*" />
</xsl:template>
This will try to apply further templates at the level of "storage" and therefore match the second template.
I would write templates for each different output type and if the order of the output is different from the order of the input throw in an xsl:sort or an XPath 3.1 sort call to change the order:
<xsl:template match="data">
<storage:one>
<xsl:apply-templates select="json-to-xml(.)"/>
</storage:one>
</xsl:template>
<xsl:template match="*[#key = 'storage']">
<xsl:apply-templates select="sort(*, (), function($el) { $el/#key = 'milk' })"/>
</xsl:template>
<xsl:template match="*[#key='storage']/*[not(#key='milk')]">
<xsl:element name="item:{#key}">
<xsl:attribute name="office">plant-1</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="*[#key='storage']/*[#key='milk']">
<xsl:element name="item:{#key}">
<xsl:attribute name="beverage">plant-2</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:template>

New Lines to HTML Line Breaks Using XSLT

I have the following XML:
<RichText>
Text
Text
Text
</RichText>
I would like to output the following HTML using XSLT 1.0 (2.0 if I really really have to):
<p>
Text<br/>
Text<br/>
Text
</p>
I've tried using the following XSL which gets close:
<xsl:template match="text()">
<xsl:param name="text" select="."/>
<!-- Because we would rely on $text containing a line break when using
substring-before($text,'
') and the last line might not have a
trailing line break, we append one before doing substring-before(). -->
<xsl:value-of select="substring-before(concat($text,'
'),'
')"/>
<br/>
<xsl:if test="contains($text,'
')">
<xsl:apply-templates select=".">
<xsl:with-param name="text" select="substring-after($text,'
')"/>
</xsl:apply-templates>
</xsl:if>
<xsl:template>
This outputs:
<p><br>
Text<br>
Text<br>
Text<br>
<br></p>
For your XSLT 1.0 solution, I think all you need is some xsl:if tests to test if there is non-white space text before and after the current line you are handling.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" />
<xsl:template match="RichText">
<p><xsl:apply-templates /></p>
</xsl:template>
<xsl:template match="text()">
<xsl:param name="text" select="."/>
<xsl:variable name="startText" select="substring-before(concat($text,'
'),'
')" />
<xsl:variable name="nextText" select="substring-after($text,'
')"/>
<xsl:if test="normalize-space($startText)">
<xsl:value-of select="$startText"/>
<xsl:if test="normalize-space($nextText)">
<br />
</xsl:if>
</xsl:if>
<xsl:if test="contains($text,'
')">
<xsl:apply-templates select=".">
<xsl:with-param name="text" select="$nextText"/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Try the following script:
<?xml version="1.0" encoding="UTF-8" ?>
<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:template match="RichText">
<p>
<xsl:variable name="txt" select="tokenize(., '
')"/>
<xsl:variable name="len" select="count($txt)"/>
<xsl:for-each select="subsequence($txt, 2, $len - 2)">
<xsl:value-of select="replace(replace(.,'\s+$',''),'^\s+','')"/>
<xsl:if test="position() < last()">
<xsl:text disable-output-escaping="yes"><br/></xsl:text>
</xsl:if>
<xsl:text>
</xsl:text>
</xsl:for-each>
</p>
</xsl:template>
</xsl:transform>
I used XSLT 2.0, as a version MUCH simpler to write.
It is possible to rewrite it using XSLT 1.0, but you must use equivalent
1.0 solutions for:
tokenization,
subsequence,
and trimming.

XSLT handling text with tag in between

I have the following XML: <a>Text with <b>stuff</b> here</a>
With my code:
<xsl:template match="*[local-name() = 'a'][namespace-uri()=namespace-uri(.)]">
<xsl:value-of select="normalize-space(text()) "/><xsl:text> </xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*[local-name() = 'b'][namespace-uri()=namespace-uri(.)]">
<xsl:value-of select="normalize-space(text())"/><xsl:text> </xsl:text>
<xsl:apply-templates/>
</xsl:template>
I only get the result:
Text with stuff
What I want is:
Text with stuff here.
So how do I handle the remaining text after the <b/> element?
Why is this so complicated? If this is really your XML input:
<a>Text with <b>stuff</b> here</a>
then the following stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:value-of select="a" />
</xsl:template>
</xsl:stylesheet>
will return the requested* result:
Text with stuff here
--
(*) except for the period at the end, which is not present in the input.

How to call template with name as a variable in XSLT?

I have the following XML document which needs to be parsed with an XSLT to HTML.
<root>
<c>
<c1>
<id>1</id>
<text>US</text>
</c1>
<c1>
<id>2</id>
<text>UK</text>
</c1>
</c>
</root>
The XSLT for converting this to HTML is given below.
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" />
<xsl:template match="root">
<html>
<xsl:for-each select="c/c1">
**<xsl:variable name="vTemplate" select="text"/>
<xsl:apply-templates select="$vTemplate[#name='text'"/>**
</xsl:for-each>
</html>
</xsl:template>
<xsl:template match="xsl:template[#name='text']" name="text">
<select>
<xsl:attribute name="id">
<xsl:value-of select="id"/>
</xsl:attribute>
</select>
</xsl:template>
</xsl:stylesheet>
I need to call a template depends up on the text field. So for the value US, one template will be executed and for UK, another will executed.
How to achieve this with a variable as a template name while calling the template? I just made a try but it gives error. Can someone help me to figure out where i made wrong?
I think it is not possible to choose name of template to be called dynamically. What could be done is xsl:choose utilization (perhaps with combination with mode attribute), like this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<html>
<xsl:for-each select="c/c1">
<xsl:choose>
<xsl:when test="text = 'US'">
<xsl:apply-templates select="text" mode="US"/>
</xsl:when>
<xsl:when test="text = 'UK'">
<xsl:apply-templates select="text" mode="UK"/>
</xsl:when>
<xsl:otherwise>
<xsl:comment>Something's wrong</xsl:comment>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</html>
</xsl:template>
<xsl:template match="text" mode="US">
<xsl:comment>US mode</xsl:comment>
<select>
<xsl:attribute name="id">
<xsl:value-of select="preceding-sibling::id"/>
</xsl:attribute>
</select>
</xsl:template>
<xsl:template match="text" mode="UK">
<xsl:comment>UK mode</xsl:comment>
<select>
<xsl:attribute name="id">
<xsl:value-of select="preceding-sibling::id"/>
</xsl:attribute>
</select>
</xsl:template>
</xsl:stylesheet>
Or you can use match with appropriate predicate and avoid for-each like this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<html>
<xsl:apply-templates select="//c1" />
</html>
</xsl:template>
<xsl:template match="c1[text = 'US']">
<xsl:comment>US mode</xsl:comment>
<select id="{id}" />
</xsl:template>
<xsl:template match="c1[text = 'UK']">
<xsl:comment>UK mode</xsl:comment>
<select id="{id}" />
</xsl:template>
</xsl:stylesheet>
The id attribute of select can be also filled by "Attribute value templates" (xpath in curly brackets) as shown in previous sample.

How this style sheet can produce HTML output

How to complete the following style sheet that should produce HTML output in figure 1.
I have styled some part of it but could not make it exactly the same as in figure 1. I have tried "copy element" in XSL but gave me duplicate results.
This is my XML:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="research.xsl"?>
<ResearchGroups xmlns="http://www.sam.com/sam.html">
<Group name="Intelligent Systems Group" id="ISG"> The Intelligent
Systems Group pursues internationally-leading research in a wide
range of intelligent systems. </Group>
<Group name="Robotics" id="RBT"> The Essex robotics group is one of
the largest mobile robotics groups in the UK. </Group>
<Staff name="Callaghan, Vic" title="Professor" groups="ISG RBT">
Intelligent environments and robotics. </Staff>
<Staff name="Gu, Dongbing" title="Dr" groups="RBT"> Multi-agent
and distributed control systems. </Staff>
</ResearchGroups>
My XSLT:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rg="http://www.sam.com/sam.html"
xmlns="http://wwww.w3.org/1999/xhtml"
version="2.0"> <xsl:template match="/">
<html>
<head> <title>Research Groups</title> </head>
<body> <xsl:apply-templates select="//rg:Group"/> </body>
</html> </xsl:template> <xsl:template match="rg:Group">
<xsl:variable name="ID" select="#id"/>
<h3> <a name="{$ID}"> <xsl:value-of select="#name"/> </a> </h3>
<p> <xsl:value-of select="text()"/> </p>
</xsl:template>
</xsl:stylesheet>
HTML Figure 1
the XSLT style sheet should output the following HTML
Something like this I think:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rg="http://www.sam.com/sam.html" xmlns="http://wwww.w3.org/1999/xhtml" version="2.0">
<xsl:template match="/">
<html>
<head>
<title>Research Groups</title>
</head>
<body>
<xsl:apply-templates select="//rg:Group"/>
</body>
</html>
</xsl:template>
<xsl:template match="rg:Group">
<xsl:variable name="ID" select="#id"/>
<h3>
<a name="{$ID}">
<xsl:value-of select="#name"/>
</a>
</h3>
<p>
<xsl:value-of select="text()"/>
</p>
<ul>
<xsl:apply-templates select="//rg:Staff[contains(#groups, current()/#id)]">
<xsl:with-param name="curGroup"><xsl:value-of select="#id"/></xsl:with-param>
</xsl:apply-templates>
</ul>
</xsl:template>
<xsl:template match="rg:Staff">
<xsl:param name="curGroup"/>
<li>
<xsl:value-of select="#name"/>
<xsl:text>: </xsl:text>
<xsl:value-of select="text()"/>
<xsl:if test="//rg:Group[(#id != $curGroup) and contains(current()/#groups, #id)]">
<xsl:text> ( </xsl:text>
<xsl:apply-templates select="//rg:Group[(#id != $curGroup) and contains(current()/#groups, #id)]" mode="otherGroups"/>
<xsl:text> ) </xsl:text>
</xsl:if>
</li>
</xsl:template>
<xsl:template match="rg:Group" mode="otherGroups">
<a href="#{#id}">
<xsl:value-of select="#id"/>
</a>
</xsl:template>
</xsl:stylesheet>