output for all nodes in the tree - xslt

I'm very new to XSL and I want to print out all element nodes by name in a tree structure. So that:
<root>
<childX>
<childY1/>
<childY2/>
</childX>
<childX2/>
</root>
will yield:
root
+--childX
+--childY1
+--childY2
+--childX2
I tried some loops, but probably need recursion....
what I have so far:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="root" match="/">
<html>
<xsl:for-each select="*">
<xsl:value-of select="local-name()"/><br/>
+-- <xsl:for-each select="*">
<xsl:value-of select="local-name()"/><br/>
</xsl:for-each>
</html>
</xsl:template>
would be awesome if you could gimme some hints.
thanks!

Recursion is done using apply-templates, I am not sure it is a good idea to output HTML and then try to construct a tree structure as plain text (creating a nested list in HTML seems more appropriate) but here you go:
<xsl:template match="/">
<html>
<head>
<title>Example</title>
</head>
<body>
<pre>
<xsl:apply-templates/>
</pre>
</body>
</html>
</xsl:template>
<xsl:template match="*">
<xsl:param name="indent" select="'--'"/>
<xsl:value-of select="concat('+', $indent, ' ', local-name())"/>
<br/>
<xsl:apply-templates select="*">
<xsl:with-param name="indent" select="concat('--', $indent)"/>
</xsl:apply-templates>
</xsl:template>

Related

XSL - How to ouput nested text-elements at the correct position

I am trying to create an xsl-stylesheet that outputs my xml-contents in the correct order.
Here is an example:
XML:
...<p>This is<mark> a nested <b>text</b></mark></p>...
XSL:
<?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="html" indent="yes"/>
<xsl:template match="/">
<html>
<body>
<h2>
<xsl:value-of select="html/head/title"/>
</h2>
<div style="border:1px solid black;margin:30px;padding:30px;box-sizing:border-box;">
<xsl:for-each select="html/body/div[#class='toc']/table/tr/td/a">
<p><a>
<xsl:attribute name="href" namespace="uri">
<xsl:value-of select="current()/#href"/>
</xsl:attribute>
<xsl:value-of select="current()"/>
</a></p>
</xsl:for-each>
</div>
<xsl:for-each select="html/body/div[#class='chapter']">
<div style="border:1px solid black;margin:30px;padding:30px;box-sizing:border-box;">
<xsl:attribute name="id" namespace="uri"><xsl:value-of select ="current()/#id"/></xsl:attribute>
<p><xsl:value-of select ="current()/#id"/></p>
<xsl:call-template name="rec">
<xsl:with-param name="parents" select="current()"/>
</xsl:call-template>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:template name="rec">
<xsl:param name="parents"></xsl:param>
<xsl:for-each select="$parents/*">
<xsl:if test="name() = 'img'">
<img class="{#class}" src="{#src}" style="max-width:100%;"/>
</xsl:if>
<xsl:if test="name() != 'img'">
<xsl:element name="{local-name()}">
<xsl:if test="name() != 'figure'">
<xsl:value-of select ="current()"/>
</xsl:if>
<xsl:call-template name="rec">
<xsl:with-param name="parents" select="current()"/>
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This outputs:
This is a nested text
a nested text
text
What I am trying to get:
<p>This is<mark> a nested <b>text</b></mark></p>
I have tried just to include a CSS-Stylesheet (which would get rid of this particular problem), however this does not seem to work with images (e.g.), which won´t be displayed but will occure inside most documents.
The XSL-Stylesheet is supposed to be working with multiple documents (I wrote an exporter, that creates xml-files, that roughly follow the same syntax). The important part should only be the recursive function inside <xsl:template name="rec">.
Help would be greatly appreciated. Thanks!
Basic push style, structure and order preserving processing usually relies on the identity transformation template plus custom templates for each node you need to transform e.g.
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="img">
<img class="{#class}" src="{#src}" style="max-width:100%;"/>
</xsl:template>
The duplicated text in your wrong output is created by the repeated use of xsl:value-of in the recursive, named template. If you treat text as nodes and let any copying be handled through adequate templates, like the identity transformation template, you don't output text values several times.

XSLT/XPATH : process XHTML files to convert delimited text portions into new <SPAN> class

