6

我有一个 XSLT,我在其中创建(从输入数据)中间变量,如下所示(硬编码示例,但本质上是动态的):

<xsl:variable name="variableX">
    <ValidCode CodePart="CP1" Code="C1"/>
    <ValidCode CodePart="CP2" Code="C2"/>
    <ValidCode CodePart="CP1" Code="C3"/>
    <ValidCode CodePart="CP2" Code="C4"/>
    <ValidCode CodePart="CP2" Code="C5"/>
</xsl:variable>

我希望遍历 CodePart 值的不同出现。在 XSLT 2.0 中很容易:

<xsl:for-each select="distinct-values($variableX/ValidCode/@CodePart)">...</xsl:for-each>

但是如何在 XSLT 1.0 中做到最好呢?
请注意,我不能使用密钥,因为它是动态确定的变量,而不是输入文件的一部分。

我的输入文件确实包含所有可能的代码部分的列表,如下所示:

<root>
    <CodePart><value>CP1</value></CodePart>
    <CodePart><value>CP2</value></CodePart>
    <CodePart><value>CP3</value></CodePart>
</root>

所以我想到了循环//CodePart/value,以确保初学者的唯一性。但是我需要一些包含条件的 Xpath 表达式

“值出现在所有 $variableX/ValidCode/@CodePart 值的节点集中”

并使用类似的东西

<xsl:for-each select="//CodePart[..condition..]/value">...</xsl:for-each>

是否有我正在寻找的 Xpath 表达式的简单形式?
还是另一种方法更可取?

4

5 回答 5

4

请注意,我不能使用密钥,因为它是动态确定的变量,而不是输入文件的一部分。

这根本不是真的。

这是使用键的简短,简单且最有效的解决方案(顺便说一句,不使用任何条件指令或根本不使用xsl:for-each:)):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:key name="kCodeByPart" match="ValidCode" use="@CodePart"/>

    <xsl:variable name="variableX">
        <ValidCode CodePart="CP1" Code="C1"/>
        <ValidCode CodePart="CP2" Code="C2"/>
        <ValidCode CodePart="CP1" Code="C3"/>
        <ValidCode CodePart="CP2" Code="C4"/>
        <ValidCode CodePart="CP2" Code="C5"/>
    </xsl:variable>

    <xsl:variable name="vCodes" select="ext:node-set($variableX)/*"/>


 <xsl:template match="/">
  <xsl:apply-templates select=
  "$vCodes[generate-id()
          =
           generate-id(key('kCodeByPart', @CodePart)[1])
           ]"/>
 </xsl:template>

 <xsl:template match="ValidCode">

  Code Part: <xsl:value-of select="@CodePart"/>
    Codes: <xsl:apply-templates select=
      "key('kCodeByPart', @CodePart)/@Code"/>
 </xsl:template>

 <xsl:template match="@Code">
   <xsl:value-of select="concat(., ' ')"/>
 </xsl:template>
</xsl:stylesheet>

当将此转换应用于任何 XML 文档(未使用)时,将产生所需的正确结果

  Code Part: CP1
    Codes: C1 C3 

  Code Part: CP2
    Codes: C2 C4 C5 

说明

W3C XSLT 1.0 建议中所述:

"The key can be used to retrieve a key from a document other than the document containing the context node."

在提供的解决方案中,我们甚至没有使用这种可能性——上下文节点和检索到的键都来自同一个文档——包含在$vCodes.

再次在那里,指定一个密钥可以用于多个文档:

“样式表使用 xsl:key 元素为每个文档声明一组键。”

简单来说:索引是在当前文档(包含上下文(当前)节点的文档)上执行的。

于 2012-06-20T02:57:05.707 回答
3

在 XPath 1.0 中,您可以distinct-values通过查找前面具有相同值的同级来模拟该函数。

我已将您的第一个文档简化为这样,以便更容易复制(甚至不使用 XSLT):

<?xml version="1.0"?>
<root>
    <ValidCode CodePart="CP1" Code="C1"/>
    <ValidCode CodePart="CP2" Code="C2"/>
    <ValidCode CodePart="CP1" Code="C3"/>
    <ValidCode CodePart="CP2" Code="C4"/>
    <ValidCode CodePart="CP2" Code="C5"/>
    <ValidCode CodePart="CP3" Code="C5"/>
</root>

然后,下面的 XPath 表达式将只选择CodePart文档中出现的每个值的一个属性:

//ValidCode/@CodePart[not(../preceding-sibling::ValidCode/@CodePart = .)]

因此,选择属性的条件CodePart是元素的前面没有同级<ValidCode>元素的CodePart属性与当前检查(选择)的属性具有相同的值CodePart

于 2012-06-19T18:35:15.277 回答
2

我不认为你可以直接使用 XPath - 但你应该能够只检查这样的有效值:

<xsl:for-each select="//CodePart/value">
  <xsl:variable name="CodePart" select="."/>
  <xsl:if test="$variableX/ValidCode[@CodePart=$CodePart]">
    . . .
  </xsl:if>
</xsl:for-each> 

或更简单地说(感谢评论):

<xsl:for-each select="//CodePart/value">
  <xsl:if test="$variableX/ValidCode[@CodePart=current()]">
    . . .
  </xsl:if>
</xsl:for-each> 

如果$variableX不是节点集而是 XML 片段,则需要将其转换为节点集 - 这取决于实现,使用 Microsoft 处理器:

<xsl:for-each select="//CodePart/value" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
  <xsl:if test="msxsl:node-set($variableX)/ValidCode[@CodePart=current()]">
    . . .
  </xsl:if>
</xsl:for-each> 
于 2012-06-19T18:11:29.763 回答
2

解决这个问题的一种方法(没有密钥)是排序和测试preceding-sibling

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">

    <xsl:variable name="variableX">
        <ValidCode CodePart="CP1" Code="C1"/>
        <ValidCode CodePart="CP2" Code="C2"/>
        <ValidCode CodePart="CP1" Code="C3"/>
        <ValidCode CodePart="CP2" Code="C4"/>
        <ValidCode CodePart="CP2" Code="C5"/>
     </xsl:variable>

     <xsl:variable name="refvarX" select="msxsl:node-set($variableX)" />

     <xsl:template match="/">
         <root>
             <xsl:for-each select="$refvarX/ValidCode">
                 <xsl:sort select="./@CodePart" data-type="text" />
                 <xsl:if test="not(./@CodePart = preceding-sibling::ValidCode/@CodePart)">
                     <cp>
                         <xsl:value-of select="./@CodePart" />
                     </cp>
                 </xsl:if>
             </xsl:for-each>
         </root>
    </xsl:template>

</xsl:stylesheet>
于 2012-06-19T18:34:05.180 回答
0

使用node-set()函数,例如撒克逊:

<xsl:template match="/">
  <xsl:apply-templates select="root/CodePart/value[
   . = saxon:node-set($variableX)/ValidCode/@CodePart]/>
</xsl:template>

<xsl:template match="value">
  <!-- template for distinct value -->
</xsl:template>
于 2012-06-19T21:34:36.017 回答