Home > Back-end >  Change node location with xslt 1.0 to "sibling" node at same position without attributes
Change node location with xslt 1.0 to "sibling" node at same position without attributes

Time:08-28

How can I change the location of node "//seps/ink[x]/c" to the same position in node "inks/ink[x]". I only found solutions using attributes.

Source:

<?xml version="1.0" encoding="UTF-16"?>
<root>
 <inks>
  <ink>
   <A>A1</A>
   <B>B1</B>
 </ink>
 <ink>
  <A>A2</A>
  <B>B2</B>
 </ink>
</inks>
<seps>
 <ink>
  <C>C1</C>
 </ink>
 <ink>
  <C>C2</C>
 </ink>
</seps>
</root>

Target:

<?xml version="1.0" encoding="UTF-16"?>
<root>   
 <inks>
  <ink>
   <A>A1</A>
   <B>B1</B>
   <C>C1</C>
  </ink>
  <ink>
   <A>A2</A>
   <B>B2</B>
   <C>C2</C>
  </ink>
 </inks>
</root>

Thanks a lot for helpful solution.

CodePudding user response:

Well, if you process root/inks/ink with e.g. xsl:apply-templates (or with the identity transformation but xsl:strip-space elements="*"/> in place), you can either store <xsl:variable name="pos" select="position()"/> and use that variable as an index in the other path e.g. /root/seps/ink[$pos]/C or in general, if you don't want to rely on processing those ink elements explicitly, in the template matching e.g. <xsl:template match="root/inks/ink">, you can always compute the position with xsl:number in a variable e.g. <xsl:variable name="pos"><xsl:number/></xsl:variable>. Then you can use that variable with e.g. /root/seps/ink[position() = $pos]/C.

CodePudding user response:

In XSLT 3.0, you could use xsl:merge, or the fn:for-each-pair function.

But 1.0 is more limited, and the only way to merge two sequences is along the lines

<xsl:variable name="root" select="."/>
<xsl:for-each select="inks/ink">
  <xsl:variable name="p" select="position()"/>
  <xsl:copy-of select="*"/>
  <xsl:copy-of select="$root/seps/ink[$p]"/>
</xsl:for-each>

CodePudding user response:

This transformation:

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

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="inks/ink">
     <xsl:variable name="vPos" select="position()"/>
     <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
      <xsl:apply-templates select="/*/seps/ink[$vPos]/C"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="seps"/>
</xsl:stylesheet>

When applied on the provided XML document:

<root>
 <inks>
  <ink>
   <A>A1</A>
   <B>B1</B>
 </ink>
 <ink>
  <A>A2</A>
  <B>B2</B>
 </ink>
</inks>
<seps>
 <ink>
  <C>C1</C>
 </ink>
 <ink>
  <C>C2</C>
 </ink>
</seps>
</root>

Produces the wanted, correct result:

<root>
   <inks>
      <ink>
         <A>A1</A>
         <B>B1</B>
         <C>C1</C>
      </ink>
      <ink>
         <A>A2</A>
         <B>B2</B>
         <C>C2</C>
      </ink>
   </inks>
</root>

Explanation:

  1. The identity rule copies every node "as-is" if a higher priority template doesn't match this node.

  2. Subtrees of elements of type inks/ink are processed by the code as in the identity rule, but extended with one final application of it to the corresponding sep/ink/C element. The current position of the matched inks/ink (current()) element, stored in a variable, is used to select the wanted corresponding seps/ink element

  3. Processing of the subtree with root the seps element is suppressed (effectively "deleting" it from the result of the transformation, by an empty matching template

  • Related