4

I'm a newbie to XSLT. I've an XML document and I need to come up with xslt to validate certain rules in the XML document. The XML and xsl file will be used in xsltproc tool and the output will be a simple Pass or Fail.

Sample XML:

...

<Manager mincount="4" grade="10"...>
  <Employee id="1" grade="9" .... />
  <Employee id="2" grade="8" .... />
.....
</Manager>
  1. The number of children under Manager (Employee in this case) must be equal to or greater than the value of mincount attribute.
  2. All the employee's grade must be less than the Manager grade.

Appreciate your help! TIA!

Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
Foo Bar
  • 131
  • 2
  • 8

3 Answers3

5

Here's an XSLT 1.0 option that gives a pass/fail. There is additional detail in the "Fail", but that can be removed. It also outputs the message to both stdout and stderr and terminates processing.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>

  <xsl:template match="/Manager">
    <xsl:if test="not(count(Employee) >= @mincount)">
      <xsl:variable name="vMessage" select="concat('Fail - Number of Employees (',count(Employee),') is not >= to @mincount (',@mincount,').')"/>
      <!--stdout-->
      <xsl:value-of select="$vMessage" disable-output-escaping="yes"/> 
      <!--stderr-->
      <xsl:message terminate="yes"><xsl:value-of select="$vMessage"/></xsl:message>
    </xsl:if>
    <xsl:if test="Employee/@grade >= @grade">
      <xsl:variable name="vMessage" select="concat('Fail - Employee (id ',Employee[@grade >= ancestor::Manager/@grade][1]/@id,') has a grade (',Employee[@grade >= ancestor::Manager/@grade][1]/@grade,') that is higher than the Manager grade (',@grade,').')"/>
      <!--stdout-->
      <xsl:value-of select="$vMessage" disable-output-escaping="yes"/> 
      <!--stderr-->
      <xsl:message terminate="yes"><xsl:value-of select="$vMessage"/></xsl:message>
    </xsl:if>
    <xsl:text>Pass</xsl:text>
  </xsl:template>

</xsl:stylesheet>

Here are a few XML/output examples:

<Manager mincount="1" grade="7" id="28">
  <Employee id="6" grade="5"/>
  <Employee id="7" grade="1"/>
  <Employee id="8" grade="2"/>
  <Employee id="3" grade="7"/>
</Manager>

Fail - Employee (id 3) has a grade (7) that is higher than the Manager grade (7).

<Manager mincount="1" grade="7" id="28">
  <Employee id="6" grade="5"/>
  <Employee id="7" grade="1"/>
  <Employee id="8" grade="2"/>
  <Employee id="3" grade="6"/>
</Manager>

Pass

<Manager mincount="10" grade="7" id="28">
  <Employee id="6" grade="5"/>
  <Employee id="7" grade="1"/>
  <Employee id="8" grade="2"/>
  <Employee id="3" grade="6"/>
</Manager>

Fail - Number of Employees (4) is not >= to @mincount (10).
Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
4

Use an XSD schema instead. It's designed to validate XML.

In particular you might be interested in XSD 1.1 assertions.

See http://www.w3schools.com/schema/ for a good tutorial.

  • +1 XSLT is used for the transformation of XML documents. The tutorial should be good to learn how to validate – Johannes Staehlin Feb 16 '12 at 06:17
  • 3
    Come on guys, also supply the XSD snippet that meets Foo Bar's requirement. – Maestro13 Feb 16 '12 at 06:37
  • 2
    As far as I know, in an XSD specification one can only supply minOccurs attribute values equal to a non negative integer. But here a refer back to an attribute in the parent element would be needed. Is there a newer version of XSD that I don't know of and can do this? If not, then the question is a valid one and appropriate for XSLT. – Maestro13 Feb 16 '12 at 07:14
  • XSD 1.1 has assertions which allow you to validate a complex type using a valid XPath 2.0 expression. –  Feb 16 '12 at 17:49
1

Here's an XSLT that checks mincount values vs actual number of occurrences of Employee. Note that xsl:function is used so this requires XSLT 2.0.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:custom="http://localhost:8080/customFunctions" exclude-result-prefixes="custom">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:function name="custom:checkMgr">
        <xsl:param name="mgr"/>
        <xsl:choose>
            <xsl:when test="$mgr/@mincount &gt; count($mgr/Employee)">false</xsl:when>
            <xsl:when test="count($mgr/Employee[number(@grade) &gt;= number($mgr/@grade)]) &gt; 0">false</xsl:when>
           <xsl:otherwise>true</xsl:otherwise>
        </xsl:choose>
    </xsl:function>

    <xsl:template match="/">
        <root>
            <xsl:apply-templates select="root/Manager"/>
        </root>
    </xsl:template>

    <xsl:template match="Manager">
        <mgrCheck>
            <xsl:attribute name="id" select="@id"/>
            <xsl:attribute name="mincount" select="@mincount"/>
            <xsl:attribute name="actual" select="count(Employee)"/>
            <xsl:attribute name="grade" select="@grade"/>
            <xsl:attribute name="numEmpNoLessGrade" select="count(Employee[number(@grade) >= number(../@grade)])"/>
            <xsl:attribute name="OK" select="custom:checkMgr (.)"/>
        </mgrCheck>
    </xsl:template>
