user3642597
user3642597

Reputation: 13

XSLT for XML Filtering

I'm new to XSLT -- I'm trying to utilize this to recreate (copy) an existing xml document based on a specified attribute.

As an example, with below XML, I want to get the lesson that has the attribute tags="6" from the mainGroup that has the attribute of group="Book1"

<Groups>
<mainGroup id="1" group="Book1">
<subGroup name="Chapter 1">
    <lesson name="Lesson1" tags="1" />
    <lesson name="Lesson2" tags="2" />
</subGroup>
<subGroup name="Chapter 2">
    <lesson name="Lesson1" tags="3" />
    <lesson name="Lesson2" tags="4" />
</subGroup>
<subGroup name="Chapter 3">
    <subGroup name="Chapter 3 Examples">
        <lesson name="Lesson2" tags="5" />
    </subGroup>
    <lesson name="Lesson1" tags="6" />
</subGroup>
</mainGroup>
<mainGroup id="1" group="Book2">
<subGroup name="Chapter 1">
    <lesson name="Lesson1" tags="1" />
    <lesson name="Lesson2" tags="2" />
</subGroup>
<subGroup name="Chapter 2">
    <lesson name="Lesson1" tags="3" />
    <lesson name="Lesson2" tags="4" />
</subGroup>
<subGroup name="Chapter 3">
    <subGroup name="Chapter 3 Examples">
        <lesson name="Lesson2" tags="6" />
    </subGroup>
    <lesson name="Lesson1" tags="5" />
</subGroup>
</mainGroup>
</Groups>

with an expected (anticipated) result of the below after XSL is applied

<Groups>
<mainGroup id="1" group="Book1">
<subGroup name="Chapter 3">
    <lesson name="Lesson1" tags="6" />
</subGroup>
</mainGroup>
 </Groups>

As far as the XSL, what I've come up with so far and the closest I've gotten is:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" indent="yes" encoding="UTF-8"/>
 <xsl:strip-space elements="*"/>


 <xsl:template match="node()|@*" name="identity">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>


// Remove all maingroups that are not book1
 <xsl:template match="mainGroup[not(@group='Book1')]" />

// Remove all lessons that do not have tags of 5
 <xsl:template match="lesson[not(@tags='5')]" />

</xsl:stylesheet>

which gives me the XML result of:

<Groups>
   <mainGroup id="1" group="Book1">
      <subGroup name="Chapter 1" />
      <subGroup name="Chapter 2" />
      <subGroup name="Chapter 3">
         <subGroup name="Chapter 3 Examples">
            <lesson name="Lesson2" tags="5" />
         </subGroup>
      </subGroup>
   </mainGroup>
</Groups>

I cannot figure out how to now remove the empty subGroups --

Some things I've tried (again please don't laugh - new at this XSL :) )

Hoping this removed those that didn't have a lesson node - but didn't seem to do anything

 <xsl:template match="subGroup[count(lesson) = 0]" />

 <xsl:template match="subGroup[not(node())]" />

and even something like this ---

<xsl:template match="subGroups" mode="copy">
     <xsl:choose>
       <xsl:when test="count(./*)">
         <xsl:copy>
           <xsl:apply-templates select="@*|node()" mode="copy"/>
         </xsl:copy>
       </xsl:when>
       <xsl:otherwise>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:template>

but after hours of no success I'm throwing in the towel - I'm sure someone here probably reading this is thinking -- ahhh, you just need to add or do this -- so any help would be appreciated.

Thanks

Upvotes: 1

Views: 116

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 116982

Instead of buying everything wholesale, then trying to get rid of the stuff you don't need, why don't you just pick what you do need in the first place?

<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="/Groups">
    <xsl:copy>
        <xsl:apply-templates select="mainGroup[@group='Book1']"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="mainGroup">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates select="subGroup[lesson[@tags='6']]"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="subGroup">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:copy-of select="lesson[@tags='6']"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Or, if you prefer:

<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="/">
    <Groups>
        <xsl:for-each select="Groups/mainGroup[@group='Book1']/subGroup/lesson[@tags='6']">
            <mainGroup>
                <xsl:copy-of select="../../@*"/>
                <subGroup>
                    <xsl:copy-of select="../@*"/>
                    <xsl:copy-of select="."/>
                </subGroup>
            </mainGroup>
        </xsl:for-each>
    </Groups>
</xsl:template>

</xsl:stylesheet>

Upvotes: 1

Ian Roberts
Ian Roberts

Reputation: 122364

The key thing is that template rules match against the input XML, so you have to suppress those subGroup elements that don't contain the lesson you want:

<xsl:template match="subGroup[not(.//lesson/@tags = '5')]"/>

For cases where the rules on which subgroups will ultimately end up empty are more complex it may be easier to do a two stage transformation, with a first pass as you're currently doing and then a second pass that processes the results of the first to strip out the remaining empty tags. In XSLT 2.0 this is straightforward

<xsl:variable name="pass1">
  <xsl:apply-templates />
</xsl:variable>
<xsl:apply-templates select="$pass1" mode="pass2"/>

But in 1.0 it requires a node-set extension function. You can find plenty of examples of this technique in other questions if you want to go that route.

Upvotes: 1

Related Questions