sld编写样式_编写更安全的XSLT样式表

sld编写样式

如果您编写了任何XSLT样式表,则可能知道在现实环境中使其安全并不总是那么容易。 例如,微小的输入错误可能会引起严重的头痛。 XPath表达式的元素和属性名称中的简单错误不会被XSLT处理器的错误检查机制检测到。 一些简单的文字/Filename ,它应该是/FileName显示仅当您自己的测试和调试的努力找到它:它不会被XSLT引擎捕捉。

这是一个例子。 假设您有一个类似于清单1的XML文档。

清单1.示例XML文档
<Things>
    <Thing thingid="12345FFD3">...</Thing>
    <Thing thingid="86779EAD0">...</Thing>
    ...
</Things>

在XSLT样式表中,您有一个命名模板,可以对某事进行处理( 清单2 )。

清单2.用于处理事物的命名模板
<xsl:template name="ProcessThing">
    <xsl:param name="ThingId"/>
    <xsl:for-each select="/Things/Thing[@id eq $ThingId]">
        <!-- Do something with the thing -->
    </xsl:for-each>
</xsl:template>

你能发现错误吗? 该模板的作者忘记了(或不知道)事物上的标识符属性是拼写的thingid而不是id 。 如果您运行此标记,则不会出现错误消息: for-each循环根本不会执行。 也许您注意到了,也许您没有。 毕竟,错误被嵌入到复杂的转换中。 因此,您不会注意到,您的代码已投入生产,并且拼写错误阻止了某些计算的重要部分发生。

还有另一个相关的问题。 尽管有相反的意图,但并非所有输入文档都将根据模式或DTD进行验证。 如果文档的作者写了<Filename>而不是预期的<FileName>怎么办?

自动捕获这些和其他错误(至少是最明显的错误),并让XSLT处理器在发生不良情况时通知您是一个轻松的想法。 本文探讨如何使您的XSLT样式表更安全,并使XSLT处理器捕获通常通常会忽略的错误。 它从软件工程的角度查看样式表,并说明如何使样式表更健壮和安全。 本文假定您至少具有XSLT的使用知识。

XSLT v2类型系统

要了解本文描述的大多数方法,您必须了解XSLT v2类型系统。 (这只是一个粗略的介绍:有关更多信息,请参阅参考资料 。)XSLT v2引入了许多新功能,包括数据类型。 高级选项包括引用架构和使用在那里定义的类型。 但是,描述的方法仅使用基本类型系统。

XSLT v2的一项新功能是能够为变量和参数提供显式数据类型。 为此,使用了XML模式的类型系统,并增加了一些额外的语法和语义。 您可以使用as属性提供数据类型,例如:

<xsl:variable name="TestVariable" as="xs:integer" select="123"/>

其他基本数据类型包括xs:stringxs:booleanxs:doublexs:datexs:dateTime 。 并记住在某处(最好在样式表的根元素上)定义xs名称空间前缀:

xmlns:xs="http://www.w3.org/2001/XMLSchema"

在XSLT v2中,还可以将树片段(文档,节点,属性等)存储在变量中。 使用一些额外的语法,您可以为此输入数据变量。 清单3提供了一些示例。

清单3.包含树片段的变量的示例
<xsl:variable name="TheCompleteDocument" as="document-node()" select="/"/>
<xsl:variable name="AnyElement" as="element()" select="*[1]"/>
<xsl:variable name="FirstThingElement" as="element(Thing)" select="/Things/Thing[1]"/>
<xsl:variable name="FirstAttribute" as="attribute()" select="@*[1]"/>
<xsl:variable name="IdAttribute" as="attribute()" select="@id"/>

这很好,但效果更好:XSLT v2中的所有变量都是序列(即值的有序集),可以具有零个,一个或多个值。 因此,“普通”变量(如以上示例中所示)只是一种特殊情况-序列中只有一个值。 要使用序列的幂,请在类型说明的末尾添加一个加号( + )表示一个或多个值,星号( * )表示零个或多个值,或问号( ? )表示零个或一个值。 。 是的,就像旧的DTD一样。 清单4提供了一些示例。

清单4.序列示例
<xsl:variable name="MultipleStrings" as="xs:string+" select="('This', 'That')"/>
<xsl:variable name="EmptySetOfIntegers" as="xs:integer?" select="()"/>
<xsl:variable name="SetOfAllThings" as="element(Thing)*" select="//Thing"/>

