how to get a transformed xml file with all child tags of every occurance of parent tag under only one parent tag - xslt

I am using this input xml file .
<Content>
<body><text>xxx</text></body>
<body><text>yy</text></body>
<body><text>zz</text></body>
<body><text>kk</text></body>
<body><text>mmm</text></body>
</Content>
after Xslt transformation the output should be
<Content>
<body><text>xxx</text>
<text>yy</text>
<text>zz</text>
<text>kk</text>
<text>mmm</text></body>
</Content>
Can anyone please provide its relavant Xsl file.

This complete transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body"/>
<xsl:template match="body[1]">
<body>
<xsl:apply-templates select="../body/node()"/>
</body>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Content>
<body>
<text>xxx</text>
</body>
<body>
<text>yy</text>
</body>
<body>
<text>zz</text>
</body>
<body>
<text>kk</text>
</body>
<body>
<text>mmm</text>
</body>
</Content>
produces the wanted, correct result:
<Content>
<body>
<text>xxx</text>
<text>yy</text>
<text>zz</text>
<text>kk</text>
<text>mmm</text>
</body>
</Content>
Explanation:
The identity rule copies every node "as-is".
It is overriden by two templates. The first ignores/deletes every body element`.
The second template overriding the identity template also overrides the first such template (that deletes every body element) for any body element that is the first body child of its parent. For this first body child only, a body element is generated and in its body all nodes that are children nodes of any body child of its parent (the current body elements and all of its body siblings) are processed.

<xsl:template match="Content">
<body>
<xsl:apply-templates select="body/text"/>
</body>
</xsl:template>
<xsl:template match="body/text">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>

Related

copy-of with search and replace relative paths

I want to insert an html snippet from an external file into my output document with copy-of like described here: https://stackoverflow.com/a/5976762/18427492
The html snipped is a navigation bar and also used by other (python) scripts to generate other html files.
I need to replace the path in "href" to match a relative path that i have in a XSLT variable.
Full file content (Template file to be copied):
<ul class="nav">
<li class="fineprint">MyNiceGame Developer Mode Documentation</li>
<li class="switchlang"><img src="/deco/dco_en_sml.gif" alt="English" border="0"></img></li>
<li>Introduction</li>
<li>Contents</li>
<li>Search</li>
<li>Engine</li>
<li>Command Line</li>
<li>Game Data</li>
<li>Script</li>
</ul>
So how can i insert this snippet into my XSL document and replace ../../sdk/ (its possible to change this string to something like {replace-me}/sdk/...) with a relative path that i already have in a XSLT variable?
My XSLT document (i want to replace the <xsl:call-template name="nav"/> with the template file processing):
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0" xpath-default-namespace="https://clonkspot.org" exclude-result-prefixes="xs">
<xsl:output method="html" encoding="ISO-8859-1" doctype-public="-//W3C//DTD HTML 4.01//EN"
doctype-system="http://www.w3.org/TR/html4/strict.dtd"/>
<xsl:template match="/clonkDoc">
<html>
<body>
<xsl:call-template name="nav"/>
<xsl:apply-templates select="func"/>
<!-- other possible nodes under /clonkDoc -->
<xsl:call-template name="nav"/>
</body>
</html>
</xsl:template>
<xsl:template name="nav">
<xsl:param name="relpath" tunnel="yes"/>
<ul class="nav">
<li class="fineprint">
<xsl:when test='lang("en")'>>MyNiceGame Developer Mode Documentation</xsl:when>
</li>
<!-- Other li elements -->
</xsl:template>
Example source file:
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="../../../clonk.xsl"?>
<clonkDoc xmlns="https://clonkspot.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://clonkspot.org ../../../clonk.xsd" xml:lang="de">
<func>
<!-- other nodes -->
</func>
</clonkDoc>
Desired target file:
<!DOCTYPE html
PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<!-- stuff -->
</head>
<body>
<ul class="nav">
<!-- The corrected li elements with modified a href link -->
</ul>
<!-- Other stuff from source file (<func>) -->
<ul class="nav">
<!-- The corrected li elements with modified a href link -->
</ul>
</body>
</html>
Martin Honnen's solution for my specific case with the xpath-default-namespace:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0" xpath-default-namespace="https://clonkspot.org" exclude-result-prefixes="xs">
<xsl:output method="html" encoding="ISO-8859-1" doctype-public="-//W3C//DTD HTML 4.01//EN"
doctype-system="http://www.w3.org/TR/html4/strict.dtd"/>
<xsl:template match="/clonkDoc">
<html>
<body>
<xsl:apply-templates select="doc('file.html')//ul[#class = 'nav']" xpath-default-namespace="" mode="fix-links"/>
<xsl:apply-templates select="func"/>
<!-- other possible nodes under /clonkDoc -->
<xsl:apply-templates select="doc('file.html')//ul[#class = 'nav']" xpath-default-namespace="" mode="fix-links"/>
</body>
</html>
</xsl:template>
<xsl:mode name="fix-links" on-no-match="shallow-copy"/>
<xsl:template mode="fix-links" match="ul/li/a/#href" xpath-default-namespace="">
<xsl:message>Value href: <xsl:value-of select="."></xsl:value-of></xsl:message>
<xsl:attribute name="{name()}" select="replace(., '../../sdk', 'foobar')"/>
</xsl:template>
copy-of makes a a deep copy, if you want to transform input nodes (even only their attribute values) you write templates to do so e.g. <xsl:apply-templates select="doc('file.xml')//ul[#class = 'nav']" mode="fix-links"/>, or, perhaps, as the edit says the snippet with the ul is all in the file, use simply <xsl:apply-templates select="doc('file.xml')" mode="fix-links"/>, and
<xsl:mode name="fix-links" on-no-match="shallow-copy"/>
<xsl:template mode="fix-links" match="ul/li/a/#href">
<xsl:attribute name="{name()}" select="replace(., '../../sdk', $varname)"/>
</xsl:template>
The xsl:mode declaration is XSLT 3 only, in earlier versions declare the identity transformation for that mode e.g.
<xsl:template mode="fix-links" match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" mode="fix-links"/>
</xsl:copy>
</xsl:template>
in XSLT 1 or
<xsl:template mode="fix-links" match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" mode="#current"/>
</xsl:copy>
</xsl:template>
in XSLT 2.
XSLT 3 sample (slighly adapted for the demonstration to work with the primary input) outputs
<ul class="nav">
<li class="fineprint">MyNiceGame Developer Mode Documentation</li>
<li class="switchlang"><img src="/deco/dco_de_sml.gif" alt="German" border="0"/></li>
<li>Introduction</li>
<li>Contents</li>
<li>Search</li>
<li>Engine</li>
<li>Command Line</li>
<li>Game Data</li>
<li>Script</li>
</ul>
As for the information in the latest edit that the secondary input document you want to process has elements in no namespace but your primary one has elements in a certain namespace that your XSLT has used as the xpath-default-namespace, in that case you need to override that for any selections in the secondary input e.g.
<xsl:mode name="fix-links" on-no-match="shallow-copy"/>
<xsl:template mode="fix-links" match="ul/li/a/#href" xpath-default-namespace="">
<xsl:attribute name="{name()}" select="replace(., '../../sdk', $varname)"/>
</xsl:template>
and if you continue to use the apply-templates with an element selector, there as well e.g. <xsl:apply-templates select="doc('file.xml')//ul[#class = 'nav']" xpath-default-namespace="" mode="fix-links"/>.

How do I excluding nodes from XLST

Sorry, this is a really novice question. My real problem involves translating HTML to Open Office XML, but this illustrates the issue I am seeing. I want to make sure the "b" node is ignored in my processing, i.e. not get the 123 at the end of the results output.
XML:
<?xml version="1.0"?><?xml-stylesheet type="text/xsl"?>
<a>
<hello-world>
<greeter>An XSLT Programmer</greeter>
<greeting>Hello, World!</greeting>
</hello-world>
<b>123
</b>
</a>
XSLT:
<?xml version="1.0"?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="hello-world">
<HTML><HEAD><TITLE></TITLE></HEAD><BODY><H1>
<xsl:value-of select="greeting"/>
</H1>
<xsl:apply-templates select="greeter"/>
</BODY></HTML>
</xsl:template>
<xsl:template match="greeter">
<DIV>from <I><xsl:value-of select="."/></I></DIV>
</xsl:template>
</xsl:stylesheet>
Results:
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<TITLE></TITLE>
</HEAD>
<BODY>
<H1>Hello, World!</H1>
<DIV>from <I>An XSLT Programmer</I></DIV>
</BODY>
</HTML>
123
I see you added an answer, but thought I could add more information.
Your first attempt was good, you could have simply added another template to ignore the "b" nodes.
<xsl:template match="b"/>
What was happening is that the XSLT built-in template rules include by default a template that copies the text of any node that's not explicitely matched by your templates.
Reference: docstore.mik.ua/orelly/xml/xmlnut/ch08_07.htm
See the output of your transformation with the added template: https://xsltfiddle.liberty-development.net/aixRus
The issue was that I wasn't selecting the whole document to begin with. If I change the xslt as follows, it then works
<?xml version="1.0"?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/*">
<xsl:apply-templates select='hello-world'/>
</xsl:template>
<xsl:template match="hello-world">
<HTML><HEAD><TITLE></TITLE></HEAD><BODY><H1>
<xsl:value-of select="greeting"/>
</H1>
<xsl:apply-templates select="greeter"/>
</BODY></HTML>
</xsl:template>
<xsl:template match="greeter">
<DIV>from <I><xsl:value-of select="."/></I></DIV>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 Copy attribute value as text and drop the attribute

I want to copy the attiribute value and move it as text of element and drop the attribute for that element. Note: it has to match the element name as i dont want to drop the attribute for other elements.
Input:
<a name = "attr" value = "text"/>
Expected
<a name = "attr"> text </a>
Suppose this is a sample XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<a name="attr" value="text" />
</root>
Then this stylesheet will result in the desired output:
<?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"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a">
<xsl:copy>
<xsl:apply-templates select="#*[local-name() != 'value']"/>
<xsl:value-of select="#value" />
</xsl:copy>
</xsl:template>
</xsl:transform>
Output:
<?xml version="1.0" encoding="UTF-8"?><root>
<a name="attr">text</a>
</root>
The default template just copies recursively. The second template with match <a> elements. It first applies templates for the attributes that are not value, then copies the text of attribute value. It must be done like this because if attribute value appears before other attributes, the opening tag would already be ended to start its text content, and by then other attributes can't be copied anymore.

Using XSLT 1.0 to wrap sibling elements of a header

I'm working with XSLT 1.0 from PHP and want to wrap all the sibling elements after a heading (h2) into a div so I can toggle them.
The input would look like
...
<h2>Nth title</h2>
<first child>...</first child>
...
<last child>...</last child>
<h2>N+1st title</h2>
...
and the output should be
...
<h2>Nth title</h2>
<div>
<first child>...</first child>
...
<last child>...</last child>
</div>
<h2>N+1st title</h2>
...
Is there a way to do this in XSLT 1.0?
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing" match="node()[not(self::h2)]"
use="generate-id(preceding-sibling::h2[1])"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="h2">
<xsl:call-template name="identity"/>
<div>
<xsl:apply-templates mode="copy"
select="key('kFollowing', generate-id())"/>
</div>
</xsl:template>
<xsl:template match="node()[not(self::h2)][preceding-sibling::h2]"/>
<xsl:template match="node()" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<html>
<h2>Nth title</h2>
<first-child>...</first-child> ...
<last-child>...</last-child>
<h2>N+1st title</h2> ...
<x/>
<y/>
<z/>
</html>
produces the wanted, correct result:
<html>
<h2>Nth title</h2>
<div>
<first-child>...</first-child> ...
<last-child>...</last-child>
</div>
<h2>N+1st title</h2>
<div> ...
<x></x>
<y></y>
<z></z>
</div>
</html>
Explanation:
The identity rule/template copies every node "as-is".
The identity rule is overriden for h2 elements. Here the action is to copy the h2 element and then to output a div and inside it to apply templates (in a special mode) to all nodes (that are not h2 themselves) for which this h2 element is the first preceding-sibling h2 element.
The nodes to include in the previous step are conveniently defined as an <xsl:key> instruction.
In order to stop the nodes that are wrapped in div to be output again by the identity rule, we provide a template matching such nodes, that simply ignores them.
Yes. Make a template that matches h2 elements; within that template, you can select all following siblings before the next h2 using this xpath expression: following-sibling::*[count(preceding-sibling::h2[1] | current()) = 1].

Output element in comments

I need to display HTML-element in comments (for example)
<!-- <img src="path" width="100px" height="100px"/> -->
I use this approach
<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="no" encoding="windows-1251"/>
<xsl:template match="myNode">
...
<xsl:comment><xsl:apply-templates select="image" /></xsl:comment>
...
</xsl:template>
<xsl:template match="image">
<img src="{#src}" width="{#width}px" height="{#height}px" />
</xsl:template>
</xsl:stylesheet>
As a result:
<!---->
that is the code in the element xsl:comment ignored.
How do I display an item in the comments?
It might be possible to replace
<xsl:comment><xsl:apply-templates select="image" /></xsl:comment>
with
<xsl:text disable-output-escaping="yes"><!--</xsl:text>
<xsl:apply-templates select="image" />
<xsl:text disable-output-escaping="yes">--></xsl:text>
Haven't tried though.
<xsl:comment><xsl:apply-templates select="image" /></xsl:comment>
As a result:
<!---->
that is the code in the element
xsl:comment ignored
The XSLT 1.0 Spec says:
It is an error if instantiating the
content of xsl:comment creates nodes
other than text nodes. An XSLT
processor may signal the error; if it
does not signal the error, it must
recover by ignoring the offending
nodes together with their content.
How do I display an item in the
comments?
It depends what is meant for "display": in a browser:
<-- <xsl:apply-templates select="image" /> -->
may be useful, provided the result of <xsl:apply-templates/> aboveis just simple text (not markup).
If to "display" means to provide the result as text, then DOE, if allowed by the XSLT processor, may give us the wanted result:
<--
Some text -->
Finally, if it is required that what should be inside the "comment" should be markup and it should be displayed as markup, then this is rather challenging. In this case one has to use:
<xsl:output method="text"/>
and should present every XML lexical item with its desired serialization (i.e. escaped).
This is how the XPath Visualizer constructs its output.
Here is a small transformation that demonstrates the first two approaches:
<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="/">
<-- Hello, World -->
<xsl:text disable-output-escaping="yes"><--</xsl:text>
Hello,world! --<xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:template>
</xsl:stylesheet>
this transformation, when applied on any XML document (not used), produces:
<-- Hello, World -->
<--
Hello,world! -->
Both "comments" may be viewed as comments in a browser, while only the second is presented as comment in free text.
The third approach (most probably what you want) is illustrated below:
<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="/">
<-- <xsl:apply-templates select="image"/> -->
</xsl:template>
<xsl:template match="image">
<img src="<xsl:value-of select="#src"/>"
width="<xsl:value-of select="#width"/>px"
height="<xsl:value-of select="#height"/>px"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<image src="http://example.com/yyy.jpg" width="200" height="300"/>
the wanted result is produced:
<--
<img src="http://example.com/yyy.jpg"
width="200px"
height="300px"/>
-->
viewed in a browser as:
<--
<img src="http://example.com/yyy.jpg"
width="200px"
height="300px"/>
-->
From http://www.w3.org/TR/xslt#section-Creating-Comments:
The xsl:comment element is instantiated to create a comment node in the result tree. The content of the xsl:comment element is a template for the string-value of the comment node.
For example, this
<xsl:comment>This file is
automatically generated. Do not
edit!</xsl:comment>
would create the comment
<!--This file is automatically
generated. Do not edit!-->
It is an error if instantiating the
content of xsl:comment creates nodes
other than text nodes. An XSLT
processor may signal the error; if it
does not signal the error, it must
recover by ignoring the offending
nodes together with their content.
It is an error if the result of
instantiating the content of the
xsl:comment contains the string -- or
ends with -. An XSLT processor may
signal the error; if it does not
signal the error, it must recover by
inserting a space after any occurrence
of - that is followed by another - or
that ends the comment.
So, in order to do what you want you need to use DOE mechanism.
As example, this stylesheet:
<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="no" encoding="windows-1251"/>
<xsl:template match="img">
<img src="{.}"/>
</xsl:template>
<xsl:template match="root">
<xsl:variable name="vResult">
<xsl:apply-templates/>
</xsl:variable>
<html>
<xsl:copy-of select="$vResult"/>
<xsl:comment>
<xsl:apply-templates select="msxsl:node-set($vResult)"
mode="encode"/>
</xsl:comment>
</html>
</xsl:template>
<xsl:template match="*" mode="encode">
<xsl:value-of select="concat('<',name())"
disable-output-escaping="yes"/>
<xsl:apply-templates select="#*" mode="encode"/>
<xsl:text>></xsl:text>
<xsl:apply-templates mode="encode"/>
<xsl:value-of select="concat('<',name(),'>')"
disable-output-escaping="yes"/>
</xsl:template>
<xsl:template match="*[not(node())]" mode="encode">
<xsl:value-of select="concat('<',name())"
disable-output-escaping="yes"/>
<xsl:apply-templates select="#*" mode="encode"/>
<xsl:text>/></xsl:text>
</xsl:template>
<xsl:template match="#*" mode="encode">
<xsl:value-of select="concat(' ',name(),'="',.,'"')"/>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
<img>http://example.org/image1.jpg</img>
<img>http://example.org/image2.jpg</img>
<img>http://example.org/image3.jpg</img>
</root>
Output:
<html>
<img src="http://example.org/image1.jpg">
<img src="http://example.org/image2.jpg">
<img src="http://example.org/image3.jpg">
<!--<img src="http://example.org/image1.jpg"/>
<img src="http://example.org/image2.jpg"/>
<img src="http://example.org/image3.jpg"/>-->
</html>
Note: node-set extension function for two pass transformation. disable-output-escaping attribute for xsl:value-of instruction.
As said before by Dimitri you can't use the xsl:comment instruction.
If your purpose is simply to comment a fragment of tree, the simplest way is to put the comments markers as text (unescaped) like this :
<xsl:text disable-output-escaping="yes"><!--</xsl:text><xsl:apply-templates select="image" /><xsl:text disable-output-escaping="yes">--></xsl:text>
instead of :
<xsl:comment><xsl:apply-templates select="image" /></xsl:comment>
and you will obtain exactly this
<!-- <img src="path" width="100px" height="100px"/> -->
used with msxml and saxon