6

i am trying to paas the dynamic parameter while calling the template to suppress nodes from the xml.

I would call this template like:

transform employee.xml suppress.xsl ElementsToSuppress=id,fname 

employee.xml

<?xml version="1.0" encoding="utf-8" ?>
<Employees>
  <Employee>
    <id>1</id>
    <firstname>xyz</firstname>
    <lastname>abc</lastname>
    <age>32</age>
    <department>xyz</department>
  </Employee>
  <Employee>
    <id>2</id>
    <firstname>XY</firstname>
    <lastname>Z</lastname>
    <age>21</age>
    <department>xyz</department>
  </Employee>
</Employees>

Suppress.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="2.0" xmlns:elements="http://localhost">

  <elements:name abbrev="id">id</elements:name>
  <elements:name abbrev="fname">firstname</elements:name>

  <xsl:param name="ElementsToSuppress" ></xsl:param>

  <xsl:variable name="tokenizedSample" select="tokenize($ElementsToSuppress,',')"/>
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    <xsl:for-each select="$tokenizedSample">
      <xsl:call-template name ="Suppress"  >
        <xsl:with-param  name="parElementName">
          <xsl:value-of select="."/>
        </xsl:with-param>
      </xsl:call-template>
    </xsl:for-each>

  </xsl:template>






  <xsl:template name="Suppress">
    <xsl:param name="parElementName" select="''"></xsl:param>
    <xsl:variable name="extNode" select="document('')/*/elements:name[@abbrev=$parElementName]"/>
    <xsl:call-template name="test" >
      <xsl:with-param name="parElementName" >
        <xsl:value-of select="$extNode"/>
      </xsl:with-param>
    </xsl:call-template>
  </xsl:template>


  <xsl:template name="test"  match="*[name() = $parElementName]" >
    <xsl:param name="parElementName" select="''"></xsl:param>
    <xsl:call-template name="SuppressElement" />    
  </xsl:template>

  <xsl:template name="SuppressElement" />

</xsl:stylesheet>

Can we achieve output by using this or some other way? The ideal way is to pass the comma separated abbreviations of nodes and suppress them in one call.

Any help will be appreciated.

Regards,

AB

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
atif
  • 1,137
  • 7
  • 22
  • 35
  • atif, consider to post the output sample you want to create for the input sample you have posted, then we might be able to suggest an XSLT 2.0 way of achieving that. – Martin Honnen Dec 10 '10 at 16:50
  • transform employee.xml suppress.xsl ElementsToSuppress=id,fname Here is the output: abc 32 xyz Z 21 xyz Please consider that it should be parametirized becasue i might passed the parameters to remove other nodes too. Like transform employee.xml suppress.xsl ElementsToSuppress=lname,age and now the output shold remove lastname and age only. thx – atif Dec 10 '10 at 17:26
  • Good question, +1. See my answer for a simple and not long XSLT 1.0 solution. It is good to know that this problem can be solved easily with XSLT 1.0 and it doesn't ultimately require XSLT 2.0. :) – Dimitre Novatchev Dec 11 '10 at 03:39
  • Also note, that my solution handles not only elements, but processing instructions, too. – Dimitre Novatchev Dec 11 '10 at 16:30

3 Answers3

7

You don't need XSLT 2.0 for this processing.

This XSLT 1.0 transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 <xsl:param name="pNodesToSuppress"
      select="'id,fname,pi'"/>

  <my:toSuppress>
   <name abbrev="id">id</name>
   <name abbrev="fname">firstname</name>
   <name abbrev="pi">somePI</name>
  </my:toSuppress>

 <xsl:variable name="vToSuppressTable" select=
  "document('')/*/my:toSuppress/*"/>

 <xsl:variable name="vNamesToSupress">
  <xsl:for-each select=
   "$vToSuppressTable
     [contains(concat(',',$pNodesToSuppress,','),
               concat(',',@abbrev, ',')
               )
     ]">
   <xsl:value-of select="concat(',',.)"/>
   </xsl:for-each>
   <xsl:text>,</xsl:text>
 </xsl:variable>

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

 <xsl:template match="node()" priority="0.01">
  <xsl:if test=
     "not(contains($vNamesToSupress,
                   concat(',',name(),',')
                   )
          )">
   <xsl:call-template name="identity"/>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document (with an added processing instruction to demonstrate how we can also delete PIs -- not only elements):

