marOne
marOne

Reputation: 169

XSLT: Get an element that has a specific sub element and without text

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

Answers (3)

Eir&#237;kr &#218;tlendi
Eir&#237;kr &#218;tlendi

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

Breaking things down

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.

Update: full working example

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

Alejandro
Alejandro

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

michael.hor257k
michael.hor257k

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

Related Questions