1

我正在使用 Schematron 在自定义模式感知 XML 编辑器中验证实例 XML 文档(请注意,Schematron 验证只是一个 XSLT 转换)。该实例可能包含在其值中带有路径的元素(简化的 XPath 表达式)。这种路径的一个例子是:

/p:root/p:level-one/r:level-two/r:level-two-leaf

其中两个前缀(p 和 r)都绑定到实例文档中定义的命名空间。我的 Schematron 通过确保路径指向的元素实际存在于实例文档中来验证此类路径。它依赖EXSLT来执行此操作(我被迫使用 XSLT1.0),更准确地说dyn:evaluate()是为了评估元素的文本值,如您所见,它基本上是一个 XPath 表达式。它就像一个魅力。

但有一个问题,实际上是一个巨大的问题。调用从 Schematron XSLT 执行,该 XSLT在它自己的命名空间上下文中dyn:evaluate()评估 XPath 表达式。这意味着为了使其正常工作,实例文档和 Schematron XSLT 必须使用完全相同的前缀 → 命名空间捆绑。我不能强制用户对我的架构中指定的相同命名空间使用相同的前缀......这将是一个愚蠢的要求(但确保至少在两者中使用相同的命名空间)。Schematron 总是在实例验证发生之前生成,但出于性能原因,这只执行一次。我唯一的选择是以某种方式预处理来自实例的路径,并进行某种从“实例路径”到“XSLT 路径”的转换。我是 XSLT 的新手,不知道如何实现这一点。

我如何将这些文本值转换为 XSLT 的名称空间上下文所需的内容?这甚至可能吗?我目前正在考虑在每次验证调用之前在内存中修复 XSLT(所有这些都在 Java 中完成)——重命名前缀以使其匹配实例绑定或注入新的命名空间属性——但这可能导致前缀名称冲突,我'不确定它将如何影响验证性能。我愿意接受任何建议,因为我假设这也是其他人必须遇到的事情(在使用 Schematron 或 时dyn:evaluate())。

编辑:从这里开始澄清我正在尝试做的事情。

我有一个用户正在编辑器中编辑的 XML 实例文件。这种文件的一个例子是:

<?xml version="1.0" encoding="utf-8"?>
<config xmlns="http://example.com/ns/config"
         xmlns:cfg="http://example.com/ns/config"
         xmlns:ns1="http://example.com/ns/custom-01"
         xmlns:ns2="http://example.com/ns/custom-02">
  <ns1:some-element>/cfg:config/ns1:other-element/ns2:nested-element</ns1:some-element>
  <ns1:other-element>
    <ns2:nested-element>some value</ns2:nested-element>
  </ns1:other-element>
</config>

然后,这样的文档会通过 schematron 验证,这基本上是一个 XSLT 转换。ns1:some-element只有当路径 in引用同一文档中的现有元素时,它才会被声明为有效(因此上面的示例是有效的)。

schematron XSLT 看起来像这样(注意它已经被大大简化了):

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--This XSLT was automatically generated from a Schematron schema.-->
<xsl:stylesheet xmlns:iso="http://purl.oclc.org/dsdl/schematron"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:dyn="http://exslt.org/dynamic"
                xmlns:exsl="http://exslt.org/common"
                xmlns:sch="http://www.ascc.net/xml/schematron"
                xmlns:cfg="http://example.com/ns/config"
                xmlns:cust1="http://example.com/ns/custom-01"
                xmlns:cust2="http://example.com/ns/custom-02"
                extension-element-prefixes="dyn exsl"
                version="1.0">

<!-- other templates -->

<xsl:template match="/cfg:config/cust1:some-element">
    <xsl:choose>
        <xsl:when test="dyn:evaluate(.)">
            <!-- do stuff -->
        <xsl:when>
    </xsl:choose>
</xsl:template>

<!-- other templates -->

</xsl:stylesheet>

我确信这可以解释问题。调用dyn:evaluate(.)将尝试评估/cfg:config/ns1:other-element/ns2:nested-elementXPath 表达式,该表达式使用来自转换的预定义的未绑定前缀(因此始终评估为 false)。

问题过去和现在仍然是:如何翻译这些 XPath 表达式,以便它们在转换中真正有意义?

4

1 回答 1

2

XSLT 1 在 XSLT 2 十年之后是痛苦的:-)

我相信下面的样式表应该可以工作,但它不能在我可用的两个 xslt 1 处理器(xsltproc 和 saxon6)中的任何一个中工作,但也许它会在你拥有的那个处理器中工作,或者你可以将其用作基础。

这个想法只是用样式表中使用的前缀替换源表达式中的前缀。xsltproc 没有实现 exlt 的正则表达式模块,所以我使用了一个简单的字符串替换模板(它会覆盖作为彼此子字符串的前缀,或者看起来像字符串中出现的前缀的东西)但是它失败了,因为 xsltproc 没有似乎正确地将命名空间节点的字符串值设为命名空间:我明白了

