PDA

View Full Version : XSLT template for filtering nodes with known attributes.


wvxvw
09-26-2009, 02:43 PM
I'm trying to do the filtering using XSLT template.
I have this input:
<data>
<foo name="someName"/>
<bar name="someOtherName"/>
<qwerty name="someName">
<nested-node/>
</querty>
</data>

And i want to get this output:
<data>
<foo name="someName"/>
<qwerty name="someName">
<nested-node/>
</querty>
</data>

No luck so far...
Any help appreciated. And, please, I know where's the manual... I've tried to read it... but it's so much "water" in there and so difficult to understand what it really wants to deliver... so, just an example would help me much better..
TIA

EDIT:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="data/*[@name='someName']">
<xsl:copy>
<xsl:apply-templates select="*|@*"/>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>
I think this is the closest I could get, but this doesn't copy attributes into attributes and nested nodes are ignored...

EDIT:
Ok, I've got it working with something like this:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8"
omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="*">
<xsl:if test="@name='someName'">
<xsl:element name="{name()}">
<xsl:for-each select="@*">
<xsl:attribute name="{name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
<xsl:value-of select="."/>
<xsl:for-each select="./*">
<xsl:call-template name="copyAll"/>
</xsl:for-each>
</xsl:element>
</xsl:if>
<xsl:apply-templates/>
</xsl:template>

<xsl:template name="copyAll">
<xsl:element name="{name()}">
<xsl:for-each select="@*">
<xsl:attribute name="{name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
<xsl:value-of select="."/>
<xsl:for-each select="./*">
<xsl:call-template name="copyAll"/>
</xsl:for-each>
</xsl:element>
</xsl:template>

</xsl:stylesheet>
However, it seems like very clumsy to me, so, if you have a better idea - please tell! :)

jasonJ
09-28-2009, 07:40 PM
So to bottom line is that you simply want to discard elements with name="someOtherName". Right?

Here's my attempt:

<?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" encoding="utf-8" indent="yes"/>

<!-- 1) Identity transform -->
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>

<!-- 2) Filter out elements -->
<xsl:template match="data">
<xsl:copy>
<xsl:apply-templates select="*[@name != 'someOtherName']" />
</xsl:copy>
</xsl:template>

</xsl:stylesheet>


Firstly, you create a copy of the input document using the identity transformation.
Secondly, you filter out elements with the attribute name = "someOtherName".

The resulting xml should be

<data>
<foo name="someName"/>
<qwerty name="someName">
<nested-node/>
</qwerty>
</data>

wvxvw
09-28-2009, 08:17 PM
Firstly, thanks for the input.
Erm... close but not exactly, there may be unlimited number of "someOtherNames", so, what I want to do is to find the nodes that have @name="someName" and then copy only those nodes along with all their content (be it text or other nodes - doesn't matter). The copied content doesn't need to match any rule.
If you're familiar with E4X, then in E4X it would be something like this:
var xml:XML =
<data>
<foo name="someName"/>
<bar name="someOtherName"/>
<qwerty name="someName">
<nested-node/>
</qwerty>
</data>;

var result:XML = <{xml.localName()}>{xml..*.(valueOf().@name == "someName")}</{xml.localName()}>;
trace(result.toXMLString());
/*
<data>
<foo name="someName"/>
<qwerty name="someName">
<nested-node/>
</qwerty>
</data>
*/

EDIT: I also know that there won't be any nested nodes that match the rule, i.e. situation like this is not possible in the input XML, so, no need to care about occasionally finding a nested node that also matches the rule for the parent:
<data>
<foo name="someName"/>
<bar name="someOtherName"/>
<qwerty name="someName">
<nested-node/>
<!-- this shouldn't happen in input XML -->
<foo name="someName"/>
</qwerty>
</data>

jasonJ
09-28-2009, 08:52 PM
Well, just change this line

<xsl:apply-templates select="*[@name != 'someOtherName']" />

to

<xsl:apply-templates select="*[@name = 'someName']" />

You still get the same result but now you can reference the node by its "someName" name attribute...

wvxvw
09-28-2009, 10:24 PM
Thanks again, but nope... it didn't do it... what it did is just a plain copy of the entire document...
I've attached the screenshot where it shows the actual result for this transformation. The red frames show what node I wanted to extract.

jasonJ
09-29-2009, 09:29 PM
Thanks again, but nope... it didn't do it... what it did is just a plain copy of the entire document...
Hmmm, could be that with your real xml data it doesn't work that well...
As I can deduce from the picture, your xml source data consists of some nested <imgdir> elements, yet you still seem to be matching with <data> (here: <xsl:template match="data">) ;)

PS. Nice xsl editor you've got there. What is that?

wvxvw
09-29-2009, 10:34 PM
Oh, I see, silly of me... however, it's still no go...
<?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" encoding="utf-8" indent="yes"/>

<!-- 1) Identity transform -->
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>

<!-- 2) Filter out elements -->
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="*[@name = 'info']" />
</xsl:copy>
</xsl:template>

</xsl:stylesheet>
That's how it looks now, and this is what I'm getting:
<?xml version="1.0" encoding="utf-8"?>
<imgdir>
<imgdir/>
</imgdir>
Which is even more puzzling and unpredictable than it was before :)
The editor I'm using is something that popped up first when googling for free XSLT editor, and it was EditiX... I cannot tell if it's better than anything else, I don't have much to compare it to. I actually needed to test some C# app I'm making and I needed some kind of substitute for E4X or XPath to make search and replace in XML... I'm not really a C# person, so, I keep bumping in all sorts of problems with that thing. Have no idea why would anyone make such a complicated and difficult to use tool. In spite of that overall impression of C# and MS tools in general is very good :)

EDIT: I think I may guess why the result is like it is... that is because the second template is also applied to the root node, which doesn't match... but, it doesn't explain why it copies anything at all... or why attributes of the root node aren't copied... and, correct me if I'm wrong, but, it seems like it is mandatory to set some mask / rule for attributes for them to be copied at all... which is just weird...