2

我想从 XPath 表达式中提取对文档根目录的所有引用,并在它们之后注入一个自定义根目录。

我正在实现基于某种模式语言创建的 XML 实例文档的一小部分验证(或者更确切地说是修复错误)。该语言提供了指定自包含 XML 块的方法。每个这样的块都在一个单独的文件中定义,并指定 XML 元素层次结构。每个这样的层次结构都有一个或多个属于同一文档根的根元素,就像任何 XML 文档的不可见文档根一样。

然而,这些文件并不知道它们指定的只是更大系统的一部分。这个更大的系统实际上是另一个具有单个顶级 XML 元素的 XML 文档(具有另一个文档根),其中包含由任意数量的此类模式语言文件定义的所有根元素。

XML 层次结构中的任何节点都可能受到 XPath 表达式的约束,该表达式必须计算为真,以便在验证期间元素被视为有效。这就是我的问题的根源。这些 XPath 表达式可能包含绝对位置路径,它引用单个 XML 块的文档根而不是系统的文档根。考虑以下 XML 实例:

<data xmlns="system:uri">
    <root-one xmlns="root-one:uri">
        <items>
            <item>
                <group>base</group>
                <class>person</person>
                <name>John Smith</name>
                <description>valid entry</description>
            </item>
            <item>
                <group>base</group>
                <class>animal</person>
                <name>Dog</name>
                <description>invalid entry</description>
            </item>
        </items>
        <item-classes>
            <item-class>                
                <class>person</class>
                <group>base</group>
            </item-class>
        </item-classes>
    </root-one>
    <root-two xmlns="root-two:uri">
        <!-- obscured content -->
    </root-two>
</data>

{system:uri}data代表系统,{root-one:uri}root-one{root-two:uri}root-two两个 XML 块,每个都在它自己的模式语言文件中定义。假设每个root-one/items/item实例必须满足以下 XPath 条件,在模式语言文件中定义(不要介意current(),它与 XSLT 中的相同,指的是item实例之一):

context: /root-one/items/item
assert: group=/root-one/item-classes/item-class[class=current()/class]/group

实际上应该是

context: /data/root-one/items/item
assert: group=/data/root-one/item-classes/item-class[class=current()/class]/group

如何在任何 XPath 表达式中获取对文档根 (/) 的所有引用并使用正确的根注入它们?我无法控制这些表达式是如何形成的,所以它们可以有任何形状和大小,只要它们满足 XPath 1.0 语法,但我必须让它们正确计算。

我目前正在考虑在 java 中编写某种标记器来处理这个问题,但如果有更简单的解决方案,我宁愿不去研究它。表达式是在系统文档上下文中的 Schematron XSLT 转换期间评估的,所以如果我能以某种方式使用 XSLT 实现路径修复,那将是完美的。但是,我已准备好接受任何可能导致我找到解决方案的指示。

编辑01

这就是包含 XPath 表达式的示例文件的样子(在我的脑海中)。我希望转换@test属性的内容。属性的值@context是微不足道的,因为它总是具有相似的结构。

<?xml version="1.0" encoding="utf-8"?>
<iso:schema    xmlns="http://purl.oclc.org/dsdl/schematron" 
           xmlns:iso="http://purl.oclc.org/dsdl/schematron" 
           xmlns:sch="http://www.ascc.net/xml/schematron"
           xmlns:tl="toplevel:uri"
           xmlns:r1="root-one:uri"
           xmlns:r2="root-two:uri">

  <iso:ns prefix="tl" uri="toplevel:uri" />
  <iso:ns prefix="r1" uri="root-one:uri" />
  <iso:ns prefix="r2" uri="root-two:uri" />

  <iso:pattern>
    <iso:rule context="/r1:root-one/r1:items/r1:item">
      <iso:assert test="r1:group=/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group">The group of an item must match one of the predefined class groups.</iso:assert>
    </iso:rule>
  </iso:pattern>

</iso:schema>

请注意,@test属性的值可以是任何有效的 XPath 1.0 表达式。我想找到一个通用解决方案,它可以找到表达式中任何位置定义的任何文档根('/'),并使用自定义根元素注入它。实际文件可能包含任意数量的iso:pattern元素、iso:rule元素等。

编辑02

对于上面的示例,想要的结果是以下iso:assert元素:

<iso:assert test="r1:group=/tl:data/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group">The group of an item must match one of the predefined class groups.</iso:assert>

编辑03

作为回应您如何决定 /r1:root-one/ 必须以 '/tl:data' 开头?能否请您描述一下规则?——迪米特雷·诺瓦切夫