<Employees>
  <Employee>
    <id>1</id>
    <firstname>xyz</firstname>
    <lastname>abc</lastname>
    <age>32</age>
    <department>xyz</department>
  </Employee>
  <?somePI This PI will be deleted ?>
  <Employee>
    <id>2</id>
    <firstname>XY</firstname>
    <lastname>Z</lastname>
    <age>21</age>
    <department>xyz</department>
  </Employee>
</Employees>

produces the wanted, correct results:

<Employees>
   <Employee>
      <lastname>abc</lastname>
      <age>32</age>
      <department>xyz</department>
   </Employee>
   <Employee>
      <lastname>Z</lastname>
      <age>21</age>
      <department>xyz</department>
   </Employee>
</Employees>

Do note:

  1. This is a pure XSLT 1.0 transformation.

    2.Not only elements, but also processing instructions are deleted, when their name is specified via the external parameter $pNodesToSuppress.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • Hi Martin, When i try to modify your script by doing following changes,I am getting error An error occurred matching pattern {*[@relationship = $relationship-to-suppress]}: Cannot convert string to type {xs:QName} Pls help. – atif Dec 13 '10 at 17:20
  • @atif: You are not commenting Martin's answer :) – Dimitre Novatchev Dec 13 '10 at 17:21
  • Thxs for letting me know :) Your solution works too perfectlybut I actually need to work on 2.0 becasue I want to apply trnasformation to multiple files. – atif Dec 13 '10 at 19:07
  • @aif: This solution works perfectly not only with XSLT 1.0 but with XSLT 2.0, too. There is nothing that stops you from choosing it. Just change the `version` attribute to `2.0` – Dimitre Novatchev Dec 13 '10 at 19:54
4

I don't get why the parameter value "fname" would suppress an element called "firstname" but apart from that you might simply want

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs"
  version="2.0">

  <xsl:param name="ElementsToSuppress" as="xs:string" select="'id,firstname'"/>
  <xsl:variable name="names-to-suppress" as="xs:QName*"
    select="for $s in tokenize($ElementsToSuppress, ',') return QName('', $s)"/>

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

  <xsl:template match="*[node-name(.) = $names-to-suppress]"/>

</xsl:stylesheet>

[edit] I missed that your sample stylesheet seemed to contain a mapping from those parameters to the elements names in the sample input. In that case here is an adapted version of the stylesheet to handle that case:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:data="http://example.com/data"
  xmlns:elements="http://example.com/elements"
  exclude-result-prefixes="xs data elements"
  version="2.0">

  <data:data>
    <elements:name abbrev="id">id</elements:name>
    <elements:name abbrev="fname">firstname</elements:name>
  </data:data>

  <xsl:key name="e-by-abbrev" match="elements:name" use="@abbrev"/>

  <xsl:param name="ElementsToSuppress" as="xs:string" select="'id,fname'"/>
  <xsl:variable name="names-to-suppress" as="xs:QName*"
    select="for $s in tokenize($ElementsToSuppress, ',') return QName('', key('e-by-abbrev', $s, document('')/xsl:stylesheet/data:data))"/>

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

  <xsl:template match="*[node-name(.) = $names-to-suppress]"/>

</xsl:stylesheet>

