XSLT template overriding - xslt

I have a small question regarding XSLT template overriding.
For this segment of my XML:
<record>
<medication>
<medicine>
<name>penicillin G</name>
<strength>500 mg</strength>
</medicine>
</medication>
</record>
In my XSLT sheet, I have two templates in the following order:
<xsl:template match="medication">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="medicine/name">
<text>!unauthorized information!</text>
</xsl:template>
What I want to do is to copy everything under the medication element to the output other than the "name" element (or any other element that I explicitly define). The final xml will be shown to the user in RAW XML form. In other words, the result I want is:
<record>
<medication>
<medicine>
<text>! unauthorized information!</text>
<strength>500 mg</strength>
</medicine>
</medication>
</record>
Whereas I am getting the same XML as input, i.e. without the element replaced by text. Any ideas why the second template match is not overriding the name element in the first one? Thanks in advance
--
Ali

Template order does not matter. The only case it possibly becomes considered (and this is processor-dependent) is when you have an un-resolvable conflict, i.e. an error condition. In that case, it's legal for the XSLT processor to recover from the error by picking the one that comes last. However, you should never write code that depends on this behavior.
In your case, template priority isn't even the issue. You have two different template rules, one matching <medication> elements and one matching <name> elements. These will never collide, so it's not a question of template priority or overriding. The issue is that your code never actually applies templates to the <name> element. When you say <xsl:copy-of select="."/> on <medication>, you're saying: "perform a deep copy of <medication>". The only way any of the template rules will fire for descendant nodes is if you explicitly apply templates (using <xsl:apply-templates/>.
The solution I have for you is basically the same as alamar's, except that it uses a separate processing "mode", which isolates the rules from all other rules in your stylesheet. The generic match="#* | node()" template causes template rules to be recursively applied to children (and attributes), which gives you the opportunity to override the behavior for certain nodes.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- ...placeholder for the rest of your code... -->
<xsl:template match="/record">
<record>
<xsl:apply-templates/>
</record>
</xsl:template>
<!-- end of placeholder -->
<xsl:template match="medication">
<!-- Instead of copy-of, whose behavior is to always perform
a deep copy and cannot be customized, define your own
processing mode. Rules with this mode name are isolated
from the rest of your code. -->
<xsl:apply-templates mode="copy-medication" select="."/>
</xsl:template>
<!-- By default, copy all nodes and their descendants -->
<xsl:template mode="copy-medication" match="#* | node()">
<xsl:copy>
<xsl:apply-templates mode="copy-medication" select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- But replace <name> -->
<xsl:template mode="copy-medication" match="medicine/name">
<text>!unauthorized information!</text>
</xsl:template>
</xsl:stylesheet>
The rule for "medicine/name" overrides the rule for "#* | node()", because the format of the pattern (which contains a "/") makes its default priority (0.5) higher than the default priority of "node()" (-1.0).
A complete but concise description of how template priority works can be found in "How XSLT Works" on my website.
Finally, I noticed you mentioned you want to display "RAW XML" to the user. Does that mean you want to display, for example, the XML, with all the start and end tags, in a browser? In that case, you'd need to escape all markup (e.g., "<" for "<"). Check out the XML-to-string utility on my website. Let me know if you need an example of how to use it.

Add
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
to your <xsl:template match="medicine/name">
And remove <xsl:template match="medication"> altogether!
<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="medicine/name">
<text>!unauthorized information!</text>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

Main differences between xsl:copy, xsl:next-match and xsl:copy-of

