(XSLT, 代码优化) 如何输出与兄弟节点的值相关的节点..?

4
我正在使用XSLT将XML转换为XML,目标是读取标记<node1>的值,如果为空,则必须分配<node2>的值,如果<node2>也为空,则必须分配默认文本“Default”到两个标记中。
编辑:如果<node2>为空而<node1>不为空,则代码不应该使用'Default'文本更新<node2>,但它必须按原样转换。
以下是我正在测试的XML:
<root>
    <node1></node1>
    <node2></node2>
  <parent>
    <node1>data1</node1>
    <node2></node2>
  </parent>
  <parent>
    <node1></node1>
    <node2>data2</node2>
  </parent>
  <parent>
    <node1>data1</node1>
    <node2>data2</node2>
  </parent>
</root>

以下是我设计的XSLT代码:

   <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
  <xsl:template name="template1" match="node2[(following-sibling::node1[.='']|preceding-sibling::node1[.=''])]">
    <xsl:choose>
      <xsl:when test=".=''">
        <node1><xsl:text>Default</xsl:text></node1>
        <node2><xsl:text>Default</xsl:text></node2>
      </xsl:when>
      <xsl:otherwise>
        <node1>
          <xsl:value-of select="text()"/>
        </node1>
        <xsl:copy>
          <xsl:apply-templates select="node()"/>
        </xsl:copy>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="template2" match="node1[.='']"/>

虽然我的代码可以工作,但我对它的臃肿感到不满..有没有办法去除多余的行?是否有任何替代方案可以使用2个模板来完成此操作(即template1和template2),是否可能减少模板数量?


我个人很喜欢Tomalak的解决方案,它满足了大部分需求。看起来很酷,技术含量也很高。虽然我可以为我的需求编写代码(可能不是100%有效的代码),但他的代码值得借鉴,所以我接受了。;-) - Rookie Programmer Aravind
我也发布了自己的答案..那会满足需求.. - Rookie Programmer Aravind
我有一个答案,也许值得你关注 :) - Dimitre Novatchev
1
@Dimitre,我已经接受了你的答案.. - Rookie Programmer Aravind
欢迎您——到目前为止,您已经发布了非常有趣的问题。 - Dimitre Novatchev
@Dimitre,谢谢你,我真的感到很高兴.. :-) - Rookie Programmer Aravind
4个回答

3

I. XSLT 1.0 解决方案:

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

    <xsl:variable name="vReplacement">Default</xsl:variable>

       <xsl:variable name="vRep" select=
        "document('')/*/xsl:variable[@name='vReplacement']/text()"/>

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

     <xsl:template match="node1[not(node())] | node2[../node1[not(node())]]">
      <xsl:copy>
          <xsl:copy-of select="../node2/text() | $vRep[not(current()/../node2/text())]"/>
      </xsl:copy>
     </xsl:template>
</xsl:stylesheet>

这个解决方案比当前的解决方案更简短和更简单 -- 7行代码更少,更重要的是,比当前选择的解决方案少了一个模板

更为重要的是,这个解决方案完全是声明式和推送式的 -- 不需要调用命名模板,而且唯一的 <xsl:apply-templates> 在恒等规则中。

II. XSLT 2.0 解决方案

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

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

 <xsl:template match="node1[not(node())] | node2[../node1[not(node())]]">
  <xsl:copy>
      <xsl:sequence select="(../node2/text(), 'Default')[1]"/>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

利用XPath 2.0序列的威力,这个解决方案比XSLT 1.0解决方案要短得多。在XSLT 1.0中不可能有类似的情况(例如选择两个节点的并集中的第一个节点而不指定谓词来使这两个节点互斥),因为默认文本节点和node1/node2节点属于不同的文档,我们知道,不同文档之间的节点排序是实现特定的,并且不能保证或规定。这个解决方案完全是声明式的(没有if/then/else),完全是推送样式:唯一的在身份规则中。