I got a bunch of files testX.xhtml that are edited in a browser using contenteditable=true. The purpose of the edit is to delimit portions of text with two identical characters like the underscore character in this xhtml file :
{xhtml source file} :
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<style xmlns:xhtml="http://www.w3.org/1999/xhtml" type="text/css" xml:space="preserve"/>
<meta content="text/html;charset=UTF-8" http-equiv="Content-Type"/>
<title>title XHTML</title>
</head>
<body>
<span class="ok">_blablabla blebleble_ bliblibli</span>
<p class="ko">blablabla _blebleble bliblibli <em class="em">one em tag</em> blablabla blebleble._</p>
</body>
</html>
The edited file is saved and then processed by the following xslt in order to have the tagged portion embedded in a new span class named my_span for further treatment :
{xslt file} :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xpath-default-namespace="http://www.w3.org/1999/xhtml"
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:output method="xhtml" version="1.0" encoding="UTF-8" indent="yes" standalone="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:for-each select="collection('?select=test*.xhtml')">
<xsl:variable name="path_to_span">
<xsl:value-of select="iri-to-uri(replace(document-uri(current()), '.xhtml', '.span.xhtml'))"/>
</xsl:variable>
<xsl:result-document indent="yes" method="xhtml" href="{$path_to_span}">
<xsl:apply-templates/>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
<xsl:template match="//text()">
<xsl:analyze-string select="." regex="(.*?)_(.*?)_">
<xsl:matching-substring>
<xsl:value-of select="regex-group(1)"/>
<span class="my_span">
<xsl:value-of select="regex-group(2)"/>
</span>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
producing the following :
{produced xhtml file} :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style xmlns:xhtml="http://www.w3.org/1999/xhtml" type="text/css" xml:space="preserve"></style>
<title>title XHTML</title>
</head>
<body>
<span class="ok"><span class="my_span">blablabla blebleble</span> bliblibli</span>
<p class="ko">blablabla _blebleble bliblibli <em class="em">one em tag</em> blablabla blebleble._</p>
</body>
</html>
Unfortunately, I figured out that some p tags contain em or i or similar tags that are not handled by my XSLT.
I would like to be able to produce this xhtml :
{expected xhtml file} :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style xmlns:xhtml="http://www.w3.org/1999/xhtml" type="text/css" xml:space="preserve"></style>
<title>title XHTML</title>
</head>
<body>
<span class="ok"><span class="my_span">blablabla blebleble</span> bliblibli</span>
<p class="ko">blablabla
<span class="my_span">blebleble bliblibli </span>
<em class="em"><span class="my_span">one em tag</span></em>
<span class="my_span">blablabla blebleble.</span>
</p>
</body>
</html>
I simplified the xhtml source file to one em tag not handled by my XSLT but there may be many combination of similar tag in one p tag.
In my expected xhtml file, I located the added span inside the em but swapping them would work too.
How to achieve this in XSLT ?
Thanks for help.
I tried to convert the _ character into a processing instruction <?marker?> in one transformation step, then in a second pair such <?marker?>s into <?open?>/<?close?> pairs to finally use a recursive function to group any such pairs based on for-each-group group-starting-with="processing-instruction('open') with a nested for-each-group group-ending-with="processing-instruction('close')":
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xpath-default-namespace="http://www.w3.org/1999/xhtml"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="wrap-class" as="xs:string">my_class</xsl:param>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="body">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:variable name="marked-content">
<xsl:apply-templates mode="analyze"/>
</xsl:variable>
<xsl:variable name="paired-content">
<xsl:apply-templates select="$marked-content/node()" mode="pair-markers"/>
</xsl:variable>
<xsl:apply-templates select="$paired-content/node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[processing-instruction('open')]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:sequence select="mf:group(node())"/>
</xsl:copy>
</xsl:template>
<xsl:mode name="analyze" on-no-match="shallow-copy"/>
<xsl:template mode="analyze" match="text()">
<xsl:apply-templates select="analyze-string(., '_')" mode="mark"/>
</xsl:template>
<xsl:template mode="mark" match="fn:*">
<xsl:apply-templates mode="#current"/>
</xsl:template>
<xsl:template mode="mark" match="fn:match">
<xsl:processing-instruction name="marker"/>
</xsl:template>
<xsl:mode name="pair-markers" on-no-match="shallow-copy"/>
<xsl:template mode="pair-markers" match="processing-instruction('marker')">
<xsl:variable name="pos" as="xs:integer">
<xsl:number/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$pos mod 2 = 1">
<xsl:processing-instruction name="open"/>
</xsl:when>
<xsl:otherwise>
<xsl:processing-instruction name="close"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:function name="mf:group">
<xsl:param name="nodes" as="node()*"/>
<xsl:for-each-group select="$nodes" group-starting-with="processing-instruction('open')">
<xsl:choose>
<xsl:when test="self::processing-instruction('open')">
<xsl:for-each-group select="tail(current-group())" group-ending-with="processing-instruction('close')">
<xsl:choose>
<xsl:when test="position() = 1">
<span class="{$wrap-class}">
<xsl:sequence select="mf:group(current-group()[position() ne last()])"/>
</span>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/nb9PtDX