Please suggest the main differences between these XSLT functions, where results are same for these three functions for the below input and XSLT code (remove comment and execute). Suggest the particular importance of usage of these functions. Are these functions are differed in namespaces area. (XSLT2)
Input xml:
<root>
<a>The text a1
<b>The text b1</b>
<b>The text b2
<c>The text c1</c>
</b>
<c>The text c2</c>
</a>
</root>
XSLT:
<xsl:stylesheet version="2.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="b">
<xsl:element name="B"><xsl:next-match/></xsl:element>
</xsl:template-->
<!--xsl:template match="b">
<xsl:element name="B"><xsl:copy-of select="."/></xsl:element>
</xsl:template-->
<xsl:template match="b">
<xsl:element name="B">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
You currently cannot see a difference between your approaches because your example is still too simple. Let's describe the difference between <xsl:copy-of> and <apply-templates> first: <xsl:copy> makes a literal copy of the context node . which is for example:
<b>The text b2
<c>The text c1</c>
</b>
In the literal copy the XSLT processor does not check anymore if there are any matching rules for the child tag <c>. They are simply ignored.
The tag <xsl:apply-templates> however, applies all available template rules to any given depth, so if you had a rule for <c> it would be applied.
Hence: to see the difference between those first two options create a template match for <c> which does not make a literal copy of it.
Understanding the use of <xslt:next-match> is slightly more difficult. It requires you to know what the next best possible template match would be at the point that you call it. In your case, since you only have the default copy rule
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
next to the specific rule for <b> the default rule would be the next best one. And, of course the default rule does nothing else but copy the sub tree again using the <apply-templates>, so that you do not see any difference.
In order to see a difference there it would be necessary for rule to create a rule for <b> that is less specific than the one present but at the same time more specific that the default rule. This will probably be hard to do.

Excluding elements with xslt not working

I am trying to transform an xml file that contain a list of words and I am trying to exclude some elements from the resulting document, more concretely and
My List is as follows:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="merge.xsl"?>
<dic:englishWords xmlns:dic = "dictionary">
<dic:words>
<dic:englishWords xmlns:dic = "dictionary">
<dic:title>
English Dictionary
</dic:title>
<dic:author>
<dic:authorsName>
Author:
<dic:name>
User
</dic:name>
<dic:lastName>
Name
</dic:lastName>
</dic:authorsName>
</dic:author>
<dic:words>
<dic:name>Water</dic:name><br/>
<dic:name>Room</dic:name><br/>
<dic:name>Computer</dic:name><br/>
<dic:name>Book</dic:name><br/>
<dic:name>Garage</dic:name><br/>
<dic:name>Car</dic:name><br/>
<dic:name>Ship</dic:name><br/>
<dic:name>Food</dic:name><br/>
<dic:name>Coffee</dic:name><br/>
<dic:name>Program</dic:name><br/>
</dic:words>
</dic:englishWords>
The path to the list of words is contained in an xml file as follows:
<dic:dictionary xmlns:dic = "dictionary">
<dic:Logo>Logo</dic:Logo>
<dic:Author>User Name</dic:Author>
<dic:EnglishWords>english</dic:EnglishWords>
<dic:SwedishTranslation>swedish</dic:SwedishTranslation>
<dic:SwedishWords>swedish</dic:SwedishWords>
<dic:EnglishTranslation>english</dic:EnglishTranslation>
</dic:dictionary>
my transformation is as follows
<!--Declare a parameter with the nodes to be removed-->
<xsl:param name="removeElementsNamed" select="'|dic:author|dic:title'"/>
<!--create a template and call it remove node-->
<xsl:template match="node()|#*" name="removeNode">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!--remove the actual nodes-->
<xsl:template match="*">
<xsl:if test="not(contains($removeElementsNamed, concat('|',name(),'|')))">
<xsl:call-template name="removeNode"/>
</xsl:if>
</xsl:template>
I am trying to follow an example which I have found here:
how to exclude elements
...but in my case it does not work.
Any help will be appreciated...
Bluetxxth
So it looks like you're trying to exclude elements based on whether |ELEMENTNAME| is present in $removeElementsNamed, but dic:author is the only item in that list that has pipes on both sides. It might almost work if you did this:
<xsl:param name="removeElementsNamed" select="'|dic:author|dic:title|'"/>
However this is a bit of a hack.
A better approach would to just do something like this:
<xsl:template match="dic:author | dic:title" />
This should exclude dic:author and dic:title from the output.
Another issue is that this template is misnamed:
<xsl:template match="node()|#*" name="removeNode">
What this template would actually do, if it worked, would be to include nodes that were sent its way, but a template can't both have a match attribute and a name attribute. I would suggest rewriting your XSLT to be like this and starting from here:
<!--Declare a parameter with the nodes to be removed-->
<xsl:template match="dic:author | dic:title" />
<!--create a template and call it remove node-->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
You could match the element you don't want but not output anything.
<xsl:template match="nodeToMatch" />

