XSLT remove data if attribute value is higher than threshold

1.4k views Asked by At

I am newbie to XSLT. My am is to remove elements from XML based on a field if it is higher than some threshold value which I will be getting as parameter to it.

My XML is as shown below:

<tns:PM objectClass="MyNode" objectName="" className="com.project.converter.PMFamilyConverter" interfaceName="ComponentPM"
    xmlns:tns="http://www.myproject/SS/PMSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.myProject.com/SS/PM.xsd">
  <tns:family desc="family0" id="f0" eventNumber="000000">
    <tns:performanceIndicator desc="Description 1" id="Success" eventNumber="000001" />
    <tns:performanceIndicator desc="Description 2" id="Error" eventNumber="000002" />
  </tns:family>
  <tns:family desc="family1" id="f1" eventNumber="010000">
    <tns:performanceIndicator desc="Description 1" id="Success" eventNumber="010001" />
    <tns:performanceIndicator desc="Description 2" id="Name Error" eventNumber="010002" />
    <tns:performanceIndicator desc="Description 3" id="Server Not Found Error" eventNumber="010003" />
  </tns:family>
  <tns:family desc="family2" id="f2" eventNumber="020000">
    <tns:performanceIndicator desc="Description 1" id="Success" eventNumber="020001" />
    <tns:performanceIndicator desc="Description 2" id="Format Error" eventNumber="020002" />
    <tns:performanceIndicator desc="Description 3" id="Logic Error" eventNumber="020003" />
    <tns:performanceIndicator desc="Description 4" id="Success with warning message" eventNumber="020004" />
  </tns:family>
  <tns:family desc="family3" id="f3" eventNumber="030000">
    <tns:performanceIndicator desc="Description 1" id="Success" eventNumber="030001" />
    <tns:performanceIndicator desc="Description 2" id="Error" eventNumber="030002" />
  </tns:family>
</tns:PM>

Based on some samples available online, the XSL I have written is:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" standalone="no" indent="yes"/>
<xsl:param name="maxEventNum"/>
<xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
</xsl:template>
    <xsl:template match="/">
      <xsl:apply-templates select="/*[contains(local-name(),'PM')]/*[contains(local-name(),'family')]/@eventNumber"/>
      <xsl:if test="number(.) < $maxEventNum">
            <xsl:copy-of select="/*[contains(local-name(),'PM')]/*[contains(local-name(),'family')]"/>
      </xsl:if>
    </xsl:template>
</xsl:stylesheet>

The output I want is shown below if I pass maxEventNumber as 020004.

<tns:PM objectClass="MyNode" objectName="" className="com.project.converter.PMFamilyConverter" interfaceName="ComponentPM"
    xmlns:tns="http://www.myproject/SS/PMSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.myProject.com/SS/PM.xsd">
  <tns:family desc="family0" id="f0" eventNumber="000000">
    <tns:performanceIndicator desc="Description 1" id="Success" eventNumber="000001" />
    <tns:performanceIndicator desc="Description 2" id="Error" eventNumber="000002" />
  </tns:family>
  <tns:family desc="family1" id="f1" eventNumber="010000">
    <tns:performanceIndicator desc="Description 1" id="Success" eventNumber="010001" />
    <tns:performanceIndicator desc="Description 2" id="Name Error" eventNumber="010002" />
    <tns:performanceIndicator desc="Description 3" id="Server Not Found Error" eventNumber="010003" />
  </tns:family>
</tns:PM>

Also can you please tell when and how to use apply-template and is it related to match if apply-template is used inside match.

2

There are 2 answers

1
michael.hor257k On BEST ANSWER

First thing: you should use the namespace/s in your input XML, not dance around them with awkward expressions like *[contains(local-name(),'...')].

Now, the simplest way to exclude specific nodes is to start with the identity transform template (as you have) to copy all nodes as the rule, then add an empty template matching the nodes you want to exclude as the exception.

However, this cannot work when the nodes you want to exclude are determined by a parameter, because a template's match pattern cannot contain a reference to a variable. In such case, you can make the content of the template conditional:

XSLT 1.0

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

<xsl:param name="maxEventNum" select="020004"/>

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

<xsl:template match="tns:family">
    <xsl:if test="@eventNumber &lt; $maxEventNum">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

Note: (1) the added xmlns:tns="http://www.myproject/SS/PMSchema" namespace declaration and the use of the tns: prefix, and (2) the escaping of the < operator as &lt;.


An alternative solution would make sure that templates are applied only to tns:family nodes that meet the criteria:

XSLT 1.0

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

<xsl:param name="maxEventNum" select="020004"/>

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

<xsl:template match="/tns:PM">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates select="tns:family[@eventNumber &lt; $maxEventNum]"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>
0
har07 On

Assuming -based on the expected output XML- you want to evaluate eventNumber attribute both in family and it's descendant elements f.e performanceIndicator element, you can try the following XSLT :

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:tns="http://www.myproject/SS/PMSchema">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" standalone="no" indent="yes"/>
<xsl:strip-space elements="*"/>
  <xsl:param name="maxEventNum" select="020004"/>
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>
  <xsl:template match="/tns:PM/tns:family">
    <xsl:if test="count(.//@eventNumber[number(.) &gt;= $maxEventNum]) = 0">
      <xsl:copy>
        <xsl:apply-templates select="@*|node()" />
      </xsl:copy>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>
  • The first template is identity template which match all nodes and attributes and copy them to the output XML as it is in the source XML.
  • The 2nd template overrides the identity template for matched elements -family elements in this case-; more specific template overrides more general ones. This template tests if any eventNumber attribute within current context family has value equals or higher than the threshold (you implicitly stated that eventNumber=020004 should be removed from output when threshold it self is 020004). If there is none of such eventNumber attribute (count(...)=0), copy current family element to the output, else don't copy/do nothing.