XSL 从另一个文档评估动态 XPATH

Posted

技术标签:

【中文标题】XSL 从另一个文档评估动态 XPATH【英文标题】:XSL evaluate dymanic XPATH from another document 【发布时间】:2021-12-23 18:11:46 【问题描述】:

我在下面定义了两个示例文档。在 module_meta.xml 中,只有 xpath /mdata/effectivity 上的效果节点是相关的。如下所示,它们包含一个path 属性和一个effrg 属性。现在的目标是评估module.xml 上的xpath(在module_meta.xml 中定义为path 属性)并将effrg 附加到它上面。有关所需结果,请参阅desired_output.xml。 xsl 转换应用于module.xml。我知道我必须使用document() 函数来“包含”module_meta.xml,但到目前为止我很茫然。

module.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE proc>
    
<procbody>
 <info>
  <action lid="a">
  </action>
  <action lid="b">
  </action>
  <action lid="c">
  </action>
 </info>
</procbody>

module_meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mdata>
<mdata>
  <metadata>
    <metadata-item name="n1" value="v1" />
    <metadata-item name="n2" value="v2" />
    <metadata-item name="n3" value="v3" />
  </metadata>
  <effectivity>
    <effect path="//*[@lid='a']" effrg="0074 0080 0087" />
    <effect path="//*[@lid='b']" effrg="0136 0146 0174" />
  </effectivity>
</mdata>

desired_output.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE proc>
        
<procbody>
 <info>
  <action lid="a" effrg="0074 0080 0087">
  </action>
  <action lid="b" effrg="0136 0146 0174">
  </action>
  <action lid="c">
  </action>
 </info>
</procbody>

【问题讨论】:

XSLT 3(受 Saxon 10 及更高版本的所有版本、Saxon 9.8 及更高版本的 PE 和 EE、Saxon-JS 2 和 Altova XML 2017 R3 及更高版本的支持)具有xsl:evaluate。一些较旧的实现提供扩展功能或让您设置一个用于 XPath 评估。或者你可以链接两个样式表。 【参考方案1】:

在支持 xsl:evaluate 的 XSLT 3 中:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  xmlns:mf="http://example.com/mf"
  expand-text="yes">
  
  <xsl:function name="mf:evaluate" as="element(action)?">
    <xsl:param name="effect" as="element(effect)"/>
    <xsl:evaluate xpath="$effect/@path" context-item="$main-doc"/>
  </xsl:function>

  <xsl:variable name="main-doc" select="/"/>
  
  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="info/action">
    <xsl:copy>
      <xsl:apply-templates select="@*, $meta//effect[current() is mf:evaluate(.)]/@effrg"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:param name="meta">
<mdata>
  <metadata>
    <metadata-item name="n1" value="v1" />
    <metadata-item name="n2" value="v2" />
    <metadata-item name="n3" value="v3" />
  </metadata>
  <effectivity>
    <effect path="//*[@lid='a']" effrg="0074 0080 0087" />
    <effect path="//*[@lid='b']" effrg="0136 0146 0174" />
  </effectivity>
</mdata>
  </xsl:param>
  
</xsl:stylesheet>

为了示例的自包含性,第二个文档是作为参数内联的,但您当然可以改用&lt;xsl:param name="meta" select="doc('module_meta.xml')"/&gt;

对于每个effect 元素只使用一次xsl:evaluate 可能会更有效,例如,通过声明一个这样做的键,并存储生成的ID(如果有):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  xmlns:mf="http://example.com/mf"
  expand-text="yes">
  
  <xsl:key name="ref" match="mdata/effectivity/effect">
    <xsl:variable name="referenced-node" as="node()?">
      <xsl:evaluate xpath="@path" context-item="$main-doc" as="node()?"/>
    </xsl:variable>
    <xsl:sequence select="generate-id($referenced-node)"/>
  </xsl:key>

  <xsl:variable name="main-doc" select="/"/>
  
  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="info/action">
    <xsl:copy>
      <xsl:apply-templates select="@*, key('ref', generate-id(), $meta)/@effrg"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:param name="meta">
<mdata>
  <metadata>
    <metadata-item name="n1" value="v1" />
    <metadata-item name="n2" value="v2" />
    <metadata-item name="n3" value="v3" />
  </metadata>
  <effectivity>
    <effect path="//*[@lid='a']" effrg="0074 0080 0087" />
    <effect path="//*[@lid='b']" effrg="0136 0146 0174" />
  </effectivity>
</mdata>
  </xsl:param>
  
</xsl:stylesheet>

如果您想匹配其他文档中引用的任何元素并复制除path 之外的任何属性,您可以将具有match="info/action" 的模板更改为

  <xsl:template match="*[key('ref', generate-id(), $meta)]">
    <xsl:copy>
      <xsl:apply-templates select="@*, key('ref', generate-id(), $meta)/(@* except @path), node()"/>
    </xsl:copy>
  </xsl:template>

【讨论】:

感谢您的回答。我看到您在模板上添加了 match="info/action"。问题是,我不能确定 xpath 是否会在 /info/action 上进行评估。它必须是动态的。从技术上讲,它可以评估为 /foo/bar 然后必须将 effrg 添加到 bar 标签。如何解决这个问题? @sboti8m,我认为您的第一个输入文档是 XSLT 的主要输入文档,并使用匹配模板处理其中的元素,尝试根据您显示的示例使用一些精确的匹配模式。这就是为什么代码显然是xsl:template match="info/action"。这与 XPath 评估无关,它发生在我设置的函数的其他地方。根据样本,我确实假设path 属性选择action 元素或什么都不选择,正如as="element(action)?" 在函数上声明的那样。没有它,该功能将起作用。 如果输入没有任何 foo/bar 元素,那么说“从技术上讲,它可以评估为 /foo/bar,然后必须将 effrg 添加到 bar 标签”是没有意义的. xsl:evaluate 将查找元素(如果存在)以及要处理的代码,例如effrg 属性不会改变。您还可以将match="info/action" 更改为更通用的名称,但如果没有明确的描述,您无法猜测和编码您的 XML 可能具有的所有选项和变体。因此,您需要编辑问题并更具体地说明您希望更改的主要 XML 中的哪些元素 您好,谢谢您的回答。可以假设,xpath 将评估为有效节点。因此,为了使其更通用,我从 info/action 更改为 node()。但这似乎不起作用。您的解决方案运行良好,但 module_meta.xml 文件可能会更改,而 module.xml 保持不变。因此可能会出现不同的 xpath,它评估为完全不同的节点(比如说 procbody/info)匹配中的“硬编码”信息/动作不够通用。 @sboti8m,看看我添加的最后一个建议是否有帮助,匹配模式没有硬编码以使该方法有效,而是从您给定的输入结构中推断出来的。但是 XSLT 肯定足够灵活,可以匹配所有元素 (match="*") 或所有引用的元素(请参阅答案的编辑)。

以上是关于XSL 从另一个文档评估动态 XPATH的主要内容,如果未能解决你的问题,请参考以下文章

xpath 文档评估包含此 OR

在 XSLT 中动态包含其他 XSL 文件

在tokenize()中使用XSL XPATH if语句

用XPath精确定位节点元素&selenium使用Xpath定位之完整篇

用XPath精确定位节点元素&selenium使用Xpath定位之完整篇

XSLT