Avoid newline within mixed content elements - xslt

I need control over the output of an XSL transformation process in terms of (not) setting newlines before certain result elements. Take this input
<text>
<line>My text uses <hi>highlighting</hi> methods</line>
<line>Next line uses <hi>two </hi><hi>highlighter</hi> elements...</line>
</text>
transformed by this simple stylesheet:
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="line">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="hi">
<span>
<xsl:apply-templates/>
</span>
</xsl:template>
</xsl:transform>
The undesirable result of the transformation is:
<p>My text uses <span>highlighting</span> methods</p>
<p>Next line uses <span>two </span>
<span>highlighter</span> elements...</p>
The second <span> within <p> produces a newline, which is not what I want.
What's the reason for this behaviour and how to avoid it, meaning: how to achieve this result:
<p>My text uses <span>highlighting</span> methods</p>
<p>Next line uses <span>two </span><span>highlighter</span> elements...</p>
(Yes, I need <xsl:output indent="yes"> and the transformation method has to be "xml".)

The only way I can see to get around this with the constraints you specify in the last line of your question (method="xml" and indent="yes") is to add xml:space="preserve" to the p elements you create, as
Whitespace characters MUST NOT be inserted in a part of the result document that is controlled by an xml:space attribute with value preserve.
(Source)
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="line">
<p xml:space="preserve"><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="hi">
<span>
<xsl:apply-templates/>
</span>
</xsl:template>
</xsl:transform>
Note that because of the xml:space="preserve" you also have to remove the whitespace between the opening and closing tags of the p element and the child xsl:apply-templates. When run on your example input using Saxon 9 HE this produces the output
<?xml version="1.0" encoding="UTF-8"?>
<p xml:space="preserve">My text uses <span>highlighting</span> methods</p>
<p xml:space="preserve">Next line uses <span>two </span><span>highlighter</span> elements...</p>
If you could instead use the xhtml output method (and the XHTML namespace) then the XHTML indenter is not allowed to add space around tags that start or end elements that XHTML specifies to be "inline" markup, and this includes span.
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:output indent="yes" method="xhtml"/>
<xsl:template match="/">
<html><body><xsl:apply-templates/></body></html>
</xsl:template>
<xsl:template match="line">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="hi">
<span>
<xsl:apply-templates/>
</span>
</xsl:template>
</xsl:transform>
when run on the same input would produce
<?xml version="1.0" encoding="UTF-8"?><html xmlns="http://www.w3.org/1999/xhtml">
<body>
<p>My text uses <span>highlighting</span> methods
</p>
<p>Next line uses <span>two </span><span>highlighter</span> elements...
</p>
</body>
</html>
without space between the two span elements.

Related

Getting 2 result values every for-each iteration

I am using a "xsl:for-each" to iterate over each element with name content and attribute that contains the text "period". When attempting to take out one date per each "xsl:for-each" iteration, it returns 2 values.
The matching of text "period" must be done like this due to the input data might change and it is unknown how many elements with id containing ="period", that would appear in the data.
I would like to keep the xpath search critera in the "xsl:for-each" syntax, because I am using the template to point out root.
When I try to subset the dates using date[1] it still return both dates.
XSLT Fiddle
Same code as in above fiddle:
Data:
<?xml version="1.0" encoding="utf-8" ?>
<section>
<content id="period1">
<date>2021-01-01</date>
</content>
<content id="period2">
<date>2020-01-01</date>
</content>
</section>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#all"
>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:template match="/section">
<xsl:for-each select="//content/#*[contains(., 'period')]">
<date>
<!--<xsl:value-of select="."/>-->
<!--<xsl:value-of select="//date[1]"/>-->
<xsl:value-of select="//content/date"/>
</date>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Result:
<!DOCTYPE HTML>
<date>2021-01-01 2020-01-01</date>
<date>2021-01-01 2020-01-01</date>
Wanted result:
<!DOCTYPE HTML>
<date>2021-01-01</date>
<date>2020-01-01</date>
Use relative paths
<xsl:for-each select="content[#*[contains(., 'period')]]">
<date>
<!--<xsl:value-of select="."/>-->
<!--<xsl:value-of select="//date[1]"/>-->
<xsl:value-of select="date"/>
</date>
</xsl:for-each>

XSLT: Reference to entity must end with the ';' delimiter. SXXP0003

Source:
<!DOCTYPE html>
<html>
<head/>
<body>
<p>Here Link</p>
</body>
</html>
Transform:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<!-- output just the <body> (without <body>) -->
<xsl:output method="html" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="body/*">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="head | meta | text()"/>
</xsl:stylesheet>
Desired Output:
<p>Here Link</p>
Error:
The reference to entity "node" must end with the ';' delimiter. SXXP0003
Solution Constraint:
I need to keep the #href as node= because that is what the web address is. I cannot delimit it or otherwise change it.
A later step is an Identity Transform, so I'll need to address the problem there as well.

Omit unneeded namespaces from the output

