Reputation: 169
Hi I have an xml file as follow:
<root>
<body>
<place attr="1" id="1" ref="www.example.com">
<name>abc
</name>
</place>
<place attr="1" id="2" ref="www.example.com">
<name>def
</name>
</place>
<place attr="2" id="3">
<place attr="3" id="4" ref="www.example.com">
<name>efg
</name>
</place>
</place>
</body>
</root>
I want to get all element <place>
that have a children <name>
and without the text between element.
What I want in output is something like:
<root>
<place attr="1" id="1" ref="www.example.com" />
<place attr="1" id="2" ref="www.example.com" />
<place attr="3" id="4" ref="www.example.com" />
</root>
My xsl code that return all <place>
tag (which I don't want) + text between <name>
tag:
<xsl:template match="root">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="place">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
xml output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<place attr="1" id="1" ref="www.example.com">abc</place>
<place attr="1" id="2" ref="www.example.com">def</place>
<place attr="2" id="3">
<place attr="3" id="4" ref="www.example.com">efg</place>
</place>
</root>
Upvotes: 0
Views: 1015
Reputation: 1180
The key is your apply-templates
statement.
As you state, you "want only the <place>
tag who have as a child a <name>
". So you should select just those.
Your current code:
<xsl:template match="root">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
When you use just <xsl:apply-templates/>
, that's the same as saying "apply templates to *everything* under this". So you wind up catching even those <place>
elements that you don't want.
To be more selective and get just what you want, we could instead say:
<xsl:apply-templates select="descendant::place[name]"/>
We use the select
statement to, well, select. :) We don't want to apply templates to everything, just to specific things, and this is how we specify which things.
We use the descendant::
axis because we want to catch all the <place>
elements contained within (that is, descendant of) the <root>
element. If we said just select="place[name]"
instead, that would mean "apply templates to just those <place>
elements that are direct children of this <root>
element. Since there aren't any <place>
elements that are direct children of this <root>
element, that wouldn't do anything useful for you.
We use the [name]
predicate to specify the condition that we only want <place>
elements that have <name>
child elements. This lets us exclude <place attr="2" id="3">
, which contains another <place>
but doesn't have any <name>
children of its own.
I hope this helps. Please comment if there's anything here that you don't understand.
I mis-gauged how much detail to include in my earlier answer. :)
Here's a full working example, with annotations.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<!-- Start at the logical root / since every XSLT *must* start there. -->
<xsl:template match="/">
<!-- We just pass everything along. NOTE: We NEED to define templates
for anything special we want to do, beyond just the default
XSLT behavior of outputting the string contents minus any elements. -->
<xsl:apply-templates/>
</xsl:template>
<!-- We want to get the <root> element and attributes in our output, so
we define a template to do that.-->
<xsl:template match="root">
<xsl:copy>
<xsl:copy-of select="@*"/>
<!-- We also want to process the content of <root>, so we use
`xsl:apply-templates`. Since we also want to be _selective_
about what we process, we also specify a `select` statement
with the XPath needed to identify what we want.
Again, we NEEd to define a template to process this,
or we'll just get the text string content and none of
the elements. -->
<xsl:apply-templates select="descendant::place[name]"/>
</xsl:copy>
</xsl:template>
<!-- Here we define what to do with the <place> elements that have
<name> children. Since your <place attr="2" id="3"> element
has no <name> child, it gets omitted from the output. -->
<xsl:template match="place[name]">
<xsl:copy>
<xsl:copy-of select="@*"/>
</xsl:copy>
</xsl:template>
<!-- Lastly, we define one more template that says "capture everything
else, and *don't* output anything". This way, we don't get the text
string output of anything we haven't explicitly defined above. -->
<xsl:template match="*"/>
</xsl:stylesheet>
We get just the desired elements, without their contained text.
Upvotes: 2
Reputation: 1882
First, you need to understand the XSLT process model: starting by the document root of the input document, the processor tries to match this node with some rule described by th pattern of some template. There are some built-in rules: basicaly, they trasverse level by level the input document and from first to last in document order.
That's why you need a rule for copying root
and place
elements, and one rule for not output the text nodes.
With this input
<root>
<body>
<place attr="1" id="1" ref="www.example.com">
<name>abc
</name>
</place>
<place attr="1" id="2" ref="www.example.com">
<name>def
</name>
</place>
<place attr="2" id="3">
<place attr="3" id="4" ref="www.example.com">
<name>efg
</name>
</place>
</place>
</body>
</root>
This stylesheet
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root|place[name]">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Output
<root>
<place attr="1" id="1" ref="www.example.com" />
<place attr="1" id="2" ref="www.example.com" />
<place attr="3" id="4" ref="www.example.com" />
</root>
Upvotes: 1
Reputation: 117053
To get the requested output, you could do simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<xsl:copy>
<xsl:for-each select=".//place[name]">
<xsl:copy>
<xsl:copy-of select="@*"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1