多年来一直在使用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>
我们的要求:
转化
a)将“美国”变成美国
b)如果价格&gt; 20创建一个新的lLement <Expensive>True</Expensive>
我猜这是用XSLT完成的,但有人能给我一些关于如何实现这个目标的指示吗?
验证
a)是ID一个整数,是Price一个浮点数(说实话最重要的工作)
b)是否填充了所有标签,例如名称标签未填写(第二重要)
c)是否存在所有标签,例如第2册中缺少国家
d)[可能很棘手] ID元素在所有书籍中都是独一无二的吗? (很高兴)
根据我的阅读,这是使用Schema或Relax NG完成的,但验证结果是否可以输出为简单的HTML来显示列表或错误?
e.g。
第1册:价格“价格。??”不是浮动的
第2册:ID不唯一,名称为空,国家缺失
或者在C#中以编程方式执行这些操作会更好吗? 谢谢。
答案 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('<',name(),'>')"/>
<xsl:apply-templates mode="escape"/>
<xsl:value-of select="concat('</',name(),'>')"/>
</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><Book><ID>1</ID><Name>Book1</Name><Price>24.??</Price><Country>US</Country></Book></li>
<li><Book><ID>1</ID><Name></Name><Price>24.69</Price></Book></li>
</ul>
<h2>Books with no float Price</h2>
<ul>
<li><Book><ID>1</ID><Name>Book1</Name><Price>24.??</Price><Country>US</Country></Book></li>
</ul>
<h2>Books with empty element</h2>
<ul>
<li><Book><ID>1</ID><Name></Name><Price>24.69</Price></Book></li>
</ul>
<h2>Books with missing element</h2>
<ul>
<li><Book><ID>1</ID><Name></Name><Price>24.69</Price></Book></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>
这里需要注意的事情:
使用原始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文档的基础知识。