I worked out an XSL template that rewrites all hyperlinks on an HTML page, containing a certain substring in the href attribute. It looks like this:
<xsl:template match="A[contains(#href, 'asp')]">
<a>
<xsl:attribute name="href">
<xsl:value-of select="bridge:linkFrom($bridge, $base, #href, 'intranet')" />
</xsl:attribute>
<xsl:apply-templates select="node()" />
</a>
</xsl:template>
I'm not liking the fact that I must recreate the A element from scratch. I know you can do something like this:
<xsl:template match="A/#href">
<xsl:attribute name="href">
<xsl:value-of select="bridge:linkFrom($bridge, $base, ., 'intranet')" />
</xsl:attribute>
</xsl:template>
But how should I merge these two together? I tried f.e. this and it doesn't work (the element does not get selected):
<xsl:template match="A[contains(#href, 'asp')]/#href">
<xsl:attribute name="href">
<xsl:value-of select="bridge:linkFrom($bridge, $base, ., 'intranet')" />
</xsl:attribute>
</xsl:template>
Any help is much appreciated!
First: If you declare a rule for matching an attribute, then you must take care of apply templates to those attributes, because there is no built-in rule doing that and apply templates without select is the same as apply-templates select="node()".
So, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a/#href[.='#']">
<xsl:attribute name="href">http://example.org</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
link
</root>
Output:
<root>
link
</root>
But, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="a/#href[.='#']">
<xsl:attribute name="href">http://example.org</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<a>link</a>
</root>
I would also expect it to work. I have no way of testing this now but did you try any other way of writing it?
For example:
<xsl:template match="A/#href[contains(. , 'asp')]">
Related
I'm trying to remove text nodes without success from a XML document, this is the XSLT I'm using:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*">
<xsl:element name="x">
<xsl:attribute name="attr">
<xsl:value-of select="name()"/>
</xsl:attribute>
<xsl:apply-templates select="node()" />
</xsl:element>
</xsl:template>
<xsl:template match="/*/*/a">
<xsl:copy>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*/a/*">
<xsl:copy-of select="node()"/>
</xsl:template>
<xsl:template match="/*/*/*">
<xsl:copy-of select="node()"/>
</xsl:template>
<xsl:template match="/*/*/*[not(self::a)]" />
<xsl:template match="text()" />
</xsl:stylesheet>
The line <xsl:template match="text()"> probably it's not working because other lines are more specific (I think), how can I do to remove ALL text nodes?
Your template to suppress text nodes is suppressing all text nodes for which matching templates are sought. But it's not suppressing all text nodes, because not all text nodes are processed using apply-templates. When you encounter some nodes (those that match the match-patterns /*/*/a/* and /*/*/*, you are copying all of their child nodes without applying templates to them; if some of those children are text nodes, or others of those children have text-node descendants, those text nodes are escaping your scythe. Get rid of the copy-of calls, then, and stick to copy with apply-templates.
We have a program that uses xml to save configurations of our program. Someone decided to rename a couple of values in our database and these renames should now also be backwards compatible in the configurations of our customers.
An example of a configuration
<configuration>
<fruitToEat>yellow_curved_thing</fruitToEat> <!-- should now become banana -->
</configuration>
A simple match would be (not tested, just an example):
<xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match"/configuration/fruitToEat/text()">
<xsl:text>banana</xsl:text>
</xsl:template>
</xsl:template>
But this is just one example and I want to do this 150 times.
Is it possible to make an xsl that reads a simple text file or ini file that tells me how the 150 matches should look alike?
<xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- recreate this template 150 times from an ini file or something -->
<xsl:template match"/configuration/fruitToEat/text()[.='yellow_curved_thing']">
<xsl:text>banana</xsl:text>
</xsl:template>
</xsl:template>
An example of my mapping file could be simply:
yellow_curved_thing = banana
round_thing = tomato
round_dotted = strawberry
And I would simply want a small xslt that tells me:
<xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- recreate this template 150 times from an ini file or something -->
<xsl:template match"/configuration/fruitToEat/text()[.=$fileRow0]">
<xsl:text>$fileRow1</xsl:text>
</xsl:template>
</xsl:template>
So even if I think there is more complexity behind the curtain, may be this will help a little bit.
There are some possibilities to do this with xlst. Which would be best in long term depends on complexity in real life and how often you need to do this with different "mapping" information.
For your easy example you can put the "mapping" information into a xml file. This could be done by some script form ini file.
<mappings>
<mapping name="fruitToEat" >
<map from="yellow_curved_thing" to="banana" />
<map from="round_thing" to="tomato" />
<map from="round_dotted" to="strawberry" />
</mapping>
</mappings>
Than you can have a template which make use of this mapping information:
<xsl:variable name="fruitMapping"
select="document('fruitmapping.xml')//mapping[#name='fruitToEat']" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/configuration/fruitToEat/text()" >
<!-- find the entry in "ini file" -->
<xsl:variable name ="map" select="$fruitMapping/map[#from = current()]" />
<xsl:choose>
<xsl:when test="$map" >
<xsl:value-of select="$map/#to"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
But if this is a onetime job I would implement this "mapping" direct a template. Like this:
<xsl:template match="/configuration/fruitToEat/text()" >
<xsl:choose>
<xsl:when test=".='yellow_curved_thing'" >banana</xsl:when>
<xsl:when test=".='round_thing'" >tomato</xsl:when>
<xsl:when test=".='round_dotted'" >strawberry</xsl:when>
<xsl:otherwise>
<xsl:copy />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
It seems you wanted to create your XSLT dynamic XSLT on the basis of your configuration file which is also an XML document. Have a look this exmple considering this:
configuration.xml
<p>
<configuration>
<fruitToEat>yellow_curved_thing</fruitToEat>
<mapped>banana</mapped>
</configuration>
<configuration>
<fruitToEat>round_thing</fruitToEat>
<mapped>tomato</mapped>
</configuration>
</p>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:element name="xsl:stylesheet">
<xsl:attribute name="version">
<xsl:text>1.0</xsl:text>
</xsl:attribute>
<xsl:element name="xsl:template">
<xsl:attribute name="match">
<xsl:text>node()|#*</xsl:text>
</xsl:attribute>
<xsl:element name="xsl:copy">
<xsl:element name="xsl:apply-templates">
<xsl:attribute name="select">
<xsl:text>node()|#*</xsl:text>
</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:element>
<xsl:for-each select="//configuration">
<xsl:element name="xsl:template">
<xsl:attribute name="match">
<xsl:text>configuration/fruitToEat/text()[.=</xsl:text>
<xsl:text>'</xsl:text>
<xsl:value-of select="fruitToEat"/>
<xsl:text>']</xsl:text>
</xsl:attribute>
<xsl:element name="xsl:text">
<xsl:value-of select="mapped"/>
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
output:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="configuration/fruitToEat/text()[.='yellow_curved_thing']">
<xsl:text>banana</xsl:text>
</xsl:template>
<xsl:template match="configuration/fruitToEat/text()[.='round_thing']">
<xsl:text>tomato</xsl:text>
</xsl:template>
</xsl:stylesheet>
I have XSLT 1.0 like this:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="paragraph">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
</xsl:stylesheet>
Only, there are lots and lots of templates that are similar to the first one. I would like to have a specific attribute emitted in each of these templates, but I want to make the least invasive change to pull it off. Here's the first thing I tried:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="paragraph">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="#class">
<xsl:attribute name="class">
<xsl:value-of select="#class"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
But that didn't work. I tried this:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="paragraph">
<p>
<xsl:attribute name="class">
<xsl:value-of select="#class"/>
</xsl:attribute>
<xsl:apply-templates/>
</p>
</xsl:template>
</xsl:stylesheet>
But to make that work in every template would require a lot of code duplication. Is that the best I can do, or is there a more appropriate way to make this work?
In your initial attempt
<xsl:template match="#class">
<xsl:attribute name="class">
<xsl:value-of select="#class"/>
</xsl:attribute>
</xsl:template>
the context node for this template is the class attribute node, therefore the value-of should select .:
<xsl:template match="#class">
<xsl:attribute name="class">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
However you should also note that a bare <xsl:apply-templates/> only applies templates matching children of the current node, and since attribute nodes do not count as children in the XSLT data model this #class template won't fire. You probably need to modify your paragraph template to say
<xsl:template match="paragraph">
<p>
<xsl:apply-templates select="#*|node()"/>
</p>
</xsl:template>
to apply templates that match the paragraph element's attributes as well as its children.
If you really don't want to make any change to the existing template rules, then add a template rule with higher priority than the rules for "p", etc, like this:
<xsl:template match="*[#class]" priority="100">
<xsl:variable name="v">
<xsl:next-match/>
</xsl:variable>
<xsl:apply-templates select="$v" mode="add-attribute">
<xsl:with-param name="att" select="#class"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*" mode="add-attribute">
<xsl:param name="att" as="attribute()"/>
<xsl:copy>
<xsl:copy-of select="#*, $att"/>
<xsl:apply-templates mode="#current"/>
</xsl:copy>
</xsl:template>
In 1.0 you would have to put this in a separate module and use apply-imports rather than next-match.
I'm trying to use XSLT to transform a document by tagging a group of XML nodes with integer ids, starting at 0, and increasing by one for each node in the group. The XML passed into the stylesheet should be echoed out, but augmented to include this extra information.
Just to be clear about what I am talking about, here is how this transformation would be expressed using DOM:
states = document.getElementsByTagName("state");
for( i = 0; i < states.length; i++){
states.stateNum = i;
}
This is very simple with DOM, but I'm having much more trouble doing this with XSLT. The current strategy I've devised has been to start with the identity transformation, then create a global variable which selects and stores all of the nodes that I wish to number. I then create a template that matches that kind of node. The idea, then, is that in the template, I would look up the matched node's position in the global variable nodelist, which would give me a unique number that I could then set as an attribute.
The problem with this approach is that the position function can only be used with the context node, so something like the following is illegal:
<template match="state">
<variable name="stateId" select="#id"/>
<variable name="uniqueStateNum" select="$globalVariable[#id = $stateId]/position()"/>
</template>
The same is true for the following:
<template match="state">
<variable name="stateId" select="#id"
<variable name="stateNum" select="position($globalVariable[#id = $stateId])/"/>
</template>
In order to use position() to look up the position of an element in $globalVariable, the context node must be changed.
I have found a solution, but it is highly suboptimal. Basically, in the template, I use for-each to iterate through the global variable. For-each changes the context node, so this allows me to use position() in the way I described. The problem is that this turns what would normally be an O(n) operation into an O(n^2) operation, where n is the length of the nodelist, as this require iterating through the whole list whenever the template is matched. I think that there must be a more elegant solution.
Altogether, here is my current (slightly simplified) xslt stylesheet:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:s="http://www.w3.org/2005/07/scxml"
xmlns="http://www.w3.org/2005/07/scxml"
xmlns:c="http://msdl.cs.mcgill.ca/"
version="1.0">
<xsl:output method="xml"/>
<!-- we copy them, so that we can use their positions as identifiers -->
<xsl:variable name="states" select="//s:state" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="s:state">
<xsl:variable name="stateId">
<xsl:value-of select="#id"/>
</xsl:variable>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each select="$states">
<xsl:if test="#id = $stateId">
<xsl:attribute name="stateNum" namespace="http://msdl.cs.mcgill.ca/">
<xsl:value-of select="position()"/>
</xsl:attribute>
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I'd appreciate any advice anyone can offer. Thanks.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:s="http://www.w3.org/2005/07/scxml"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="s:state">
<xsl:variable name="vNum">
<xsl:number level="any" count="s:state"/>
</xsl:variable>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:attribute name="stateId">
<xsl:value-of select="#id"/>
</xsl:attribute>
<xsl:attribute name="id">
<xsl:value-of select="$vNum -1"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<scxml xmlns="http://www.w3.org/2005/07/scxml">
<state id="Compound1">
<state id="Basic1"/>
<state id="Basic2"/>
<state id="Basic3"/>
</state>
</scxml>
produces the wanted, correct output:
<scxml xmlns="http://www.w3.org/2005/07/scxml">
<state stateId="Compound1" id="0">
<state stateId="Basic1" id="1"/>
<state stateId="Basic2" id="2"/>
<state stateId="Basic3" id="3"/>
</state>
</scxml>
Simplest approach:
<xsl:template match="s:state">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="stateNum" namespace="http://msdl.cs.mcgill.ca/">
<xsl:value-of select="count(preceding::s:state)" />
</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
Not sure how your XSLT processor handles the preceding axis, so this is something to benchmark in any case.
I have an XSLT file generating plain HTML. I need to wrap some elements in CDATA blocks, so intend to use cdata-section-elements. But, if the element I want to have contain CDATA is only one <p> on the page, how do I get it to not put CDATA in all the other <p> elements?
The input data is this:
<item>
...
<g:category>Gifts under £10</g:category>
</item>
My XSL is:
<xsl:element name="a">
<xsl:attribute name="href">productlist.aspx</xsl:attribute>
<xsl:copy-of select="text()" />
</xsl:element>
I want this to render something like:
Gifts under £10
But all I get is:
Gifts under £10
Well assuming you have some way of targeting the <p> tag that you want to enclose in CDATA section, you could do something like:
<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="p[#test = 'test']">
<xsl:copy>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:apply-templates/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
In this case all <p> tags with an attribute test = 'test' will have CDATA applied to them, all other tags will just be output as normal.
The code I have is:
<xsl:element name="a">
<xsl:attribute name="href">
product.aspx?prod=<xsl:copy-of select="title/text()" />
</xsl:attribute>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:copy-of select="g:price" /> - <xsl:copy-of select="title/text()" />
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:element>