$ xsltproc cfg.xsl cfg.xml
xml
here 1 http://www.w3.org/XML/1998/namespace
ns2
here 2
ns1
here 2
cfg
here 2


===
/cfg:config/ns1:other-element/ns2:nested-element
===
XPath error : Undefined namespace prefix

所以除了预定义的 xml 命名空间之外,所有命名空间节点都有空字符串值。

saxon6 gets this right:
$ saxon cfg.xml cfg.xsl
xml
here 1 http://www.w3.org/XML/1998/namespace
cfg
here 1 http://example.com/ns/config
ns1
here 1 http://example.com/ns/custom-01
ns2
here 1 http://example.com/ns/custom-02


===
/cfg:config/cust1:other-element/cust2:nested-element
===

Error at xsl:choose on line 26 of file:/C:/tmp/cfg.xsl:
  The URI http://exslt.org/dynamic does not identify an external Java class
Transformation failed: Run-time errors were reported

但没有实现 dyn:evaluate 函数。

无论如何,这是代码:

<xsl:stylesheet xmlns:iso="http://purl.oclc.org/dsdl/schematron"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:dyn="http://exslt.org/dynamic"
        xmlns:exsl="http://exslt.org/common"
        xmlns:sch="http://www.ascc.net/xml/schematron"
        xmlns:cfg="http://example.com/ns/config"
        xmlns:cust1="http://example.com/ns/custom-01"
        xmlns:cust2="http://example.com/ns/custom-02"
        extension-element-prefixes="dyn exsl"
        version="1.0">

 <!-- other templates -->

 <xsl:variable name="ns-catch"><x/></xsl:variable>
 <xsl:variable name="xsl-ns" select="exsl:node-set($ns-catch)/*/namespace::*[name()]"/>
 <xsl:template match="/cfg:config/cust1:some-element">
  <xsl:variable name="fix-source">
   <xsl:call-template name="nsprefix"/>
  </xsl:variable>
  <xsl:message>

   ===
   <xsl:value-of select="$fix-source"/>
   ===
  </xsl:message>
  <xsl:choose>
   <xsl:when test="dyn:evaluate($fix-source)">
    <!-- do stuff -->
   </xsl:when>
  </xsl:choose>
 </xsl:template>

 <xsl:template name="nsprefix">
  <xsl:param name="source-ns" select="namespace::*[name()]"/>
  <xsl:param name="string" select="."/>
  <xsl:choose>
   <xsl:when test="$source-ns">
    <xsl:message><xsl:value-of select="name($source-ns)"/></xsl:message>
    <xsl:choose>
     <xsl:when test="string($source-ns[1])=$xsl-ns">
      <xsl:message>here 1 <xsl:value-of  select="string($source-ns[1])"/></xsl:message>
      <xsl:variable name="newstring">
       <xsl:call-template name="replace">
    <xsl:with-param name="string" select="$string"/>
    <xsl:with-param name="from" select="concat(name($source-ns[1]),':')"/>
    <xsl:with-param name="to" select="concat(name($xsl-ns[.=$source-ns[1]][1]),':')"/>
       </xsl:call-template>
      </xsl:variable>
      <xsl:call-template name="nsprefix">
       <xsl:with-param name="source-ns" select="$source-ns[position()!=1]"/>
       <xsl:with-param name="string" select="$newstring"/>
      </xsl:call-template>
     </xsl:when>
     <xsl:otherwise>
      <xsl:message>here 2</xsl:message>
      <xsl:call-template name="nsprefix">
       <xsl:with-param name="source-ns" select="$source-ns[position()!=1]"/>
       <xsl:with-param name="string" select="$string"/>
      </xsl:call-template>
     </xsl:otherwise>
    </xsl:choose>
   </xsl:when>
   <xsl:otherwise>
    <xsl:value-of select="$string"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
 <xsl:template name="replace">
  <xsl:param name="string"/>
  <xsl:param name="from"/>
  <xsl:param name="to"/>
  <xsl:choose>
   <xsl:when test="contains($string,$from)">
    <xsl:value-of select="substring-before($string,$from)"/>
    <xsl:value-of select="$to"/>
    <xsl:call-template name="replace">
     <xsl:with-param name="string" select="substring-after($string,$from)"/>
     <xsl:with-param name="from" select="$from"/>
     <xsl:with-param name="to" select="$to"/>
    </xsl:call-template>
   </xsl:when>
   <xsl:otherwise>
    <xsl:value-of select="$string"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
 <!-- other templates -->

</xsl:stylesheet>
于 2012-04-05T19:26:33.543 回答