/tl:data表示通过将多个其他 XML 文档组合成一个文档而创建的文档的根元素。这些文档的内容作为子元素附加到此根元素。r1:root-one成为这样的孩子之一。XPath 约束是描述元素结构r1:root-one外观的模式定义的一部分,旨在仅在此子 XML 文档的上下文中工作。当子 XML 文档被附加到“父”文档时,如果表达式中存在绝对路径,它们就失去了意义。因此,如果表达式包含/r1:root-onethis 在新文档中将没有任何意义(其中没有root-one根元素,tl:data是唯一的根)。我想找到所有这样的案例(/r1:root-one/)并将它们转换(变成/tl:data/r1:root-one/) 所以表达式在新文档的上下文中起作用。

很难指定确切的规则。每个/出现在路径开头的“”(因此引用子 XML 文档的文档根)都应该替换为“ /tl:data/”,因此它现在引用新创建的文档的文档根。

编辑04

如上文所述,该解决方案应该适用于任何可以想象的 XPath 表达式。其他示例(来自 r1 命名空间的虚构元素是由我组成的 - 这在我脑海中听起来更好):

<iso:assert test="r1:group=/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group and r1:imaginary-element1=/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:imaginary-element1" />
<iso:assert test="r1:group=/r1:root-one/r1:item-classes/r1:item-class[r1:class=/r1:root-one/r1:imaginary-constants/r1:imaginary-constant]/r1:group" />

应该成为

<iso:assert test="r1:group=/tl:data/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group and r1:imaginary-element1=/tl:data/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:imaginary-element1" />
<iso:assert test="r1:group=/tl:data/r1:root-one/r1:item-classes/r1:item-class[r1:class=/tl:data/r1:root-one/r1:imaginary-constants/r1:imaginary-constant]/r1:group" />

编辑05

我现在可以选择切换到 XSLT 2.0 处理器。所以我会接受 XSLT 2.0 解决方案。

事实上,如果有人可以为我提供一个XSLT 正则表达式,该表达式将匹配/表示 XPath 1.0 表达式中的文档根的符号,这将解决我的问题(我将使用该replace()函数)。我一直在研究XPath 1.0 语法,但还没有任何有用的东西。

4

2 回答 2

0

在检查了XPath 1.0语法/规范并切换到XSLT2.0以获得regex支持后,我想出了以下怪物。

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:iso="http://purl.oclc.org/dsdl/schematron"
 xmlns:def="http://purl.oclc.org/dsdl/schematron">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="iso:assert">
    <xsl:copy>

        <xsl:variable name="no-double-slash">
          <!-- need to ignore double slashes since they interfere later -->  
          <xsl:value-of select="replace(@test,'//','unlikelycharseq_double_slash')" />
        </xsl:variable>        
        <xsl:variable name="no-asterisk">
          <!-- need to ignore '*/' which is preceded by and|or|mod|div since that's not mul operator -->  
          <xsl:value-of select="replace($no-double-slash,'([^a-zA-Z0-9_\-](and|or|mod|div)\s*)\*\s*/',concat('$1','unlikelycharseq_asterisk_slash'))" />
        </xsl:variable>
        <xsl:variable name="escaped1">
          <!-- any slash at line start or after an operator (except mul) needs to be replaced, '-' needs to be preceded by whitespace if it represents an operator -->  
          <xsl:value-of select="replace($no-asterisk,'^\s*/|([=+&lt;&gt;\[\(]\s*|\s+\-\s*|[^a-zA-Z0-9_\-](and|or|mod|div)\s*)/',concat('$1','/tl:data/'))" />
        </xsl:variable>
        <xsl:variable name="escaped2">
          <!-- any '*' not preceded by an operator, '@', '::', '(', '[' or ','  is a mul operator, so the following slash needs to be replaced -->  
          <xsl:value-of select="replace($escaped1,'((\s+[^\-]|[^@:\(\[,/|+=&gt;&lt;*])\s*\*\s*)/',concat('$1','/tl:data/'))" />
        </xsl:variable>  
        <xsl:variable name="with-asterisk">
          <!-- restore '*/' which we needed to ignore for the above regexes to work  -->  
          <xsl:value-of select="replace($escaped2,'unlikelycharseq_asterisk_slash','*/')" />
        </xsl:variable>        
        <xsl:variable name="fixed-path">
          <!-- restore '//' which we needed to ignore for the above regexes to work -->  
          <xsl:value-of select="replace($with-asterisk,'unlikelycharseq_double_slash','//')" />
        </xsl:variable>      

      <xsl:attribute name="test">
        <xsl:value-of select="$fixed-path" />
      </xsl:attribute>

      <xsl:comment>
        <xsl:value-of select="@test" />
      </xsl:comment>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

