Newbie: XSLT Transformation to validate rules in X

2019-03-31 02:19发布

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!

3条回答
在下西门庆
2楼-- · 2019-03-31 02:40

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.

查看更多
迷人小祖宗
3楼-- · 2019-03-31 02:48

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).
查看更多
Root(大扎)
4楼-- · 2019-03-31 02:54

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>
查看更多
登录 后发表回答