这样做有很多功能:您可以在变量上使用所有常用的XPath构造。 清单5提供了一个示例。

清单5.使用XSLT v2变量的XPath构造
<xsl:for-each select="$SetOfAllThings">
    <!-- Do something with the things -->
</xsl:for-each>
<xsl:variable name="LastString" as="xs:string" select="$MultipleStrings[last()]"/>
<xsl:variable name="ThingCount" as="xs:integer" select="count($SetOfAllThings)"/>
<xsl:variable name="AllThingIdAttributes" as="attribute(thingid)*" 
              select="$SetOfAllThings/@thingid"/>

那么,这如何帮助您使样式表更健壮? 类型系统的好处是,在运行时,XSLT处理器实际上根据变量的类型检查变量的值,并抱怨它是否不合适,包括其多重性。 因此,如果将输入文档中的重要值放入具有适当类型的变量中,则在出现问题时会出错。

陷入类型错误和意外的元素和属性

您可以采取什么措施来防止XPath元素和属性名称(在样式表和输入文档中)中的错字? 仔细检查,重新阅读,进行艰苦的调试,保持双手交叉,祈祷……当然,可以全部完成。 但是,您真正想要的是将错误显示在错误消息中的错误。

使用变量

如果您如清单6所示重写清单1中的示例,则会立即弹出错误。

清单6.清单1中带有变量的XML示例
<xsl:template name="ProcessThing">
    <xsl:param name="ThingId"/>
    <xsl:variable name="ThingToProcess" as="element(Thing)"
        select="/Things/Thing[@id eq $ThingId]"/>
    <xsl:for-each select="$ThingToProcess">
        <!-- Do something with the thing -->
    </xsl:for-each>
</xsl:template>

在运行时,XSLT处理器注意到/Things/Thing[@id eq $ThingId]不会返回期望的<Thing>元素,而是返回一个空序列。 不过, ThingsToProcess变量的类型定义是element(Thing) ,这意味着一个Thing元素。 该元素不合适,因此弹出错误消息,并且转换过程停止。

这里甚至还有奖金。 如果您的输入文档包含一个错误,并且有两个具有相同ID的事物,那么您也会收到一个错误。 在$ThingToProcess变量只能包含一个单一的东西。

为了使该模板更加安全,我按如下所示输入参数:

<xsl:param name="ThingId" as="xs:string" required="yes"/>

这样做会导致忘记参数(由于required="yes"元素)并传递空值或多个值。

您可以通过多种方式使用此技术。 例如,我经常将重要的全局值放在根元素的属性中。 为了使这些值在我的代码中全局可用,我在使用它们之前将它们存储在顶级变量中:

<xsl:variable name="GlobalId" as="xs:string" select="/*/@id"/>
<xsl:variable name="GlobalName" as="xs:string" select="/*/@name"/>

为变量提供数据类型,即使具有xs:string通用名称,也可以捕获该属性的意外缺失-这种东西比我想承认的更能节省我的时间。 因此,首先将输入文档中的重要值放入变量中。 为这些变量提供正确的类型和正确的多重性的数据类型。

包罗万象

如果您的样式表主要由匹配模板组成,则即使您似乎不需要它,也可以考虑添加一个包罗万象的模板。

假设存在以下情况:您为输入中的所有元素编写了匹配模板,并使用<xsl:apply-templates>传播控件。 当您输错名称或有人更改您的输入文档时会发生什么? 元素的默认模板会启动并执行静默<xsl:apply-templates> 。 也许这就是您想要的,也许不是。 最好得到一个错误,以便您可以调查发生了什么。 一个简单的通用模板(如清单7中的模板)就可以实现此目的。

清单7.简单的通用模板
<xsl:template match="*">
    <xsl:message terminate="yes">
        Unexpected element: <xsl:value-of select="name()"/>
    </xsl:message>
</xsl:template>

更安全的命名模板

问题的另一个来源是模板及其参数。 为数据类型参数提供as属性已经捕获了许多错误。 还有更多。

避免参数错误

在XSLT v2中,您不能将参数传递给未在其参数列表中定义的命名模板。 此限制实际上是XSLT v1的少数不兼容之一,但它是一个很好的限制,因为它停止了在参数名称中键入错误。

您可能不知道使命名模板更加安全的提示是XSLT v2的内置功能:您可以根据需要标记参数:

<xsl:template name="DoSomething">
    <xsl:param name="Subject" as="xs:string" required="yes"/>
    <!-- ... -->