My XSLT is outputiung some tags with xmlns:x="http://something" attribute... How to avoid this redundant attribute? The output XML never use, neither in a the x:tag, nor in an x:attribute.
EXAMPLE OF XML:
<root><p>Hello</p><p>world</p></root>
EXAMPLE OF XSL:
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xlink="http://www.w3.org/1999/xlink">
<xsl:output encoding="UTF-8" method="xml" version="1.0" indent="no"/>
<xsl:template match="root"><foo>
<xsl:for-each select="p">
<p><xsl:value-of select="." /></p>
</xsl:for-each></foo>
<xsl:for-each select="x">
<link xlink:href="{x}" />
</xsl:for-each></foo>
</xsl:template>
EXAMPLE OF XML OUTPUT:
<foo>
<p xmlns:xlink="http://www.w3.org/1999/xlink">Hello</p>
<p xmlns:xlink="http://www.w3.org/1999/xlink">world</p>
</foo>
The xmlns:xlink is a overhead, it is not used!
A typical case where XSLT must use namespace but the output not:
<xsl:value-of select="php:function('regFunction', . )" />
As Dimitre has already said, if you are not using the xlink namespace anywhere in your XSLT, you should just remove its namespace declaration. If, however, your XSLT is actually using it somewhere that you haven't shown us, you can prevent it from being output by using the exclude-result-prefixes attribute:
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xlink="http://www.w3.org/1999/xlink"
exclude-result-prefixes="xlink">
Just remove this namespace declaration from the xsl:stylesheet instruction -- it isn't used (and thus necessary) at all:
xmlns:xlink="http://www.w3.org/1999/xlink"
The whole transformation now becomes:
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" method="xml" version="1.0" indent="no"/>
<xsl:template match="root"><foo>
<xsl:for-each select="p">
<p class="a"><xsl:value-of select="." /></p>
</xsl:for-each></foo>
</xsl:template>
</xsl:transform>
and when applied on the provided XML document:
<root><p>Hello</p><p>world</p></root>
produces result that is free of namespaces:
<foo>
<p class="a">Hello</p>
<p class="a">world</p>
</foo>

Embedding static CDATA with its tag in XSLT

I need to output from the XSL a static CDATA construct embedded in the XSL, not from the XML that I am transforming. For example...
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<!-- ================================================== -->
<xsl:template match="/">
<Document>
<text><![CDATA[
<b>static</b>
<br/><br/>
text
<br/><br/>
]]>
</text>
<xsl:apply-templates select="//tag"/>
</Document>
</xsl:template>
<!-- ================================================== -->
<xsl:template match="tag">
So on and so forth...
</xsl:template>
<!-- ================================================== -->
</xsl:stylesheet>
I want this to output...
<?xml version="1.0"?>
<Document>
<text><![CDATA[
<b>static</b>
<br/><br/>
text
<br/><br/>
]]>
</text>
So on and so forth...
</Document>
But what I get is...
<?xml version="1.0"?>
<Document>
<text>
<b>static</b>
<br/><br/>
text
<br/><br/>
</text>
So on and so forth...
</Document>
I've tried several combinations of escaping the text and entities, but none seem to work.
Use
<xsl:output cdata-section-elements="text" />
to enforce CDATA for certain elements (spec).
In any case, what you currently get is equivalent to a CDATA section and it should not bother you. (i.e.: If it's bothering you for optical reasons, then get over it. If it is bothering you for technical reasons, fix them.)

XSLT node Traversal

Here is a snip-it of the XML:
<?xml version="1.0" encoding="iso-8859-1" ?>
<NetworkAppliance id="S123456">
<Group id="9">
<Probe id="1">
<Value>74.7</Value>
</Probe>
</NetworkAppliance>
I want to get the single point value of 74.7. There are many groups with unique ID's and many Probes under that group with unique ID's each with values.
I am looking for example XSLT code that can get me this one value. Here is what i have that does not work:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" version="3.2" />
<xsl:template match="NetworkAppliance">
<xsl:apply-templates select="Group[#id='9']"/>
</xsl:template>
<xsl:template match="Group">
Temp: <xsl:value-of select="Probe[#id='1']/Value"/>
<br/>
</xsl:template>
</xsl:stylesheet>
Here is what worked for me in the end:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="NetworkAppliance/Group[#id=9]/Probe[#id=1]">
Value: <xsl:value-of select="Value" />
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Don't forget that you can do select several levels at once. Fixing your XML to:
<?xml version="1.0" encoding="iso-8859-1" ?>
<NetworkAppliance id="S123456">
<Group id="9">
<Probe id="1">
<Value>74.7</Value>
</Probe>
</Group>
</NetworkAppliance>
and using this stylesheet:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" version="3.2" />
<xsl:template match="/">
Temp: <xsl:value-of select="//Group[#id='9']/Probe[#id='1']/Value"/>
<br/>
</xsl:template>
</xsl:stylesheet>
we can pick out that one item you're interested in.
Points to note:
The // part of the expression means that the search for Group nodes takes place throughout the whole tree, finding Group nodes at whatever depth they're at.
The [#id='9'] part selects those Group nodes with id of 9
The Probe[#id='1'] part immediately after that selects those children of the Group nodes it found where the id is 1, and so on.
<xsl:value-of select="/NetworkAppliance/Group[#id=9]/Probe[#id=1]/Value"/>
XSLT is just one of the tools in the box, and nothing without XPath.
the xpath for value of a node is /node/text()
So
<xsl:value-of select="Probe[#id='1']/text()"/>