【问题标题】:dynamic xpath in xslt?xslt中的动态xpath?
【发布时间】:2011-06-05 12:38:45
【问题描述】:

我有以下一组文件:

SourceFile.xml:

      <?xml version="1.0" encoding="utf-8" ?>
     <Employees>
     <Employee id="1">
          <firstname relationship="headnote">Atif</firstname>
          <lastname relationship="lname">Bashir</lastname>
          <age relationship="age">32</age>
          </Employee>
     </Employees>

参数设置.xml

        <?xml version="1.0" encoding="utf-8"?>
        <Settings>
        <Employee id="1">
             <sourceFile>Lookup1.xml</sourceFile>
             <sourceXpathfield>Employees/Employee[@id</sourceXpathfield>
             <lookupXpathfield>Employees/Employee[@id='1']</lookupXpathfield>
             <elementstoinsert>xyz</elementstoinsert>
             </Employee>
         </Settings>

查找.xml

<?xml version="1.0" encoding="utf-8"?>
 <Employees>
  <Employee id="1">
      <department code="102">HR</department>
   </Employee>
   </Employees>

transform.xsl

  <?xml version="1.0" encoding="UTF-8" ?>
   <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">

   <xsl:include href="identity.xsl"/>

  <xsl:param name="EmployeeId" select="'1,2'" />
  <xsl:variable name="FileSettings" select="document('test3.xml')" />
  <xsl:variable name="SuppressSetting" select="$FileSettings/Settings/Employee[@id = tokenize($EmployeeId, ',')]" />

  <xsl:template match="Employee">
  <xsl:copy>
  <xsl:apply-templates select="@*"/>
  <xsl:apply-templates select="publisher" />
  <xsl:apply-templates select="node() except publisher"/>
  <xsl:variable name="outerfile" select="document($SuppressSetting/sourceFile)"></xsl:variable>
  <xsl:variable name="outerfiledetails" select="$outerfile/$SuppressSetting/lookupXpathfield"></xsl:variable>
  <xsl:value-of select="$outerfiledetails"></xsl:value-of>
</xsl:copy>
</xsl:template>

</xsl:stylesheet> 

输出应该是:

     <?xml version="1.0" encoding="utf-8" ?>
     <Employees>
     <Employee id="1">
          <firstname relationship="headnote">Atif</firstname>
          <lastname relationship="lname">Bashir</lastname>
          <age relationship="age">32</age>
          HR
          </Employee>
     </Employees>

我在 Transform.xsl 中更改了以下行

<xsl:variable name="outerfiledetails" select="$outerfile/$SuppressSetting/lookupXpathfield"></xsl:variable>

进入

<xsl:variable name="outerfiledetails" select="$outerfile/Employees/Employee[@id='1']"></xsl:variable>

然后我得到我的输出,但我想将 SourceFile.xmlLookup.xml 的 XPath 压缩保留到 ParamerterSettings.xml 中,以便我可以编写更通用的脚本。除了动态 xpath 之外,这可以通过任何其他方式完成吗?任何想法或暗示都将受到高度赞赏。

【问题讨论】:

  • 你已经简化了你最初的几乎可怕的问题,这是一个很好的进步,但是这个问题仍然太复杂并且没有明确定义。尝试改写它并进一步简化它——我相信你不需要所有的细节。特别是,必须处理两个以上的文件,这让每个人都放弃了,甚至试图理解这个问题。太复杂了:我永远不会以这种方式设计 XSLT 应用程序,相信我,我的 XSLT 应用程序具有真正具有挑战性的复杂性,99% 的开发人员认为使用 XSLT 是不可能的。
  • 嗨 Dimitre,我想要的是从外部文件执行 xpath 值。原因是我有多个外部文件,我想从中获取数据并将该数据插入主源文件。我可以通过硬编码多个模板来做到这一点,但我想避免这种情况,并制作一个模板,该模板根据不同的连接或 xpath 值从多个文件中读取定义为外部文件中的设置。
  • @Nick-Jones 的回答是正确的:这不能在 XSLT/XPath 2.0 中完成,它可能由下一个版本提供。然而,我高度怀疑动态 XPath 评估的必要性——如果你很好地描述了你的问题,那么可能有一个不需要这个的解决方案。为什么不以最简单的形式问这个问题:“我如何评估这个 XML 文档中包含的这个表达式?”。虽然不可能使用纯 XSLT 解决方案,但我知道至少有三种不同的“混合”解决方案可以解决这个问题。
  • 查看我的回答,了解您的问题的三种不同解决方案,这样您就不必等到 XSLT 3.0 到来。 :)

标签: xslt xslt-2.0


【解决方案1】:

在纯 XSLT 1.0 或 2.0 中无法进行动态 XPath 评估。

在“混合”解决方案中至少有三种方法可以做到这一点

我。使用 EXSLT 函数dyn:evaluate()

不幸的是,很少有 XSLT 1.0 处理器实现 dyn:evaluate()

二。使用 XSLT 处理 XML 文档并生成一个包含 XPath 表达式的新 XSLT 文件——然后执行新生成的转换

很少有人这样做,在我看来,这比下一个解决方案更复杂。

三。 the XPath Visualizer 的工作方式