</xsl:stylesheet>

When applied on the following input:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <Manager mincount="4" grade="10" id="26">
        <Employee id="1" grade="9"/>
        <Employee id="2" grade="8"/>
    </Manager>
    <Manager mincount="1" grade="10" id="27">
        <Employee id="3" grade="9"/>
        <Employee id="4" grade="8"/>
        <Employee id="5" grade="4"/>
    </Manager>
    <Manager mincount="1" grade="7" id="28">
        <Employee id="6" grade="8"/>
        <Employee id="7" grade="7"/>
        <Employee id="8" grade="6"/>
        <Employee id="9" grade="9"/>
    </Manager>
    <Manager mincount="3" grade="9" id="29">
        <Employee id="10" grade="9"/>
        <Employee id="11" grade="8"/>
        <Employee id="12" grade="7"/>
    </Manager>
</root>

the result is:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <mgrCheck id="26" mincount="4" actual="2" grade="10" numEmpNoLessGrade="0" OK="false"/>
    <mgrCheck id="27" mincount="1" actual="3" grade="10" numEmpNoLessGrade="0" OK="true"/>
    <mgrCheck id="28" mincount="1" actual="4" grade="7" numEmpNoLessGrade="3" OK="false"/>
    <mgrCheck id="29" mincount="3" actual="3" grade="9" numEmpNoLessGrade="1" OK="false"/>
</root>

An alternative in XSLT 1.0 could be the following:

<?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" indent="yes"/>

    <xsl:template match="/">
        <root>
            <xsl:apply-templates select="root/Manager"/>
        </root>
    </xsl:template>

    <xsl:template match="Manager">
        <mgrCheck>
            <id><xsl:value-of select="@id"/></id>
            <mincount><xsl:value-of select="@mincount"/></mincount>
            <actual><xsl:value-of select="count(Employee)"/></actual>
            <grade><xsl:value-of select="@grade"/></grade>
            <numEmpNoLessGrade><xsl:value-of select="count(Employee[@grade &gt;= ../@grade])"/></numEmpNoLessGrade>
            <OK>
                <xsl:choose>
                    <xsl:when test="@mincount &gt; count(Employee)">false</xsl:when>
                    <xsl:when test="count(Employee[@grade &gt;= ../@grade]) &gt; 0">false</xsl:when>
                    <xsl:otherwise>true</xsl:otherwise>
                </xsl:choose>
            </OK>
        </mgrCheck>
    </xsl:template>
</xsl:stylesheet>

with result

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <mgrCheck>
        <id>26</id>
        <mincount>4</mincount>
        <actual>2</actual>
        <grade>10</grade>
        <numEmpNoLessGrade>0</numEmpNoLessGrade>
        <OK>false</OK>
    </mgrCheck>
    <mgrCheck>
        <id>27</id>
        <mincount>1</mincount>
        <actual>3</actual>
        <grade>10</grade>
        <numEmpNoLessGrade>0</numEmpNoLessGrade>
        <OK>true</OK>
    </mgrCheck>
    <mgrCheck>
        <id>28</id>
        <mincount>1</mincount>
        <actual>4</actual>
        <grade>7</grade>
        <numEmpNoLessGrade>3</numEmpNoLessGrade>
        <OK>false</OK>
    </mgrCheck>
    <mgrCheck>
        <id>29</id>
        <mincount>3</mincount>
        <actual>3</actual>
        <grade>9</grade>
        <numEmpNoLessGrade>1</numEmpNoLessGrade>
        <OK>false</OK>
    </mgrCheck>
</root>
Maestro13
  • 3,656
  • 8
  • 42
  • 72
  • 1
    In your first example you could change your function to a named template and it would then be XSL 1.0. You'd also have to take the "select" attributes out of your `xsl:attribute` elements though. This would be taken care of by using AVT's (http://www.w3.org/TR/xslt#attribute-value-templates). – Daniel Haley Feb 16 '12 at 16:23
  • Thanks a lot folks. Much appreciated! I posted another similar question with some variation of the input xml document, http://stackoverflow.com/questions/9505086/xslt-transformation-to-validate-rules-in-xml-document. – Foo Bar Feb 29 '12 at 19:27