Add attribute to tag with XSLT - xslt

I have a few svg documents with a 1-n Path elements now i wanted to change the color of those path elements.
i have haven't found a way to do this
Svg example document:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="45" width="45" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<g transform="matrix(1.25,0,0,-1.25,0,45)">
<path d="m9 18h18v-3h-18v3"/>
</g>
</svg>
XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' version='1.0'>
<xsl:template match='path'>
<xsl:copy>
<xsl:attribute name='fill'>red</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
What do i need to change to make it add/change the fill attribute to red?

I think you misunderstand how XSLT works. It takes an input XML tree and produces a new tree by interpreting your stylesheet. In other words, your stylesheet defines how a completely new tree is produced from scratch, based on the input XML tree.
It's important to understand that you are not modifying the original XML tree. It's like a difference between a purely functional and imperative language. Bottom line: you can't change the fill attribute to red, you can produce a copy of your original document where the fill attribute is set to red.
That said, this is more or less how you would do it:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:svg="http://www.w3.org/2000/svg" version='1.0'>
<!-- this template is applied by default to all nodes and attributes -->
<xsl:template match="#*|node()">
<!-- just copy all my attributes and child nodes, except if there's a better template for some of them -->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- this template is applied to an existing fill attribute -->
<xsl:template match="svg:path/#fill">
<!-- produce a fill attribute with content "red" -->
<xsl:attribute name="fill">red</xsl:attribute>
</xsl:template>
<!-- this template is applied to a path node that doesn't have a fill attribute -->
<xsl:template match="svg:path[not(#fill)]">
<!-- copy me and my attributes and my subnodes, applying templates as necessary, and add a fill attribute set to red -->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:attribute name="fill">red</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

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.

Insert attributes on-the-fly to an "a" tag

I need to modify on-the-fly the "content" of all the "a" tags present in a specific div (#navigation).
Is there a diazo rule or xslt pattern?
Thank's
Vito
The following demonstrates adding an attribute (in this case target) to each of the a tags that are child elements under an element with the id of navigation (so corresponding to #navigation in CSS). All content and other attributes from the original tags are maintained (although order may not be - though this shouldn't be an issue).
<?xml version="1.0" encoding="UTF-8"?>
<rules xmlns="http://namespaces.plone.org/diazo"
xmlns:css="http://namespaces.plone.org/diazo/css"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<after css:theme="#target" css:content="#navigation" />
<xsl:template match="*[#id='navigation']//a">
<xsl:copy>
<xsl:attribute name="target">_blank</xsl:attribute>
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
</rules>
Adjust the match condition accordingly with additional conditions if you want to match specific a tags. The xsl:template will be executed after all standard Diazo rules, so ensure that you adjust the match condition accordingly if you happen to change the structure of where the a tags are in your resulting document.
This was extended an example in the official Diazo documentation at http://docs.diazo.org/en/latest/recipes/adding-an-attribute/index.html
Not sure what you mean.
If you want to create an XSLT that copies everything but tweak only the "a" elements inside the div id='navigation' you should do something 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="html" />
<xsl:template match="#*|node()" priority="-1">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//div[#id='navigation']//a">
<a>
<xsl:attribute name='href'>
<xsl:value-of select='#href' />
</xsl:attribute>
<!-- Change your content here -->
</a>
</xsl:template>
</xsl:stylesheet>

How to copy all child nodes of any type of a template context element

I am transforming XML into HTML using XSLT.
I have the following XML structure:
<root>
<element>
<subelement>
This is some html text which should be <span class="highlight">displayed highlighted</span>.
</subelement>
</element>
</root>
I use the following template for the transformation:
<xsl:template name="subelement">
<xsl:value-of select="." />
</xsl:template>
Unfortunately, I lose the <span>-tags.
Is there a way to keep them so the HTML is displayed correctly (highlighted)?
The correct way to get the all the contents of the current matching node (text nodes included) is:
<xsl:template match="subelement">
<xsl:copy-of select="node()"/>
</xsl:template>
This will copy everything descendent.
Try using <xsl:copy-of... instead of <xsl:value-of... for example:
<xsl:template name="subelement">
<xsl:copy-of select="*" />
</xsl:template>
Note the * which will stop the <subelement></subelement> bits being output to the results, rather than using . which will include the <subelement></subelement> bits .
For example, the xsl stylesheet:
<?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"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="root/element">
<output>
<xsl:apply-templates select="subelement"/>
</output>
</xsl:template>
<xsl:template match="subelement">
<xsl:copy-of select="*"/>
</xsl:template>
</xsl:stylesheet>
when applied to your example xml file returns:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<span class="highlight">displayed highlighted</span>
</output>
The <xsl:value-of> declaration takes the concatenated contents of all text nodes within the element, in sequential order, and doesn't output any elements at all.
I'd recommend using <xsl:apply-templates> instead. Where it finds a text node, it will output the contents as-is, but you would need to define a template for handling span tags to convert them to html ones. If that span tag IS an html tag, then strictly speaking, you should have separate namespaces for your own document structure and html.

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

Overriding match="*" template from DocBook XSL

DocBook XSL includes a template that matches all element
<xsl:template match="*">
<xsl:message> .... </xsl:message>
</xsl:template>
I need to override it with another template because my source XML tree contains more that just the DoocBook XML. If I specify such a template in the file it overrides all templates in DocBook XSL. It seems like that all imported templates, are prioritized on the order of import only, and NOT according to how specific the template is.
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:db="http://docbook.org/ns/docbook" version="1.0">
<xsl:import href="docbook-xsl-ns/xhtml/docbook.xsl" />
<xsl:import href="copy.xsl"/>
<xsl:template match="/">
<xsl:apply-templates select="//db:book"/>
</xsl:template>
</xsl:stylesheet>
copy.xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<!-- go process attributes and children -->
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Sample XML source
<?xml version="1.0" encoding="UTF-8"?>
<root>
<http-host>localhost</http-host>
<book xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:svg="http://www.w3.org/2000/svg" xmlns:m="http://www.w3.org/1998/Math/MathML" xml:id="course.528" xml:lang="en" version="5.0">
<info>
<title>Postoperative Complications</title>
</info>
<chapter xml:id="chapter.1">
<title>INTRODUCTION</title>
<para>Postoperative complications are a constant threat to the millions ....</para>
</chapter>
</book>
<errors></errors>
</root>
This is true for both Xalan and xsltproc processors. How do I override this template without having to change the DocBook XSL source. I tried messing with priorities but that did not work.
From what I understand, you want to apply the copy.xsl's template only for non-docbook elements. Try to be more specific in your copy.xsl - by being more specific in your copy.xsl, that template will get selected for all non-docbook elements.
copy.xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform>
<xsl:template match="*[not(namespace-uri() = 'http://docbook.org/ns/docbook')]">
<xsl:element name="{local-name()}">
<!-- go process attributes and children -->
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Depending on the presence of DocBook elements within non-Docbook nodes, you might need to restrict the nodeset for which you apply at the apply-templates part as well(based on the namespace) and maybe mess around the apply-templates flow to make sure it handles it predictably. Hope this is of some use to you..