Passing tags in XSLT templates

I'm just starting to learn XSLT, and everything was working fine, until I tried to centralize the formatting.
This is my problem:
XML
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<document>
<code>code</code>
<code>2<exp>3</exp></code>
<text>
This is a <special>special</special> word. 2<exp>3</exp>
</text>
</document>
XSLT
<?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" indent="yes" doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" doctype-public="-//W3C//DTD XHTML 1.1//EN" encoding="utf-8"/>
<xsl:template name="times">·</xsl:template>
<xsl:template name="pow">
<xsl:param name="exponent"/>
<xsl:element name="sup"><xsl:value-of select="$exponent"/></xsl:element>
</xsl:template>
<xsl:template match="exp">
<xsl:call-template name="times"/>
<xsl:text>10</xsl:text>
<xsl:call-template name="pow">
<xsl:with-param name="exponent"><xsl:apply-templates/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="codeword">
<xsl:param name="word"/>
<xsl:element name="tt">
<xsl:value-of select="$word"/>
</xsl:element>
</xsl:template>
<xsl:template match="special">
<xsl:call-template name="codeword">
<xsl:with-param name="word"><xsl:apply-templates/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="document">
<xsl:element name="html">
<xsl:attribute name="xmlns">http://www.w3.org/1999/xhtml</xsl:attribute>
<xsl:element name="head">
<xsl:element name="title"><xsl:text>Title</xsl:text></xsl:element>
</xsl:element>
<xsl:element name="body">
<xsl:apply-templates select="code"/>
<xsl:apply-templates select="text"/>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="code">
<xsl:element name="div">
<xsl:text>(</xsl:text>
<xsl:call-template name="codeword">
<xsl:with-param name="word"><xsl:apply-templates/></xsl:with-param>
</xsl:call-template>
<xsl:text>)</xsl:text>
</xsl:element>
</xsl:template>
<xsl:template match="text">
<xsl:element name="p">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
XHTML (with xsltproc)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Title</title>
</head>
<body>
<div>(<tt>code</tt>)</div>
<div>(<tt>2·103</tt>)</div>
<p>
This is a <tt>special</tt> word. 2·10<sup>3</sup>
</p>
</body>
</html>
So, I'm trying to convert both <code> and <special> tags in the source XML into <tt> in XHTML. But if when I add further tags in the content (like <sup> in this case, through the "exp" and "pow" templates), they are dropped on adding the <tt> (as in the <tt>2·103</tt> line, which should be <tt>2·10<sup>3</sup></tt>).
What am I doing wrong?
As usual, I find the answer shortly after I ask the question (and I had spent some time before trying to find an answer). The answer is here, I have to use copy-of instead of value-of when using the parameters.

Replace href value in anchor tags of html using XSLT

I want to replace the value of href tags in the HTML using XSLT. For example: if the anchor tag is <a href="/dir/file1.htm" />, I want to replace the href value like this: <a href="http://site/dir/file1.htm" />. The point is I want to replace all the relative urls with the absolute values.
I want to do this for all the anchor tags in the HTML content. How can I do this using XSLT?
Thanks.
EDIT: This is for Google Appliance. I display the results in a frame and the links doesn't work in the Cached page. It takes the address bar URL as the root. Here the HTML is in the form of a string, and it displays the HTML based on a condition. Can someone suggest a way to replace all the href tags in the string?
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pServerName" select="'http://MyServer'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a/#href[not(starts-with(.,'http://'))]">
<xsl:attribute name="href">
<xsl:value-of select="concat($pServerName, .)"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
when applied to this XML document:
<html>
Link 1
Link 2
Link 3
</html>
produces the wanted, correct result:
<html>
Link 1
Link 2
Link 3
</html>
II. XSLT 2.0 solution:
In XPath 2.0 one can use the standard function resolve-uri()
This transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:variable name="vBaseUri" select="'http://Myserver/ttt/x.xsl'"/>
<xsl:template match="/">
<xsl:value-of select="resolve-uri('/mysite.aspx', $vBaseUri)"/>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (not used), produces the wanted, correct result:
http://Myserver/mysite.aspx
If the stylesheet module comes from the same server as the relative URLs to be resolved, then there is no need to pass the base uri in a parameter -- doing the following produces the wanted result:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:variable name="vBaseUri">
<xsl:for-each select="document('')">
<xsl:sequence select="resolve-uri('')"/>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<xsl:value-of select="resolve-uri('/mysite.aspx', $vBaseUri)"/>
</xsl:template>
</xsl:stylesheet>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="pDirectoryPath" select="'http://site.org/dir'"/>
<xsl:variable name="vSitePath" select="concat(
substring-before(
$pDirectoryPath,
'//'),
'//',
substring-before(
substring-after(
$pDirectoryPath,
'//'),
'/'))"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a/#href[starts-with(.,'/')]" priority="1">
<xsl:attribute name="href">
<xsl:value-of select="concat($vSitePath,.)"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="a/#href[not(contains(.,'://'))]">
<xsl:attribute name="href">
<xsl:value-of select="concat($pDirectoryPath,'/',.)"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
With this input:
<html>
<body>
<h4>Headline</h4>
<p>Root relative link <a href="/image/image1.jpg" /></p>
<p>Relative link <a href="next.htm" /></p>
<p>Absolute Link <a href="http://site.org/dir/file1.htm" /></p>
</body>
</html>
Output:
<html>
<body>
<h4>Headline</h4>
<p>Root relative link </p>
<p>Relative link </p>
<p>Absolute Link </p>
</body>
</html>
Edit: Example of root relative path an real relative path.

