XSL / Schemas初学者提问

时间:2010-08-26 22:14:17

标签: c# xml xslt xsd

多年来一直在使用XML进行数据存储&转移,但从未必须验证或转换它。目前正在开始一个新项目并做出一些设计决策,需要了解一些关于XSL和XSL的基本知识。模式。

我们的XML就是这样(借口无聊的书:) :):

<Books>
  <Book>
    <ID>1</ID>
    <Name>Book1</Name>
    <Price>24.??</Price>
    <Country>US</Country>
  </Book>
  <Book>
    <ID>1</ID>
    <Name></Name>
    <Price>24.69</Price>
  </Book>
</Books>

我们的要求:

  1. 转化

    a)将“美国”变成美国 b)如果价格&gt; 20创建一个新的lLement <Expensive>True</Expensive>

    我猜这是用XSLT完成的,但有人能给我一些关于如何实现这个目标的指示吗?

  2. 验证

    a)是ID一个整数,是Price一个浮点数(说实话最重要的工作)
    b)是否填充了所有标签,例如名称标签未填写(第二重要)
    c)是否存在所有标签,例如第2册中缺少国家 d)[可能很棘手] ID元素在所有书籍中都是独一无二的吗? (很高兴)

  3. 根据我的阅读,这是使用Schema或Relax NG完成的,但验证结果是否可以输出为简单的HTML来显示列表或错误?

    e.g。
    第1册:价格“价格。??”不是浮动的 第2册:ID不唯一,名称为空,国家缺失

    或者在C#中以编程方式执行这些操作会更好吗? 谢谢。

3 个答案:

答案 0 :(得分:3)

此样式表:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="map"
exclude-result-prefixes="m">
    <xsl:key name="kTestIntID" match="Book"
             use="number(ID)=number(ID) and not(contains(ID,'.'))"
             m:message="Books with no integer ID"/>
    <xsl:key name="kTestFloatPrice" match="Book"
             use="number(Price)=number(Price) and contains(Price,'.')"
             m:message="Books with no float Price"/>
    <xsl:key name="kTestEmptyElement" match="Book"
             use="not(*[not(node())])"
             m:message="Books with empty element"/>
    <xsl:key name="kTestAllElements" match="Book"
             use="ID and Name and Price and Country"
             m:message="Books with missing element"/>
    <xsl:key name="kBookByID" match="Book" use="ID"/>
    <m:map from="US" to="United States"/>
    <m:map from="CA" to="Canada"/>
    <xsl:variable name="vCountry" select="document('')/*/m:map"/>
    <xsl:variable name="vKeys" select="document('')/*/xsl:key/@name
                                           [starts-with(.,'kTest')]"/>
    <xsl:variable name="vTestNotUniqueID"
                  select="*/*[key('kBookByID',ID)[2]]"/>
    <xsl:template match="/" name="validation">
        <xsl:param name="pKeys" select="$vKeys"/>
        <xsl:param name="pTest" select="$vTestNotUniqueID"/>
        <xsl:param name="pFirst" select="true()"/>
        <xsl:choose>
            <xsl:when test="$pTest and $pFirst">
                <html>
                    <body>
                        <xsl:if test="$vTestNotUniqueID">
                            <h2>Books with no unique ID</h2>
                            <ul>
                                <xsl:apply-templates
                                 select="$vTestNotUniqueID"
                                 mode="escape"/>
                            </ul>
                        </xsl:if>
                        <xsl:variable name="vCurrent" select="."/>
                        <xsl:for-each select="$vKeys">
                            <xsl:variable name="vKey" select="."/>
                            <xsl:for-each select="$vCurrent">
                                <xsl:if test="key($vKey,'false')">
                                    <h2>
                                        <xsl:value-of
                                         select="$vKey/../@m:message"/>
                                    </h2>
                                    <ul>
                                        <xsl:apply-templates
                                         select="key($vKey,'false')"
                                         mode="escape"/>
                                    </ul>
                                </xsl:if>
                            </xsl:for-each>
                        </xsl:for-each>
                    </body>
                </html>
            </xsl:when>
            <xsl:when test="$pKeys">
                <xsl:call-template name="validation">
                    <xsl:with-param name="pKeys"
                     select="$pKeys[position()!=1]"/>
                    <xsl:with-param name="pTest"
                     select="key($pKeys[1],'false')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="Book" mode="escape">
        <li>
            <xsl:call-template name="escape"/>
        </li>
    </xsl:template>
    <xsl:template match="*" name="escape" mode="escape">
        <xsl:value-of select="concat('&lt;',name(),'&gt;')"/>
        <xsl:apply-templates mode="escape"/>
        <xsl:value-of select="concat('&lt;/',name(),'&gt;')"/>
    </xsl:template>
    <xsl:template match="text()" mode="escape">
        <xsl:value-of select="normalize-space()"/>
    </xsl:template>

    <!-- Up to here, rules for validation.
         From here, rules for transformation -->

    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="Country/text()">
        <xsl:variable name="vMatch"
                      select="$vCountry[@from=current()]"/>
        <xsl:choose>
            <xsl:when test="$vMatch">
                <xsl:value-of select="$vMatch/@to"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="."/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="Price[. > 20]">
        <xsl:call-template name="identity"/>
        <Expensive>True</Expensive>
    </xsl:template>