它实现了一些必须满足的规则,以便 '/' 表示文档根而不是路径分隔符:

  1. 出现在行首
  2. 出现在运算符之后
  3. 出现在 [, ( 等之后。

在使用正则表达式进行处理时,有几个 XPath 标记需要特殊处理,并且还必须考虑空格。字符 '*' 和 '-' 可能根本不代表运算符。到目前为止,这对我所有的测试用例都有效,但是由于我依赖语法而不是 XPath 的经验来形成表达式,所以我可能遗漏了一些东西。

这些正则表达式展示了 XML Schema/XPath 正则表达式风格的许多功能。需要多次替换运行,因为其中不支持某些高级功能。最值得注意的是环视。

如果有人给我比这个意大利面条 XSLT 更好的解决方案,我会很乐意接受。请注意,这可能不是最好的,甚至不是解决我的问题的唯一方法。

当这个变换被应用到

<iso:schema    xmlns="http://purl.oclc.org/dsdl/schematron"
           xmlns:iso="http://purl.oclc.org/dsdl/schematron"
           xmlns:sch="http://www.ascc.net/xml/schematron"
           xmlns:tl="toplevel:uri"
           xmlns:r1="root-one:uri"
           xmlns:r2="root-two:uri">

  <iso:ns prefix="tl" uri="toplevel:uri" />
  <iso:ns prefix="r1" uri="root-one:uri" />
  <iso:ns prefix="r2" uri="root-two:uri" />

  <iso:pattern>
    <iso:rule context="/r1:root-one/r1:items/r1:item">
      <iso:assert test="r1:group=/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group">The group of an item must match one of the predefined class groups.</iso:assert>
      <iso:assert test="r1:group=/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group and r1:imaginary-element1=/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:imaginary-element1" />
      <iso:assert test="r1:group=/r1:root-one/r1:item-classes/r1:item-class[r1:class=/r1:root-one/r1:imaginary-constants/r1:imaginary-constant]/r1:group" />
      <iso:assert test="../type[. * /root-one/imaginary-constants/imaginary-constant1 > 10]"/>
      <iso:assert test="../type[. mod /root-one/imaginary-constants/imaginary-constant1 = 1]"/>
      <iso:assert test="   /root-one/imaginary-constants/imaginary-constant1"/>
      <iso:assert test="../type[. mod /root-one/imaginary-constants/imaginary-constant1 = 1]"/>
      <iso:assert test="../group= /root-one/item-classes/item-class[name=current()/name]/group and ../class=/root-one/item-classes/item-class[name=current()/name]/class"/>
      <iso:assert test="../type[. = /root-one/imaginary-constants/imaginary-constant1]"/>
      <iso:assert test="../type[.*/root-one/imaginary-constants/imaginary-constant1 = 1]"/>
      <iso:assert test="../type[/root-one/imaginary-constants/imaginary-constant1 mod ../../*/type]"/>
      <iso:assert test="../type[/root-one/imaginary-constants/imaginary-constant1 mod ../multiminus---/type > 1]"/>
      <iso:assert test="../type[../multiminus---*/root-one/imaginary-constants/imaginary-constant1 > 1]"/>
      <iso:assert test="//container/*/type[. &gt; 5]"/>
    </iso:rule>
  </iso:pattern>
</iso:schema>

它导致了这个(路径按预期固定)

<iso:schema xmlns="http://purl.oclc.org/dsdl/schematron"
            xmlns:iso="http://purl.oclc.org/dsdl/schematron"
            xmlns:sch="http://www.ascc.net/xml/schematron"
            xmlns:tl="toplevel:uri"
            xmlns:r1="root-one:uri"
            xmlns:r2="root-two:uri">
   <iso:ns prefix="tl" uri="toplevel:uri"/>
   <iso:ns prefix="r1" uri="root-one:uri"/>
   <iso:ns prefix="r2" uri="root-two:uri"/>
   <iso:pattern>
      <iso:rule context="/r1:root-one/r1:items/r1:item">
         <iso:assert test="r1:group=/tl:data/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group"><!--r1:group=/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group--></iso:assert>
         <iso:assert test="r1:group=/tl:data/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group and r1:imaginary-element1=/tl:data/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:imaginary-element1"><!--r1:group=/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group and r1:imaginary-element1=/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:imaginary-element1--></iso:assert>
         <iso:assert test="r1:group=/tl:data/r1:root-one/r1:item-classes/r1:item-class[r1:class=/tl:data/r1:root-one/r1:imaginary-constants/r1:imaginary-constant]/r1:group"><!--r1:group=/r1:root-one/r1:item-classes/r1:item-class[r1:class=/r1:root-one/r1:imaginary-constants/r1:imaginary-constant]/r1:group--></iso:assert>
         <iso:assert test="../type[. * /tl:data/root-one/imaginary-constants/imaginary-constant1 &gt; 10]"><!--../type[. * /root-one/imaginary-constants/imaginary-constant1 > 10]--></iso:assert>
         <iso:assert test="../type[. mod /tl:data/root-one/imaginary-constants/imaginary-constant1 = 1]"><!--../type[. mod /root-one/imaginary-constants/imaginary-constant1 = 1]--></iso:assert>
         <iso:assert test="/tl:data/root-one/imaginary-constants/imaginary-constant1"><!--   /root-one/imaginary-constants/imaginary-constant1--></iso:assert>
         <iso:assert test="../type[. mod /tl:data/root-one/imaginary-constants/imaginary-constant1 = 1]"><!--../type[. mod /root-one/imaginary-constants/imaginary-constant1 = 1]--></iso:assert>
         <iso:assert test="../group= /tl:data/root-one/item-classes/item-class[name=current()/name]/group and ../class=/tl:data/root-one/item-classes/item-class[name=current()/name]/class"><!--../group= /root-one/item-classes/item-class[name=current()/name]/group and ../class=/root-one/item-classes/item-class[name=current()/name]/class--></iso:assert>
         <iso:assert test="../type[. = /tl:data/root-one/imaginary-constants/imaginary-constant1]"><!--../type[. = /root-one/imaginary-constants/imaginary-constant1]--></iso:assert>
         <iso:assert test="../type[.*/tl:data/root-one/imaginary-constants/imaginary-constant1 = 1]"><!--../type[.*/root-one/imaginary-constants/imaginary-constant1 = 1]--></iso:assert>
         <iso:assert test="../type[/tl:data/root-one/imaginary-constants/imaginary-constant1 mod ../../*/type]"><!--../type[/root-one/imaginary-constants/imaginary-constant1 mod ../../*/type]--></iso:assert>
         <iso:assert test="../type[/tl:data/root-one/imaginary-constants/imaginary-constant1 mod ../multiminus---/type &gt; 1]"><!--../type[/root-one/imaginary-constants/imaginary-constant1 mod ../multiminus- - -/type > 1]--></iso:assert>
         <iso:assert test="../type[../multiminus---*/tl:data/root-one/imaginary-constants/imaginary-constant1 &gt; 1]"><!--../type[../multiminus- - -*/root-one/imaginary-constants/imaginary-constant1 > 1]--></iso:assert>
         <iso:assert test="//container/*/type[. &gt; 5]"><!--//container/*/type[. > 5]--></iso:assert>
      </iso:rule>
   </iso:pattern>
</iso:schema>
于 2013-01-16T10:41:45.937 回答
0

这种转变

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:iso="http://purl.oclc.org/dsdl/schematron"
 xmlns:def="http://purl.oclc.org/dsdl/schematron">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="iso:assert/@test">
  <xsl:attribute name="test">
    <xsl:value-of select=
    "concat(substring-before(.,'/'),'/t1:data/', substring-after(.,'/'))"/>
  </xsl:attribute>
 </xsl:template>
</xsl:stylesheet>

应用于提供的 XML 文档时:

<iso:schema    xmlns="http://purl.oclc.org/dsdl/schematron"
           xmlns:iso="http://purl.oclc.org/dsdl/schematron"
           xmlns:sch="http://www.ascc.net/xml/schematron"
           xmlns:tl="toplevel:uri"
           xmlns:r1="root-one:uri"
           xmlns:r2="root-two:uri">

  <iso:ns prefix="tl" uri="toplevel:uri" />
  <iso:ns prefix="r1" uri="root-one:uri" />
  <iso:ns prefix="r2" uri="root-two:uri" />

  <iso:pattern>
    <iso:rule context="/r1:root-one/r1:items/r1:item">
      <iso:assert test="r1:group=/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group">The group of an item must match one of the predefined class groups.</iso:assert>
    </iso:rule>
  </iso:pattern>
</iso:schema>

产生想要的正确结果

<iso:schema xmlns:iso="http://purl.oclc.org/dsdl/schematron"
 xmlns="http://purl.oclc.org/dsdl/schematron"
 xmlns:sch="http://www.ascc.net/xml/schematron"
 xmlns:tl="toplevel:uri" xmlns:r1="root-one:uri"
 xmlns:r2="root-two:uri">
   <iso:ns prefix="tl" uri="toplevel:uri"/>
   <iso:ns prefix="r1" uri="root-one:uri"/>
   <iso:ns prefix="r2" uri="root-two:uri"/>
   <iso:pattern>
      <iso:rule context="/r1:root-one/r1:items/r1:item">
         <iso:assert test="r1:group=/t1:data/r1:root-one/r1:item-classes/r1:item-class[r1:class=current()/r1:class]/r1:group">The group of an item must match one of the predefined class groups.</iso:assert>
      </iso:rule>
   </iso:pattern>
</iso:schema>

说明

正确使用:

  1. 身份规则。

  2. 模板匹配模式。

  3. 标准 XPath函数concat()和.substring-before()substring-after()

于 2013-01-12T15:50:17.107 回答