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

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.

Related

XSL: replacing <br><br/> with another character

I have an input xml document that I am transforming which has this content in an node:
<misc-item>22 mm<br></br><fraction>7/8</fraction> in.</misc-item>
When I create a variable by selecting 'misc-item', the br and fraction tags disappear. However, if I create a variable using 'misc-item/br' and test to see if it is finding the br, the test seems to work.
What I want to do is make the
'<br></br>'
into a space or semicolon or something, but I have had no luck. I tried getting the siblings of 'misc-item/br', but it has none. I checked the child count of 'misc-item' and it is one.
Any help greatly appreciated.
I looked at the post identified as possible dupe. I tried this to no avail:
<xsl:template match="#*|node()" mode='PageOutput'>
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="PageOutput" />
</xsl:copy>
</xsl:template>
<xsl:template match="br" mode='PageOutput'>
<xsl:value-of select="' '" />
</xsl:template>
Since I am not ignoring an element as in the suggested dupe, but rather substituting, this doesn't seem to be quite right.
When I create a variable by selecting 'misc-item', the br and fraction tags disappear. However, if I create a variable using 'misc-item/br' and test to see if it is finding the br, the test seems to work.
When you create a variable you're storing a reference to the misc-item node in the variable. If you ask for the value-of that node you'll get just the text, with elements stripped out, but the variable still holds the node itself.
This is probably something you need to tackle using apply-templates instead of value-of. A common theme is to have an "identity template" which essentially copies everything as-is but can be overridden with different behaviour for certain nodes by providing more specific templates.
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<!-- replace any br element with a semicolon -->
<xsl:template match="br">;</xsl:template>
You can use a mode to restrict these templates for use in specific situations only
<xsl:template match="#*|node()" mode="strip-br">
<xsl:copy><xsl:apply-templates select="#*|node()" mode="strip-br" /></xsl:copy>
</xsl:template>
<!-- replace any br element with a semicolon -->
<xsl:template match="br" mode="strip-br">;</xsl:template>
and now you can use e.g.
<xsl:apply-templates select="$miscitem/node()" mode="strip-br" />
instead of <xsl:value-of select="$miscitem"/> to get the result you're after.

xml:space='preserve' doesn't seem to get on with xsl:apply-templates select="node()"

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

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/>

XSLT: Using a different way of processing within the current way of processing

Below I'm trying to match certain nodes.
<xsl:template match="nodes">
<element>
<xsl:apply-templates select="nodes" mode="different" />
</element>
</xsl:template>
Now, there are multiple ways of processing for the same nodes. I want to use this different way of processing within the current way of processing. That's why I perform apply-templates on the same selection, which is nodes, however the mode is different now.
Here's how the different mode could look like:
<xsl:template match="nodes" mode="different">
<!-- another way of processing these nodes -->
</xsl:template>
Now, this does not work. Only the first type of processing is processed and the apply-templates call is simply not applied.
To be a bit more specific:
<xsl:template match="Foundation.Core.Association.connection">
<xsl:for-each select="Foundation.Core.AssociationEnd">
<someElement>
<xsl:apply-templates select="Foundation.Core.Association.connection" mode="different" />
</someElement>
</xsl:for-each>
</xsl:template>
As you can see, I select Foundation.Core.Association.connection. Of course this is wrong, but how do I refer to this element given the current element and position? Given Derek his comment, that should do it.
What am I doing wrong, how can I get what I want using XSLT? What could be another approach to solve this problem?
Thanks.
if "nodes" is referring to the same exact set of nodes in the containing match, try:
<xsl:template match="nodes">
<element>
<xsl:apply-templates select="." mode="different" />
</element>
</xsl:template>
<xsl:template match="Foundation.Core.Association.connection">
<xsl:for-each select="Foundation.Core.AssociationEnd">
<someElement>
<xsl:apply-templates
select="Foundation.Core.Association.connection"
mode="different" />
As you can see, I select
Foundation.Core.Association.connection.
Of course this is wrong, but how do I
refer to this element given the
current element and position?
Use:
<xsl:apply-templates select=".." mode="different" />
The element you want to process differently is the parent of the current node.
Of course, most likely this convoluted processing is not necessary at all, which would be confirmed, had you been able to show more of the XML document and to formulate the problem in a more succint way.

XSLT template overriding

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>