String comparison in XSLT

When I call this template I get the following results.
155IT Matches 155OO
155OO Matches 155OO
155PP
The XML I am processing does have three rows and those are the values, but why is the test returning true for the first two and false for the last one? How should I be doing the string comparison?
<?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 name="ProofOfConcept">
<xsl:param name="Lines"/>
<xsl:param name="MainDeliveryCode"/>
<xsl:choose>
<xsl:when test="$Lines">
<xsl:variable name="CurrentDeliveryCode" select="$Lines/DLVYLOCCD"/>
<p>
<xsl:choose>
<xsl:when test=" $MainDeliveryCode = $CurrentDeliveryCode">
<xsl:value-of select="$CurrentDeliveryCode"/> Matches <xsl:value-of select="$MainDeliveryCode"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Lines"/> Fails <xsl:value-of select="$MainDeliveryCode"/>
</xsl:otherwise>
</xsl:choose>
</p>
<xsl:call-template name="ProofOfConcept">
<xsl:with-param name="Lines" select="$Lines[position() > 1]"/>
<xsl:with-param name="MainDeliveryCode" select="$MainDeliveryCode"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/">
<html>
<head>
<title></title>
</head>
<body>
<xsl:call-template name="ProofOfConcept">
<xsl:with-param name="Lines" select="data/Lines/LINE"/>
<xsl:with-param name="MainDeliveryCode" select="data/header/DLVYLOCCD"/>
</xsl:call-template>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Sample data
<?xml version="1.0"
encoding="ISO-8859-1"
standalone="yes"?> <data>
<header><DLVYLOCCD>155OO</DLVYLOCCD>
</header> <Lines>
<LINE><DLVYLOCCD>155IT</DLVYLOCCD></LINE>
<LINE><DLVYLOCCD>155OO</DLVYLOCCD></LINE>
<LINE><DLVYLOCCD>155PP</DLVYLOCCD></LINE>
</Lines> </data>
Thanks for any advice.
Here is a less painful version of your XSLT:
<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="data">
<html>
<head>
<title></title>
</head>
<body>
<!-- this selects the matching LINE node(s), or nothing at all -->
<xsl:apply-templates select="
Lines/LINE[DLVYLOCCD = /data/header/DLVYLOCCD]
" />
</body>
</html>
</xsl:template>
<xsl:template match="LINE">
<p>
<!-- for the sake of the example, just output a copy -->
<xsl:copy-of select="." />
</p>
</xsl:template>
</xsl:stylesheet>
gives (formatted result):
<?xml version="1.0" encoding="utf-8"?>
<html>
<head>
<title></title>
</head>
<body>
<p>
<LINE><DLVYLOCCD>155OO</DLVYLOCCD></LINE>
</p>
</body>
</html>
There are a few things wrong with your implementation. Most important, the expression:
<xsl:variable name="CurrentDeliveryCode" select="$Lines/DLVYLOCCD"/>
returns a node-set consisting of all the DLVYLOCCD elements, not just the current one as you seem to assume. Also, you shouldn't be using recursion to iterate. Use <xsl:for-each> instead, in which case you will process the items one at a time.
I figured it out.
I need to change my test to
<xsl:when test="contains($MainDeliveryCode, $CurrentDeliveryCode" >
That solved the problem.
http://www.zvon.org/xxl/XSLTreference/Output/function_contains.html is the documentation for the function.