</xsl:stylesheet>

输入,输出:

<html>
<body>
<h2>Books with no unique ID</h2>
<ul>
<li>&lt;Book&gt;&lt;ID&gt;1&lt;/ID&gt;&lt;Name&gt;Book1&lt;/Name&gt;&lt;Price&gt;24.??&lt;/Price&gt;&lt;Country&gt;US&lt;/Country&gt;&lt;/Book&gt;</li>
<li>&lt;Book&gt;&lt;ID&gt;1&lt;/ID&gt;&lt;Name&gt;&lt;/Name&gt;&lt;Price&gt;24.69&lt;/Price&gt;&lt;/Book&gt;</li>
</ul>
<h2>Books with no float Price</h2>
<ul>
<li>&lt;Book&gt;&lt;ID&gt;1&lt;/ID&gt;&lt;Name&gt;Book1&lt;/Name&gt;&lt;Price&gt;24.??&lt;/Price&gt;&lt;Country&gt;US&lt;/Country&gt;&lt;/Book&gt;</li>
</ul>
<h2>Books with empty element</h2>
<ul>
<li>&lt;Book&gt;&lt;ID&gt;1&lt;/ID&gt;&lt;Name&gt;&lt;/Name&gt;&lt;Price&gt;24.69&lt;/Price&gt;&lt;/Book&gt;</li>
</ul>
<h2>Books with missing element</h2>
<ul>
<li>&lt;Book&gt;&lt;ID&gt;1&lt;/ID&gt;&lt;Name&gt;&lt;/Name&gt;&lt;Price&gt;24.69&lt;/Price&gt;&lt;/Book&gt;</li>
</ul>
</body>
</html>

正确输入:

<Books>
    <Book>
        <ID>1</ID>
        <Name>Book1</Name>
        <Price>19.50</Price>
        <Country>US</Country>
    </Book>
    <Book>
        <ID>2</ID>
        <Name>Book2</Name>
        <Price>24.69</Price>
        <Country>CA</Country>
    </Book>
</Books>

输出:

<Books>
    <Book>
        <ID>1</ID>
        <Name>Book1</Name>
        <Price>19.50</Price>
        <Country>United States</Country>
    </Book>
    <Book>
        <ID>2</ID>
        <Name>Book2</Name>
        <Price>24.69</Price>
        <Expensive>True</Expensive>
        <Country>Canada</Country>
    </Book>
</Books>

注意:使用键来提高性能。这是概念的证明。在现实生活中,XHTML输出应该包含在xsl:message指令中。来自http://www.w3.org/TR/xslt#message

  

xsl:message指令发送一个   消息的依赖方式   XSLT处理器。的内容   xsl:message指令是一个模板。   xsl:消息由实例化   实例化内容以创建   XML片段。这个XML片段是   消息的内容。

     

注意:XSLT处理器可能会实现   xsl:弹出警告框的消息   或者通过写入日志文件。

     

如果terminate属性有   值是,然后是XSLT处理器   应该在之后终止处理   发送消息。默认值   不是。

修改:压缩代码并解决国家/地区地图问题。