[second edit to implement further request] Here is a sample that also checks the relationship attribute values:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:data="http://example.com/data"
  xmlns:elements="http://example.com/elements"
  exclude-result-prefixes="xs data elements"
  version="2.0">

  <xsl:param name="ElementsToSuppress" as="xs:string" select="'id'"/>

  <xsl:param name="parVAObjectRelationship" as="xs:string" select="'a,b'"/>

  <xsl:variable name="names-to-suppress" as="xs:QName*"
    select="for $s in tokenize($ElementsToSuppress, ',') return QName('', key('e-by-abbrev', $s, document('')/xsl:stylesheet/data:data))"/>

  <xsl:variable name="att-values-to-suppress" as="xs:string*"
    select="tokenize($parVAObjectRelationship, ',')"/>

  <data:data>
    <elements:name abbrev="id">id</elements:name>
    <elements:name abbrev="fname">firstname</elements:name>
  </data:data>

  <xsl:key name="e-by-abbrev" match="elements:name" use="@abbrev"/>

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

  <xsl:template match="*[@relationship = $att-values-to-suppress]"/>

  <xsl:template match="*[node-name(.) = $names-to-suppress]"/>

</xsl:stylesheet>

When applied to

<Employees>
  <Employee>
    <id>1</id>
    <firstname>xyz</firstname>
    <lastname relationship="a">abc</lastname>
    <age relationship="b">32</age>
    <department>xyz</department>
  </Employee>
</Employees>

the output is

<Employees>
  <Employee>

    <firstname>xyz</firstname>


    <department>xyz</department>
  </Employee>
</Employees>

You might want to add strip-space and output indent="yes" to prevent the blank lines.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • I want to furthur modify it,lets say if i add another parameter parVAObjectRelationship and i want check if the attribute values matches the passed paramter parVAObjectRelationship then suppress thoes nodes. transform employee.xml suppress.xsl ElementsToSuppress=id parVAObjectRelationship=a,b 1 xyz abc 32 xyz Thanks for your help – atif Dec 10 '10 at 18:41
  • @Martin Honnen: For inline data in XSLT 2.0 I would use the `@as` feature of `xsl:param`. That makes `document('')` no needed for getting a node set, avoiding the problems for null URI (dynamic documents). –  Dec 10 '10 at 19:51
  • Any ideas guys, how can we impelemnt it to remove the node with specific atirbute value? These attribute values should be passed as parameter just like above post. xxxx Thanks – atif Dec 10 '10 at 20:34
  • atif, I added some stylesheet code to show how to use the attribute value. – Martin Honnen Dec 11 '10 at 11:35
  • Hi Martin, When i try to modify your script by doing following changes,I am getting error An error occurred matching pattern {*[@relationship = $relationship-to-suppress]}: Cannot convert string to type {xs:QName} Pls help. – – atif Dec 13 '10 at 19:05
  • atif, the sample I posted has `` which makes sense as you want to compare the attribute value of the `relationship` attribute to a string passed in and not to a QName. I am not sure why you changed that, you will need to clarify first what you want to achieve. – Martin Honnen Dec 14 '10 at 11:35
  • I want to do the same thing aswe did earlier that I passed abbrevations of relationship and then mapped it based to the data element for the actual value.The reason is that our replationship names are not finilized yet and it keep on changing so i thought that I will map it in xls script and then simply pass the abbrevation from our make files. We have serval make files so idea is that I shuold only change it the xsl file without making any changes to my make files. – atif Dec 14 '10 at 15:19
  • I am not sure I understand your latest requirement, you might simply want `` and then ` – Martin Honnen Dec 14 '10 at 18:09
3

If you are using the old Saxon-B or newer Saxon-PE or Saxon-EE as XSLT processor, you can use a saxon extension to achieve dynamic template calls:

<saxon:call-template name="{$templateName}"/>

Don't forget to declare the saxon-Namespace in xsl-stylesheet element:

<xsl:stylesheet xmlns:saxon="http://saxon.sf.net/" [...] >
ToFi
  • 1,167
  • 17
  • 29