Having trouble selecting properties with XSLT

I need to select Property1, and SubProperty2 and strip out any other properties. I need to make this future proof so that any new properties added to the xml won't break validation. iow's new fields have to be stripped by default.
<Root>
<Property1/>
<Property2/>
<Thing>
<SubProperty1/>
<SubProperty2/>
</Thing>
<VariousProperties/>
</Root>
so in my xslt I did this:
<xsl:template match="Property1">
<Property1>
<xsl:apply-templates/>
</Property1>
</xsl:template>
<xsl:template match="/Thing">
<SubProperty1>
<xsl:apply-templates select="SubProperty1" />
</SubProperty1>
</xsl:template>
<xsl:template match="*" />
The last line should strip anything I haven't defined to be selected.
This works to select my property1 but it always selects an empty node for SubProperty. The match on * seems to strip out the deeper object before my match on them can work.
I removed the match on * and it select my SubProperty with a value. So, how can I select the sub properties and still strip everything away that I am not using.
Thanks for any advise.
There are two problems:
<xsl:template match="*"/>
This ignores any element for which there isn't an overriding, more specific template.
Because there isn't a specific template for the top element Root it is ignored together with all of its subtree -- which is the complete document -- no output at all is produced.
The second problem is here:
<xsl:template match="/Thing">
This template matches the top element named Thing.
However in the provided document the top element is named Root. Therefore the above template doesn't match any node from the provided XML document and is never selected for execution. As the code inside its body is supposed to generate SubProperty1, no such output is generated.
Solution:
Change
<xsl:template match="*"/>
to:
<xsl:template match="text()"/>
And change
<xsl:template match="/Thing">
to
<xsl:template match="Thing">
The whole transformation becomes:
<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="Property1">
<Property1>
<xsl:apply-templates/>
</Property1>
</xsl:template>
<xsl:template match="Thing">
<SubProperty1>
<xsl:apply-templates select="SubProperty1" />
</SubProperty1>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
And when applied on the following XML document (as the provided is severely malformed it had to be fixed):
<Root>
<Property1/>
<Property2/>
<Thing>
<SubProperty1/>
<SubProperty2/>
</Thing>
<VariousProperties/>
</Root>
the result now is what is wanted:
<Property1/>
<SubProperty1/>

XSL parent selector