想法是:

  1. 在 XSLT 样式表中有一个像这样定义的全局变量

      <xsl:variable name="vExpression" select="dummy"/>
    
  2. 然后,使用 DOM 将样式表加载为 XML 文档,并将 vExpression 变量的 select 属性替换为源 XML 文档中包含的实际 XPath 表达式。

  3. 最后,使用加载到内存中并动态更新的 xslt 样式表启动转换


四。使用 XSLT 3.0

使用 xsl:evaluate> 指令

【讨论】:

  • 嗨 Dimitre,我想可以使用第三个选项,但这本身有点复杂。我现在将尝试使用多个模板。感谢您的意见。
  • @atif:它并不太复杂——您可能有兴趣查看 XPath Visualizer 的源代码——这只是几行代码。
  • 这里是方法 #1 的示例:stackoverflow.com/questions/230411/…
  • dyn:evaluate ingenious.. xsltproc 实现了它,这就是我所需要的。感谢您的精彩提示。
  • @Jagger,在 XSLT 3.0 中有一条新指令:&lt;xsl:evaluate&gt; -- 在这里阅读:w3.org/TR/2014/WD-xslt-30-20141002/#dynamic-xpath
【解决方案2】:

一个可能的技巧是调用参数化的xsl:template,它执行关键的选择比较部分并将其输出评估为字符串。 See this answer

【讨论】:

    【解决方案3】:

    是的,我们可以...至少是初步的。这是我在“评估”功能可用之前与 Saxon CE (XSLT 2.0) 一起使用的解决方法。也许这不适用于各种复杂的 XML 文档,但也许您可以根据需要调整“过滤器”(查询属性等)。

    在我的特殊情况下,我有 xPath 表达式描述元素的“完整”路径,包括它们的名称,诀窍是使用通配符仅与动态 xPath 表达式的最后一个元素组合,例如使用“第三”而不是“第一/第二/第三”:

    <xsl:variable name="value" select="//*[name() = 'third']" />
    

    为了限制结果(将选择名称为“第三”的所有元素),您还必须查询祖先“第一”和“第二”。也许任何人都有简化以下代码的想法,尤其是祖先的调用:

    <!-- global variable which holds a XML document with root node "data" -->
    <xsl:variable name="record" select="document('record.xml')/data"/>
    
    <!-- select elements from the global "record" variable using dynamic xpath expressions -->
    <xsl:function name="utils:evaluateXPath">
    
        <xsl:param name="xpath" as="xs:string"/>
    
        <xsl:choose>
    
            <xsl:when test="function-available('evaluate')">
    
                <!-- modify the code if the function has been implemented :-) -->
                <xsl:value-of select="'function evaluate() can be used ...'"/>
    
            </xsl:when>
    
            <xsl:otherwise>
    
                <!-- get a list of elements defined in the xpath expression -->
                <xsl:variable name="sequence" select="tokenize($xpath, '/')" />
    
                <!-- get the number of ancestors for the last element -->
                <xsl:variable name="iAncestors" select="count($sequence)-1" as="xs:integer" />
    
                <!-- get the last element from the xpath expression -->
                <xsl:variable name="lastElement" select="if ($iAncestors > 0) then $sequence[last()] else $xpath" />
    
                <!-- try to find the desired element as defined in xpath expression -->
                <!-- use parenthesis to grab only the first occurrence -->
                <xsl:value-of select="
                    if ($iAncestors = 0) then
                        ($record//*[name() = $lastElement and not(*)])[1]
                    else if ($iAncestors = 1) then
                        ($record//*[name() = $lastElement and not(*) and (name(..) = $sequence[1])])[1]
                    else if ($iAncestors = 2) then
                        ($record//*[name() = $lastElement and not(*) and (name(..) = $sequence[2]) and (name(../..) = $sequence[1])])[1]
                    else if ($iAncestors = 3) then
                        ($record//*[name() = $lastElement and not(*) and (name(..) = $sequence[3]) and (name(../..) = $sequence[2]) and (name(../../..) = $sequence[1])])[1]
                    else if ($iAncestors = 4) then
                        ($record//*[name() = $lastElement and not(*) and (name(..) = $sequence[4]) and (name(../..) = $sequence[3]) and (name(../../..) = $sequence[2]) and (name(../../../..) = $sequence[1])])[1]
                    else if ($iAncestors = 5) then
                        ($record//*[name() = $lastElement and not(*) and (name(..) = $sequence[5]) and (name(../..) = $sequence[4]) and (name(../../..) = $sequence[3]) and (name(../../../..) = $sequence[2]) and (name(../../../../..) = $sequence[1])])[1]
                    else 'failure: too much elements for evaluating dyn. xpath ... add another level!'"
                />
    
            </xsl:otherwise>
    
        </xsl:choose>
    
    </xsl:function>
    

    就我的目的而言,只返回第一个没有子节点的匹配元素。可能您必须根据您的特定需求进行调整。

    【讨论】:

    • 使用&lt;xsl:choose&gt;是个好主意,至少如果选择比较短,几行就解决了我的动态问题。
    【解决方案4】:

    您无法在 XSLT 2.0 中执行此操作,但您可以在最新版本的 XSLT 中执行此操作:

    http://www.w3.org/TR/xslt-21/#element-evaluate

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-11
      • 2019-06-02
      • 1970-01-01
      相关资源
      最近更新 更多