开发者

(XSLT, Code optimization) How to output the nodes refering to the value of sibling-nodes ..?

开发者 https://www.devze.com 2022-12-21 00:16 出处:网络
I am transforming XML to XML using XSLT, The objective is to read the value of tag <node1>, if it is null then it must be assigned with the value of <node2>, if incase <node2>, is al

I am transforming XML to XML using XSLT, The objective is to read the value of tag <node1>, if it is null then it must be assigned with the value of <node2>, if incase <node2>, is also null, then default text "Default" has to be assigned .. to both tags ..

EDIT: If <node2>is null and <node1> isn't .. then the code shouldn't update <node2> with 'Default' text but it has to be transformed as it is ..

This is the test XML I am trying with:

<root>
    <node1></node1>
    <node2></node2>开发者_JAVA百科
  <parent>
    <node1>data1</node1>
    <node2></node2>
  </parent>
  <parent>
    <node1></node1>
    <node2>data2</node2>
  </parent>
  <parent>
    <node1>data1</node1>
    <node2>data2</node2>
  </parent>
</root>

And this the XSLT code I have designed:

   <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[.='']"/>

Though my code works, I am not happy with its bulkiness of the code .. Is there anyway to get rid of redundant (if any) lines .... And is there any alternative to use 2 templates to accomplish this (namely template1 and template2), is it possible to reduce the number of templates?


I. XSLT 1.0 solution:

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

It is shorter and simpler than the current solutions -- 7 lines less and, more importantly, one template less than the currently selected solution.

Even more importantly, this solution is completely declarative and push-style -- no calling of named templates and the only <xsl:apply-templates> is in the identity rule.

II. XSLT 2.0 solution

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

Using the power of XPath 2.0 sequences this solution is quite shorter than the XSLT 1.0 solution.

Something similar is not possible in XSLT 1.0 (such as selecting the first of the union of two nodes without specifying predicates to make the two nodes mutually exclusive), because the node with the default text and the node1/node2 nodes belong to different documents and, as we know well, node ordering between nodes of different documents is implementation specific and is not guaranteed/prescribed.

This solution is completely declarative (no if/then/else) and completely push style: the only <xsl:apply-templates> is in the identity rule.


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


I have modified Tomalak's answer and accomplished the requirement ..
As I have mentioned in my question, This code passes node2 as null (if it is null) if the sibling node1 isn't null (and also if there is no sibling node1) ..

This code finally became an alternative to the one I have posted in my Q .. (I don't say it is perfect enough .. but I am glad that I could try .. :-)
And this code is more efficient than mine by some 10-20 msecs .. :-)

Here it goes ..

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


Using XSLT 2.0 I would do this, but yours is easier to read anyway.

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

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号