</xsl:template>

现在,在调用DoSomething时必须提供Subject参数。 此技巧对于长参数列表特别有用,因为很容易忘记参数。

因此,在XSLT v2中,鉴于先前定义的DoSomething命名模板,以下两个调用都是非法的,并且将被捕获:

<xsl:call-template name="DoSomething">
    <xsl:with-param name="subject" select="'Safer stylesheets'"/>
</xsl:call-template>
<xsl:call-template name="DoSomething"/>

检查上下文

命名模板对于将代码分成较小的块并避免代码重复特别有用。 例如,如果您的代码中有多个位置处理Thing元素(以相同的方式),那么您可能想要编写一个命名模板,如清单8所示

清单8.处理事物作为上下文元素
<xsl:template name="HandleThing">
    <!-- Current element must be a <Thing>! -->
    <!-- ... -->
</xsl:template>

同样,如果当前元素不是Thing ,可能会引发错误。 您可以按照清单9所示执行此步骤。

清单9.处理事物并检查上下文
<xsl:template name="HandleThing">
    <xsl:param name="ThingToHandle" as="element(Thing)" select="."/>
    <xsl:for-each select="$ThingToHandle">
        <!-- Now the current element is a <Thing> or we get an error! -->
        <!-- ... -->
    </xsl:for-each>
</xsl:template>

您可以将ThingToHandle声明为变量而不是参数。 但是,使用参数可以为您带来好处:现在,当当前元素不是Thing ,您现在也可以使用HandleThing 。 只需在ThingToHandle参数( 清单10ThingToHandle它应该使用的元素传递给它ThingToHandle

清单10.处理不是当前上下文的事物
<xsl:template match="/">
    <!-- Only handle the first thing: -->
    <xsl:call-template name="HandleThing"> 
        <xsl:with-param name="ThingToHandle" select="/*/Thing[1]"/>
    </xsl:call-template>
</xsl:template>

技巧和窍门

这是创建更安全的XSLT样式表的两个最终提示:断言和normalize-space()函数。

执行断言

大多数编程语言都有一个称为断言的东西-满足特定条件(例如重要变量中的意外值)时停止处理的语句。 XSLT缺少断言,但是有两种方法可以阻止XSLT处理器继续运行: <xsl:message terminate="yes">和XPath error()函数。 将其中之一与<xsl:if> ,您便创建了一个出色的断言。 例如:

<xsl:if test="empty(/*/Thing)">
    <xsl:message terminate="yes">No things found in input document</xsl:message>
</xsl:if>

或者,使用error()函数:

<xsl:if test="empty(/*/Thing)">
    <xsl:value-of select="error((), 'No things found in input document')"/>
</xsl:if>

在决定使用哪一个之前,请测试它们在IDE和运行时环境中的行为。 在我的系统设置中, error()函数提供了最有用的结果:错误消息。 <xsl:message>告诉我在特定的行上发生了错误,但没有显示错误消息本身。

normalize-space()反对漂亮的印刷

使样式表更安全的最后一个技巧是大量使用normalize-space()函数。 为什么? 因为XML编辑器的漂亮打印功能会引入不需要的换行符和(更重要的是)空白。 例如,假定XML输入文档中某处是这样的元素,它是深层嵌套的:

<DeeplyNestedElement>This is an example of a pretty print error</DeeplyNestedElement>

现在,一个无知的作者单击了漂亮打印按钮,您的元素突然看起来像这样:

<DeeplyNestedElement>This is an example of a pretty
     print error</DeeplyNestedElement>

现在,在漂亮和打印之间有一个换行符和一堆空格。 如果您生成HTML没问题,但是如果您的代码依赖于元素的确切内容,那么这种方法根本就不是很漂亮。 使用normalize-space()设置此权利:它删除开头和结尾的空白并将所有其他空白序列转换为单个空格。

您必须小心: normalize-space()也可以删除必要的空格和换行符。 但是,至少在我的世界中,依赖于文本中确切空白的输入很少。

结论

这个世界并不完美,尤其是在编程时,您并不期望如此。 本文展示了许多方法来捕获或防止XSLT处理中的错误,否则这些错误可能不会被注意到。 特别是类型系统为您提供了可能是意料之外但非常有用的错误捕获可能性。


翻译自: https://www.ibm.com/developerworks/xml/library/x-safexslt/index.html

sld编写样式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值