user1919189
user1919189

Reputation: 23

XSLT to filter elements from XML

I Have an XML like below.

<d2p1:Application> 
 <d2p1:Identifier>0001482022534</d2p1:Identifier> 
 <d2p1:Channel>Web</d2p1:Channel> 
 <d2p1:Type>Change</d2p1:Type> 
 <d2p1:Status>Received</d2p1:Status> 
 <d2p1:Value>Received</d2p1:Value> 
 <d2p1:FilterList> 
  <d2p1:Filter>Channel</d2p1:Filter> 
  <d2p1:Filter>Type</d2p1:Filter> 
  <d2p1:Filter>Value</d2p1:Filter> 
 </d2p1:FilterList> 
</d2p1:Application>

Desired Output:

<d2p1:Application> 
 <d2p1:Channel>Web</d2p1:Channel> 
 <d2p1:Type>Change</d2p1:Type> 
 <d2p1:Value>Received</d2p1:Value> 
</d2p1:Application>

My desired output is to remove the elements from XML which are not in <FilterList>. I need an XSLT to do this. The values in Filter list is dynamic. Can anyone help on this.

Upvotes: 2

Views: 4000

Answers (2)

Ian Roberts
Ian Roberts

Reputation: 122414

If you have the normal approach of using an identity template to copy everything and then overriding for the things you want to remove, then the problem becomes how to identify those things you want to remove. How about

<!-- NB - fix the xmlns:d2p1 to match the document -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:d2p1="urn:d2p1">

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

  <xsl:template match="d2p1:Application/*[
      not(local-name() = ../d2p1:FilterList/d2p1:Filter)]" />
</xsl:stylesheet>

This will remove any children of a d2p1:Application whose name does not match one of the filters contained in that same d2p1:Application element's FilterList (including the d2p1:FilterList element itself, if there's no <d2p1:Filter>FilterList</d2p1:Filter>).

This will work even if there is more than one d2p1:Application element in your document, with each being controlled by its own FilterList (I presume from the lack of namespace declarations that the XML you provided is a fragment of your document rather than a complete example).

Upvotes: 0

Tim C
Tim C

Reputation: 70648

One method could be to use a key, to look up your 'Filter' values

<xsl:key name="Filter" match="d2p1:Filter" use="." />

Then, you could just extend the XSLT Identity Transform to include a template that matches elements for which the key doesn't return anything.

<xsl:template match="d2p1:Application/*[not(key('Filter', local-name()))]" />

Here is the full XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:d2p1="d2p1">
   <xsl:output method="xml" indent="yes"/>
   <xsl:key name="Filter" match="d2p1:Filter" use="." />

   <xsl:template match="d2p1:Application/*[not(key('Filter', local-name()))]" />

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

When applied to to your sample XML (assuming it includes a namespace declaration for the prefix d2p1), the following is output

<d2p1:Application xmlns:d2p1="d2p1">
   <d2p1:Channel>Web</d2p1:Channel>
   <d2p1:Type>Change</d2p1:Type>
   <d2p1:Value>Received</d2p1:Value>
</d2p1:Application>

Do note you will have to change the namespace accordingly.

Upvotes: 2

Related Questions