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.
Related
I want an xml attribute one level up in an xml structure.
As for requested I show a more detailed example:
<items>
<kitchen>
<furnitures>
<chairs type="wood">
<chair_1 color="green" legs="4"/>
</chairs>
<tables type="stone">
</tables>
</furnitures>
</kitchen>
</items>
And I want to output this:
<items>
<kitchen>
<furnitures>
<chairs type="wood"/>
<chair_1 color="green" legs="4"/>
<tables type="stone">
</tables>
</furnitures>
</kitchen>
</items>
As you see I move char_1 to under from under
<xsl:template match="node()">
<xls:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
<xsl:apply-templates select="text()">
</xsl:copy>
</xsl:template>
<xsl:template match="/items/kitchen/furnitures/chairs">
<xsl:choose>
<xsl:when test="chair_1">
<xsl:copy>
<xsl:apply-templates select="child::node()[not(self:chair_1)]|#*|text()"/>
</xsl:copy>
<xsl:apply-templates select="chair_1"/>
</xsl:when>
<!----- edit -->
<xsl:otherwise>
<xsl:copy>
<xls:apply-templates select ="#*|node()"/>
</xsl:copy>
<xsl:apply-templates select="settings"/>
<xsl:text>
</xsl:text>
<chair_1 color="green" legs="4"/>
</xls:otherwise>
</xls:choose>
</xsl:template>
So, my main problem is, that my copy don't contain line-breaks.
Please keep in mind, I am using PHP:Xsltproc, on my dev-comp the indentation works fine, but with PHP's xsltproc it isn't fine, and drops the line breaks.
so the output like this:
<items>
<kitchen>
<furnitures>
<chairs type="wood"/><chair_1 color="green" legs="4"/>
<tables type="stone">
</tables>
</furnitures>
</kitchen>
</items>
Which is fine, but not correctly indented.
(Disclaimer: there could be some typos, as this is not the original XML, and of course I am using the required stylsheet, version, phpversion, xml version tags, and of course my output method is xml and indent="yes" )
UPDATE:
when I have the second "WHEN", (in case there is no chair_1) I want to "paste" it into the code. But the indentation fails, it makes the whole copy into one line. What could be the problem?
The templates you present do not effect the transformation you say they do, at least not by themselves. In fact, they are not even valid XSL.
After the obvious syntax errors are corrected, the resulting template matching node() explicitly rearranges whitespace around elements (where it is not stripped) and does nothing effective at preserving attributes. You seem to intend for it to be an identity transform, but the conventional identity transform goes like this:
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
Observe in particular that the node() test matches both elements and text nodes (and comments and processing instructions), but not attributes, and that if you do not want to rearrange an element's text nodes relative to its child elements then you must transform them all via the same xsl:apply-templates directive.
Additionally, do note that in many XML applications, whitespace-only runs of text separating tags are insignificant. I don't see any reason to think that your particular application is among the exceptions, so you really ought to ask yourself is "does it matter?"
Supposing that it does matter -- e.g. because you want improved human readability even though the XML is primarily meant to be consumed by a computer program that doesn't care about the indentation -- you should consider letting your XSL processor provide indentation for you. To do this, start by stripping all the insignificant whitespace from the input document:
<xsl:strip-space elements="*"/>
and follow up by asking the processor to provide hierarchical indentation for you:
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
Both of those directives have global effect, and they need to appear as direct children of the xsl:stylesheet or xsl:transform element. Here's the cleaned up and updated version:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/items/kitchen/furnitures/chairs">
<xsl:choose>
<xsl:when test="chair_1">
<xsl:copy>
<!-- also simplified the 'select' expression below: -->
<xsl:apply-templates select="node()[not(self::chair_1)]|#*"/>
</xsl:copy>
<xsl:apply-templates select="chair_1"/>
</xsl:when>
<xsl:otherwise>
BUNCH of code there if we don't have chair_1
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Doing some work with xsl - first time I've done anything serious, and I've hit something which I can't explain. Easiest way to show it is with the identity transform:
This works:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
This doesn't (says "Unable to apply transformation on current source"):
<xsl:template match="#*|node()" xml:space='preserve'>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
This does:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="node()" xml:space='preserve'/>
</xsl:copy>
</xsl:template>
OK, I can see what's happening. But I don't understand why. Why does xml:space not want to play nicely with attributes? Just curious.
BTW, this is using the xsl translator that's built into Notepad++. Perhaps I shouldn't trust it?
What are you trying to accomplish? xml:space="preserve" tells XML-consuming applications that you want to preserve whitespace-only text nodes that are descendants of the element that xml:space is an attribute of. In this example, you have xml:space as an attribute of <xsl:apply-templates>, but <xsl:apply-templates> has no whitespace-only text node descendants, so xml:space has no possible effect.
I think you wanted to preserve whitespace-only text nodes from the input XML document (not from the XSLT stylesheet). In that case, you need xml:space to be in the input XML document, not in the XSLT stylesheet. The stylesheet can have xsl:preserve-space-elements="*", but that's already the default, unless you have xsl:strip-space-elements set.
Yes, I would be inclined to wonder whether the XSLT processor used by Notepad++ (libxml) is doing something illegit. As a good diagnostic, try a respected processor like Saxon and see if you get any errors.
Either that, or just remove xml:space from your stylesheet, since it won't do you any good even if the processor doesn't throw an error.
Suggestion:
Just use
<xsl:output method="html" indent="yes"/>
as the first child of <xsl:stylesheet>.
The indent="yes" will prevent all the output elements from being crammed together on one line, so you can read the results.
Whitespace is not preserved for attributes according to specification - it is highlighted in this posting. Preserving attribute whitespace in XSLT
This is a slightly version of other question posted here:
XSLT: change node inner text
Imagine i use XSLT to transform the document:
<a>
<b/>
<c/>
</a>
into this:
<a>
<b/>
<c/>
Hello world
</a>
In this case i can't use neither the
<xsl:strip-space elements="*"/>
element or the [normalize-space() != ''] predicate since there is no text in the place where i need to put new text. Any ideas? Thanks.
Here is what I would do:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<!-- identity template to copy everything unless otherwise noted -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- match the first text node directly following a <c> element -->
<xsl:template match="text()[preceding-sibling::node()[1][self::c]]">
<!-- ...and change its contents -->
<xsl:text>Hello world</xsl:text>
</xsl:template>
</xsl:stylesheet>
Note that text nodes contain "surrounding" whitespace - in the sample XML in the question the matched text node is whitespace only, which is why the above works. It will stop to work as soon as the input document looks like this:
<a><b/><c/></a>
because here is no text node following <c>. So if this is too brittle for your use case, an alternative would be:
<!-- <c> nodes get a new adjacent text node -->
<xsl:template match="c">
<xsl:copy-of select="." />
<xsl:text>Hello world</xsl:text>
</xsl:template>
<!-- make sure to remove the first text node directly following a <c> node-->
<xsl:template match="text()[preceding-sibling::node()[1][self::c]]" />
In any case, stuff like the above makes clear why intermixing of text nodes and element nodes is best avoided. This is not always possible (see XHTML). But when you have the chance and the XML is supposed to be purely a container for structural data, staying clear of mixed content makes your life easier.
This transformation inserts the desired text (for generality) after the element named a7:
<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()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a7">
<xsl:call-template name="identity"/>
<xsl:text>Hello world</xsl:text>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<a>
<a1/>
<a2/>
.....
<a7/>
<a8/>
</a>
the desired result is produced:
<a>
<a1/>
<a2/>
.....
<a7/>Hello world
<a8/>
</a>
Do note:
The use of the identity rule for copying every node of the source XML document.
The overriding of the identity rule by a specific template that carries out the insertion of the new text.
How the identity rule is both applied (on every node) and called by name (for a specific need).
edit: fixed my fail to put proper syntax in.
<xsl:template match='a'>
<xsl:copy-of select="." />
<xsl:text>Hello World</xsl:text>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
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.
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>