XML
<dsInventory>
<Room>
<RoomName>kantoor 1</RoomName>
<RoomId>1376257</RoomId>
</Room>
<Room>
<RoomName>Hal</RoomName>
<RoomId>1376258</RoomId>
</Room>
<Method>
<RoomId>1376257</RoomId>
<Name>test</Name>
</Method>
<Method>
<RoomId>1376258</RoomId>
<Name>test</Name>
</Method>
</dsInventory>
XSL
<xsl:for-each select="/dsInventory/Room">
<xsl:for-each select="/dsInventory/Method">
<xsl:if test="RoomId=../RoomId">
</xsl:if>
</xsl:for-each>
</xsl:for-each>
Problem
I'm trying to select the methods for the corresponding rooms based on the roomid. however the selector ../RoomId doesn't work. How do i get the value of the RoomId from the first for-each? Or what is the proper way to select the methods for the rooms?
Your problem here is context. The if condition is failing because it's running from the context of Method nodes, and therefore the XPath expression ../RoomID is looking for a RoomID node on dsInventory. There is no such node.
You don't say what output you want, but based on the assumption that you simply want to iterate over rooms and find the method for each, try this: (note also I use templates rather than for-each loops, which are best avoided in XSL).
You can run it and see the output at this XML Playground.
<!-- kick things off from the point of dsInventory -->
<xsl:template match="dsInventory">
<ul><xsl:apply-templates select='Room' /></ul>
</xsl:template>
<!-- room nodes -->
<xsl:template match='Room'>
<xsl:variable name='method' select='../Method[RoomId = current()/RoomId]' />
<li>
<xsl:value-of select='concat(RoomName," (",RoomId,")")' />:
method = <xsl:value-of select='concat($method/Name," (",$method/RoomId,")")' />
</li>
</xsl:template>
You havn't specified the version of XSLT you are working with or your expected output. I am going to assume you want XSLT 1.0, want to copy the document, but add the linked method name under the Room and drop the Methods.
This XSLT 1.0 style-sheet ...
<?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"/>
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Method"/>
<xsl:template match="Room">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:apply-templates select="../Method[RoomId=current()/RoomId]/Name"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
... when applied to your sample input document, will produce ...
<dsInventory>
<Room>
<RoomName>kantoor 1</RoomName>
<RoomId>1376257</RoomId>
<Name>test</Name>
</Room>
<Room>
<RoomName>Hal</RoomName>
<RoomId>1376258</RoomId>
<Name>test</Name>
</Room>
</dsInventory>
Explanation
Use the current() function to specify the current item relative to the sequence constructor for the Room template, which in this case is a Room element. In the predicate we compare current()/RoomId with the RoomId of the Method step of our select expression. Thus linked Names are copied under Room. No xsl:if, no xsl:for-each, no variables. All can be achieved in the select expression using the current() function.
<xsl:for-each select="/dsInventory/Room">
<xsl:for-each select="/dsInventory/Method">
<xsl:if test="RoomId=../RoomId"></xsl:if>
</xsl:for-each>
</xsl:for-each>
As the people, who answered this question pointed out, you are trying to compare a /dsInventory/Method/RoomId to a /dsInventory/RoomId and the latter doesn't exist in the provided XML document.
I'm trying to select the methods for the corresponding rooms based on
the roomid.
Here is a complete transformation, showing how to do this:
<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="Room">
<xsl:copy>
<xsl:apply-templates/>
<xsl:apply-templates mode="grab"
select="/*/Method[RoomId = current()/RoomId]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Method" mode="grab">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="Method | Method/RoomId"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document (the provided one with changed method names -- to be different from each other):
<dsInventory>
<Room>
<RoomName>kantoor 1</RoomName>
<RoomId>1376257</RoomId>
</Room>
<Room>
<RoomName>Hal</RoomName>
<RoomId>1376258</RoomId>
</Room>
<Method>
<RoomId>1376257</RoomId>
<Name>test1</Name>
</Method>
<Method>
<RoomId>1376258</RoomId>
<Name>test2</Name>
</Method>
</dsInventory>
the wanted, correct result is produced (methods added to rooms and the Method children of the top element deleted):
<dsInventory>
<Room>
<RoomName>kantoor 1</RoomName>
<RoomId>1376257</RoomId>
<Method>
<Name>test1</Name>
</Method>
</Room>
<Room>
<RoomName>Hal</RoomName>
<RoomId>1376258</RoomId>
<Method>
<Name>test2</Name>
</Method>
</Room>
</dsInventory>
Explanation:
The identity rule copies "as-is" every node for which it is selected for execution.
A template, overriding the identity template, matches Room. Here the Room element is "shallow-copied and templates are applied to its children (which selects the identity template for them and they are copied "as-is" to the output) and then templates are applied to any Method, whose RoomId child has the same value as the RoomId child of the currently matched (current()) Room element. Because we also want to later delete any Method element, here we specify that the wanted processing (different from deletion) is to be done in "grab" mode.
A second template overriding the identity template deletes all Method elements by simply matching them and having an empty body.
The template that matches a Method in mode "grab" simply shallow-copies it and applies templates to its children. Of its children the Name is matched only by the identity template and is copied "as-is". The RoomId child is matched by a more specific template than the identity template and the former is chosen for execution. The chosen template has no body, which effectively "deletes" the matched Method/RoomId from the output.

