xslt中的动态xpath?

时间:2011-01-07 20:31:42

标签: xslt xslt-2.0

我有一组文件:

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>

ParamerterSettings.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>

Lookup.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以任何其他方式完成吗?任何暗示相同的想法或提示都将受到高度赞赏。

4 个答案:

答案 0 :(得分:11)

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

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

<强>予。使用EXSLT函数dyn:evaluate()

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

<强> II。使用XSLT处理XML文档并生成包含XPath表达式的新XSLT文件 - 然后执行新生成的转换。

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

<强> III。 the XPath Visualizer的工作方式

这个想法是:

  1. 在XSLT样式表中定义一个全局变量,如下所示

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

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

答案 1 :(得分:3)

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

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

答案 2 :(得分:2)

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

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

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

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

<!-- 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>

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

答案 3 :(得分:0)

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