编辑2 :在现实生活中,使用大型XML文档和更多的enterprice工具,最好的方法是使用XSLT 2.0架构感知处理器运行转换以进行验证,或独立运行验证众所周知的模式验证器。如果由于某种原因这些选择不可用,请不要使用我的概念验证答案,因为为每个验证规则设置密钥会导致大量文档的大量内存使用。对于最后一种情况,更好的方法是添加规则以捕获结束带有消息的转换的验证错误。例如,这个样式表:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="map"
exclude-result-prefixes="m">
    <xsl:key name="kIDByValue" match="ID" use="."/>
    <m:map from="US" to="United States"/>
    <m:map from="CA" to="Canada"/>
    <xsl:variable name="vCountry" select="document('')/*/m:map"/>
    <xsl:template name="location">
        <xsl:param name="pSteps" select="ancestor-or-self::*"/>
        <xsl:if test="$pSteps">
            <xsl:call-template name="location">
                <xsl:with-param name="pSteps"
                                select="$pSteps[position()!=last()]"/>
            </xsl:call-template>
            <xsl:value-of select="concat('/',
                                         name($pSteps[last()]),
                                         '[',
                                         count($pSteps[last()]/
                                               preceding-sibling::*
                                               [name()=
                                                name($pSteps[last()])])
                                         +1,
                                         ']')"/>
        </xsl:if>
    </xsl:template>
    <xsl:template match="ID[not(number()=number() and not(contains(.,'.')))]">
        <xsl:message terminate="yes">
            <xsl:text>No integer ID at </xsl:text>
            <xsl:call-template name="location"/>
        </xsl:message>
    </xsl:template>
    <xsl:template match="Price[not(number()=number() and contains(.,'.'))]">
        <xsl:message terminate="yes">
            <xsl:text>No float Price at </xsl:text>
            <xsl:call-template name="location"/>
        </xsl:message>
    </xsl:template>
    <xsl:template match="Book/*[not(node())]">
        <xsl:message terminate="yes">
            <xsl:text>Empty element at </xsl:text>
            <xsl:call-template name="location"/>
        </xsl:message>
    </xsl:template>
    <xsl:template match="Book[not(ID and Name and Price and Country)]">
        <xsl:message terminate="yes">
            <xsl:text>Missing element at </xsl:text>
            <xsl:call-template name="location"/>
        </xsl:message>
    </xsl:template>
    <xsl:template match="ID[key('kIDByValue',.)[2]]">
        <xsl:message terminate="yes">
            <xsl:text>Duplicate ID at </xsl:text>
            <xsl:call-template name="location"/>
        </xsl:message>
    </xsl:template>
    <!-- Up to here, rules for validation.
         From here, rules for transformation -->
    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="Country/text()">
        <xsl:variable name="vMatch"
                      select="$vCountry[@from=current()]"/>
        <xsl:choose>
            <xsl:when test="$vMatch">
                <xsl:value-of select="$vMatch/@to"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="."/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="Price[. > 20]">
        <xsl:call-template name="identity"/>
        <Expensive>True</Expensive>
    </xsl:template>
</xsl:stylesheet>

根据您的输入,此消息将停止转换:

Duplicate ID ar /Books[1]/Book[1]/ID[1]

输入正确后,输出与之前相同。

答案 1 :(得分:0)

以下是RelaxNG schema

<grammar xmlns="http://relaxng.org/ns/structure/1.0"
         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">

  <start>
    <element name="Books">
      <zeroOrMore>
        <element name="Book">
          <element name="ID"><data type="ID"/></element>
          <element name="Name"><text/></element>
          <element name="Price"><data type="decimal"/></element>
          <element name="Country"><data type="NMTOKEN"/></element>
        </element>
      </zeroOrMore>
    </element>
  </start>

</grammar>

这是XML Schema版本。 (我想。)

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="Books">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="Book"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="Book">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="ID"/>
        <xs:element ref="Name"/>
        <xs:element ref="Price"/>
        <xs:element ref="Country"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="ID" type="xs:ID"/>
  <xs:element name="Name" type="xs:string"/>
  <xs:element name="Price" type="xs:decimal"/>
  <xs:element name="Country" type="xs:NMTOKEN"/>
</xs:schema>

这里需要注意的事情:

  • ID简单类型将使验证程序检查标识符的多次出现,并在有任何错误时进行投诉。然而,使用ID标签的缺点是它们不能以数字开头。所以,A1,A2,...... An会很好,但是像1,2,......,n这样的ID无论如何都会被视为无效。
  • 价格已设为十进制类型。由于四舍五入错误,Float永远不是财务数字的合适类型。

使用原始XML文档作为输入(带有修改的标识符)通过xmllint运行此命令:

wilfred$ xmllint --noout --relaxng ./books.rng ./books.xml
./books.xml:5: element Price: Relax-NG validity error : Type decimal doesn't allow value '24.??'
./books.xml:5: element Price: Relax-NG validity error : Error validating datatype decimal
./books.xml:5: element Price: Relax-NG validity error : Element Price failed to validate content
./books.xml:8: element Book: Relax-NG validity error : Expecting an element , got nothing
./books.xml fails to validate

答案 2 :(得分:0)

在一般的XSL教育中,你可能会发现我在几年前写的有用的XSL Primer。它不是所有最新趋势的最新信息,但涵盖了如何处理XML文档的基础知识。