将一个模板用于属性(按值),将另一个模板用于(父)节点

时间:2014-08-18 14:55:46

标签: xml xslt xpath xslt-2.0 tei

我在一个大的(TEI风格的)XML文件(~6000行)的大型(420行)XSL转换上得到这个模糊的匹配警告(在OS X上使用Saxon-HE 9.5.1.6J)。我想了解(并修复)警告。

 Recoverable error 
  XTRE0540: Ambiguous rule match for /TEI/text[1]/group[1]/text[1]/body[1]/lg[33]/head[2]
 Matches both "tei:lg[@type='poem']/tei:head" on line 103 of
  file: hs2latex.xsl
 and "*[@rend='italics']" on line 110 of
  file: hs2latex.xsl

XML看起来像:

<lg type='poem'>
<head rend='italics'>Sonnet 3<head>
...
</lg>

冲突的XSL规则看起来像这样:

<xsl:template match="tei:lg[@type='poem']/tei:head">
...
<xsl:apply-templates />
</xsl:template>

<xsl:template match="*[@rend='italics']"><!-- blah blah --></xsl:template>

由于属性只是另一个节点,我认为我可以单独匹配它。但是如果我在匹配中只是一个属性,我会收到一个错误,所以我将用过的星号与所有节点匹配,并使用rend =&#39; italics&#39;属性,然后产生上面引用的模糊错误。

是否有可能做我在这里尝试的事情,即使用一个模板根据值匹配属性(无论元素的类型如何)?我有兴趣让一个模板处理任何元素,例如,&#34; @rend =&#39; italics&#39;&#34;属性。

我尝试用最小的工作示例来重现这个问题,但是提出了一个稍微不同的例子(这可能与我误解的原因完全相同)。

最少工作XML

<?xml version="1.0" encoding="utf-8"?>

<document>
  <book>
    <title>"One Title"</title>
  </book>

  <book>
    <title rend="italics">Another Title</title>
  </book>
</document>

最小XSLT

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="2.0" 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        exclude-result-prefixes="xsl">
  <xsl:output omit-xml-declaration="yes" />
  <xsl:template match="/">
    <xsl:apply-templates />
  </xsl:template>

  <xsl:template match="*[@rend='italics']">
    <italics><xsl:apply-templates /></italics>
  </xsl:template>

  <xsl:template match="title">
    <title><xsl:apply-templates /></title>
  </xsl:template>

</xsl:stylesheet>

这个最小的例子(我认为产生与我上面描述的相同的情况)不会产生模糊的匹配错误,而是产生这个输出:

<title>"One Title"</title>
<italics>Another Title</italics>

我想要的(在这个最小的例子中)将是:

<title>"One Title"</title>
<title><italics>Another Title</italics></title>

我怀疑我误解了一些关于XSLT或XPath的基本信息,但我现在处于亏损状态并希望得到任何指导。非常感谢。

2 个答案:

答案 0 :(得分:3)

  

是否有可能做我在这里尝试的事情,即使用一个模板根据值匹配属性(无论元素的类型如何)?我有兴趣让一个模板处理任何元素,例如,&#34; @rend =&#39; italics&#39;&#34;属性。

是的,这是可能的。您的代码中的问题是您有两个匹配的NodeTest[predicate]形式的模板,默认情况下它们都具有相同的优先级。如果您希望一个优先于另一个,则应添加priority="X",其中X是任意数字。即:

<xsl:template match="*[@rend='italics']" priority="2">
  <italics><xsl:apply-templates /></italics>
</xsl:template>
  

这个最小的例子(我认为它产生了与我上面描述的相同的情况)不会产生模糊的匹配错误,而是产生这个输出:

正确。那是因为在XSLT中default values are assigned to the priorities based roughly on the complexity of the match pattern。只有NodeTest的优先级低于NodeTest[predicate]

  

由于属性只是另一个节点,我认为我可以单独匹配它。

是的,你可以。您没有通过属性匹配显示您尝试过的内容,但它应如下所示:match="@rend"match="@rend[. = 'italics']"。但是,请注意属性是特殊节点。您需要专门将模板应用于属性才能匹配它们。此外,具有焦点的节点将是属性节点本身,因此您可能必须走父轴以获得与当前相同的结果。

  

我想要的(在这个最小的例子中)将是:

您似乎想要的是,当更通用的匹配匹配时,您希望特定匹配也应用于同一节点。任何节点最多匹配一个匹配模板。要让一个节点与多个模板匹配,可以使用xsl:next-match指令。但是,这可以从特定(首先匹配)到泛型(最后匹配)。

在你的情况下,你想要反过来。我会做这样的事情,它提供你期望的输出(所有标题元素首先匹配标题模板,因为显式优先级,只有斜体元素也匹配斜体模板,将<italics>添加到输出):

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

<xsl:template match="*[@rend='italics']">
    <italics><xsl:apply-templates /></italics>
</xsl:template>

<xsl:template match="title" priority="2">
    <title><xsl:next-match /></title>
</xsl:template>

您可能希望对更大的示例应用类似的编码模式,否则<italics>是唯一匹配的模式,我认为您希望两者都匹配,并且顺序正确(通用优先,然后具体)。

答案 1 :(得分:1)

Abel已经给出了一个很好的答案,我写另一个答案的原因是:

...but if I understand you, it is my only real option.

远非如此 - 通常是编程的情况,有不止一种方法可以做到这一点。我不会说xsl:next-match是丑陋的,但它破坏了许多样式表中存在的原则,即每个节点只处理一次,并且找到一个合适的模板。

另一种选择是将应该转换为包含文本内容的元素的属性与单独的模板相匹配:

<xsl:template match="title/@*">
  <xsl:element name="{.}">
     <xsl:apply-templates select="../text()"/>
  </xsl:element>
</xsl:template>

如果我理解正确,这就是你首先要做的事情。上面的模板匹配title元素的任何属性。然后,构造一个新元素,其名称对应于属性值(在本例中为“italic”)。最后,应用文本节点的内置模板来处理属性的父节点的文本内容。

<强>样式表

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="2.0" 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
  <xsl:output omit-xml-declaration="yes" indent="yes"/>

  <xsl:strip-space elements="*"/>

  <xsl:template match="/document">
    <xsl:copy>
        <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="title/@*">
    <xsl:element name="{.}">
        <xsl:apply-templates select="../text()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="title">
    <title>
        <xsl:choose>
            <xsl:when test="@*">
                <xsl:apply-templates select="@*"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates/>
            </xsl:otherwise>
        </xsl:choose>
    </title>
  </xsl:template>

</xsl:stylesheet>

另外,您不需要使用exclude-result-prefixes="xsl"从结果树中排除XSLT命名空间。任何带有xsl:前缀的内容都会被识别为要执行的指令,默认情况下会从生成的XML中排除名称空间。

XML输出

<document>
   <title>"One Title"</title>
   <title>
      <italics>Another Title</italics>
   </title>
</document>