这正是我一直在寻找的.. :-) - Rookie Programmer Aravind
1
@Dimitre,我需要在XSLT 1.0中获得一个小澄清,为什么我们需要声明另一个变量(即$vRep)并将变量$vReplacement的值分配给它。而直接使用$vReplacement会导致错误(!)(我也曾尝试过很久以前),这个错误背后的原因是什么?这是XSLT 1.0的一个漏洞吗? - Rookie Programmer Aravind
1
@infant-programmer:我想将xsl:variable作为节点使用,而不是作为值,因为联合运算符“|”仅在节点上定义。在XSLT 1.0中,对变量的引用仅给出变量的值。不幸的是,如果该值具有嵌套的子元素,则它属于臭名昭著的RTF(结果树片段)类型,其内部无法使用XPath进行操作。有三种方法可以解决这个问题:1.使用xsl:stylesheet的命名空间子节点(具有全局范围)。2.将xsl:variable用作节点(我在这里做出了选择),3.使用xxx:node-set()扩展函数。我的选择:(2) - Dimitre Novatchev
1
@infant-programmer:有关RTF类型的更多信息,请参见http://www.w3.org/TR/xslt#section-Result-Tree-Fragments和http://www.biglist.com/lists/xsl-list/archives/200304/msg01234.html。 - Dimitre Novatchev

2
<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:template match="node() | @*">
    <xsl:copy>
      <xsl:apply-templates select="node() | @*" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="node1[.=''] | node2[.='']">
    <xsl:copy>
      <xsl:call-template name="GetOwnValue" />
    </xsl:copy>
  </xsl:template>

  <xsl:template name="GetOwnValue">
    <xsl:variable name="node2" select="following-sibling::node2[1]" />
    <xsl:choose>
      <xsl:when test="$node2 != ''">
        <xsl:value-of select="$node2" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>Default</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

@Tomalak,如果node1不为null而node2为null,则无需更新node2.. node2必须被转换为null本身..您能否请更新答案以包含此条件?我尝试了但失败了..谢谢.. - Rookie Programmer Aravind
你应该在问题中说明这个要求。 - Tomalak
在我的代码中,除非我明确指定使用默认文本更新<node2>,否则它不会这样做。但在你的代码中,方法是相反的。因此,我疏忽地明确编写了该要求。 - Rookie Programmer Aravind
我知道...但我喜欢这个解决方案,因为它满足了我的大部分需求,对我来说已经足够了。嗯,更新<node2/>并没有什么坏处,但我只是想知道代码的可能性 ;) - Rookie Programmer Aravind
我有一个答案,也许值得你关注 :) - Dimitre Novatchev

2
我修改了Tomalak的答案并完成了要求...
正如我在问题中提到的那样,如果兄弟节点node1不为null(且没有兄弟节点node1),则此代码将node2作为null传递(如果它为null)...

这段代码最终成为了我在Q中发布的代码的替代品...(我不说它足够完美...但我很高兴我能尝试...:-)
这段代码比我的代码更有效率,快了10-20毫秒... :-)

就是这样..

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

  <xsl:template match="node1[.=''] | node2[.='']">
    <xsl:copy>
      <xsl:call-template name="GetOwnValue">
        <xsl:with-param name="node">
          <xsl:value-of select="name()"/>
        </xsl:with-param>
      </xsl:call-template>
    </xsl:copy>
  </xsl:template>

  <xsl:template name="GetOwnValue">
    <xsl:param name="node"/>
    <xsl:variable name="node2" select="following-sibling::node2[1]|preceding-sibling::node2[1]" />
    <xsl:variable name="node1" select="following-sibling::node1[1]|preceding-sibling::node1[1]" />
     <xsl:choose>
      <xsl:when test="$node2 != ''">
          <xsl:value-of select="$node2" />
      </xsl:when>
       <xsl:when test="$node!='node1' and ($node1!='' or not(following-sibling::node1[1]|preceding-sibling::node1[1]))"/>
      <xsl:otherwise>
          <xsl:text>Default</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

0

使用XSLT 2.0我可以这样做,但是你的方式更易读。

<xsl:template match="node1[.='']">
    <xsl:copy>
        <xsl:value-of select="if (following-sibling::node2[.!='']) then following-sibling::node2[.!=''] else if (preceding-sibling::node2[.!='']) then preceding-sibling::node2[.!=''] else 'Default'"/>
    </xsl:copy>
</xsl:template>
<xsl:template match="node2[.='']">
    <xsl:copy>
        <xsl:value-of select="if (following-sibling::node1[.!='']) then '' else if (preceding-sibling::node1[.!='']) then '' else 'Default'"/>
    </xsl:copy>
</xsl:template>

我更喜欢 XSLT 1.0.. 因为微软产品不支持 xslt 2.0.. 不管怎样,谢谢回复.. :-) - Rookie Programmer Aravind

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接