XSLT to Select Desired Elements When Nested In Not-Desired Elements

What XSLT would I use to extract some nodes to output, ignoring others, when the nodes to be be extracted are some times nested nodes to be ignored?
Consider:
<alpha_top>This prints.
<beta>This doesn't.
<alpha_bottom>This too prints.</alpha_bottom>
</beta>
</alpha_top>
I want a transform that produces:
<alpha_top>This prints.
<alpha_bottom>This too prints.</alpha_bottom>
</alpha_top>
This answer shows how to select nodes based on the presence of a string in the element tag name.
Ok, here is a better way
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="beta">
<xsl:apply-templates select="*"></xsl:apply-templates>
</xsl:template>
<xsl:template match="/|*|text()">
<xsl:copy>
<xsl:apply-templates select="*|text()"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This basically does an identity transform, but for the element you don't want to include I removed the xsl:copy and only applied templates on the child elements.
The following stylesheet works on your particular case, but I suspect you are looking for something a bit more generic. I'm also sure there is a simpler way.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="alpha_top"></xsl:apply-templates>
</xsl:template>
<xsl:template match="alpha_top">
<xsl:copy>
<xsl:apply-templates select="beta/alpha_bottom|text()"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="*|text()">
<xsl:copy>
<xsl:apply-templates select="*|text()"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I think, that once you have a reasonable understand of how XSLT traversal works (hopefully I answered that in your other question) this becomes quite simple.
You have several choices on how to do this. Darrell Miller's answer shows you have to process a whole document and strip out the elements you're not interested in. That's one approach.
Before I go further, I get the impression that you might not entirely 'get' the concept of context in XSLT. This is important and will make your life simpler. At any time in XSLT there is one and only context node. This is the node (element, attribute, comment, etc) currently being 'processed'. Inside a template called via xsl:select the node that has been selected is the context node. So, given your xml:
<alpha_top>This prints.
<beta>This doesn't.
<alpha_bottom>This too prints.</alpha_bottom>
</beta>
</alpha_top>
and the following:
<xsl:apply-templates select='beta'/>
and
<xsl:template match='beta'>...</xsl:template>
the beta node will be the context node inside the template. There's a bit more to it than that but not much.
So, when you start your stylesheet with something like:
<xsl:template match='/'>
<xsl:apply-templates select='alpha_top'/>
</xsl:apply-templates>
you are selecting the children of the document node (the only child element is the alpha_top element). Your xpath statement inside there is relative to the context node.
Now, in that top level template you might decide that you only want to process your alpha_bottom nodes. Then you could put in a statement like:
<xsl:template match='/>
<xsl:apply-templates select='//alpha_top'/>
</xsl:template>
This would walk down the tree and select all alpha_top elements and nothing else.
Alternatively you could process all your elements and simply ignore the content of the beta node:
<xsl:template match='beta'>
<xsl:apply-templates/>
</xsl:template>
(as I mentioned in my other reply to you xsl:apply-templates with no select attribute is the same as using select=''*).
This will ignore the content of the beta node but process all of it's children (assuming you have templates).
So, ignoring elements in your output is basically a matter of using the correct xpath statements in your select attributes. Of course, you might want a good xpath tutorial :)
The probably simplest solution to your problem is this:
<xsl:template match="alpha_top|alpha_bottom">
<xsl:copy>
<xsl:value-of select="text()" />
<xsl:apply-templates />
</xsl:copy>
</xs:template>
<xsl:template match="text()" />
This does not exhibit the same white-space behavior you have in your example, but this is probably irrelevant.