5
votes

I have created an XSLT and i was wondering how it is possible to copy all nodes between one set of tags, and add another tag at the bottom. I have created the XSLT that has all the logic for determining which tag to add, and what it should be called. However the issue i am now getting is that i can not copy all the other tags across too. Below are the files in question:

XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:output method="xml" indent="yes"/>

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

    <xsl:template match="/csvImportSchema">
        <csvImportSchema>
            <xsl:for-each select="payload">
                <payload>
                    <xsl:copy-of select="@*"/>
                    <xsl:variable name="ean">
                        <xsl:value-of select="ean"/>
                    </xsl:variable>
                    <xsl:for-each select="../product">
                        <xsl:if test="ean = $ean">
                            <productId><xsl:value-of select="article"/></productId>
                        </xsl:if>
                    </xsl:for-each>
                </payload>
            </xsl:for-each>
        </csvImportSchema>
    </xsl:template>

</xsl:stylesheet>

INPUT

<?xml version="1.0" encoding="UTF-8"?>
<csvImportSchema>
    <payload>
        <test>1</test>
        <test2>2</test2>
        <test3>3</test3>
        <ean>1111111111</ean>
        <productId/>
    </payload>
    <product>
        <article>722619</article>
        <ean>1111111111</ean>
    </product>
</csvImportSchema>

CURRENT OUTPUT

<?xml version="1.0" encoding="utf-8"?>
<csvImportSchema>
    <payload>
        <productId>722619</productId>
    </payload>
</csvImportSchema>

DESIRED OUTPUT

<?xml version="1.0" encoding="UTF-8"?>
<csvImportSchema>
    <payload>
        <test>1</test>
        <test2>2</test2>
        <test3>3</test3>
        <ean>1111111111</ean>
        <productId>722619</productId>
    </payload>
</csvImportSchema>
4

4 Answers

9
votes

This short and simple 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="productId">
  <productId>
    <xsl:value-of select="../../product/article"/>
  </productId>
 </xsl:template>
 <xsl:template match="product"/>
</xsl:stylesheet>

when applied on the provided XML document:

<csvImportSchema>
    <payload>
        <test>1</test>
        <test2>2</test2>
        <test3>3</test3>
        <ean>1111111111</ean>
        <productId/>
    </payload>
    <product>
        <article>722619</article>
        <ean>1111111111</ean>
    </product>
</csvImportSchema>

produces the wanted, correct result:

<csvImportSchema>
   <payload>
      <test>1</test>
      <test2>2</test2>
      <test3>3</test3>
      <ean>1111111111</ean>
      <productId>722619</productId>
   </payload>
</csvImportSchema>

Explanation:

  1. The identity rule copies "as-is" every node for which it is selected for execution.

  2. An overriding template matching product "deletes" this element from the output (by its empty body).

  3. Another overriding template matches productId and generates this element with a text-node child taken from product/article.

2
votes

One observation on your code. Don't use this:

<xsl:variable name="ean">
    <xsl:value-of select="../ean"/>
</xsl:variable>

when you could write this:

<xsl:variable name="ean" select="../ean"/>

Not only is it verbose, it's also incredibly inefficient: instead of binding $ean to an existing node, you are extracting the string value of an existing node, forming a text node with that string value, creating a new XML document tree, and adding this text node to the content of this new document. (I once got a stylesheet to run 3 times faster by eliminating this horrible construct.)

1
votes

It should be as simple as changing your payload xsl:copy-of select="@*"/ to

<xsl:copy-of select="*[local-name() != 'productId'] | @*"/>

i.e. copy everything, except productId, because you build this up manually.

This gives the output you've required

<?xml version="1.0" encoding="utf-8"?>
<csvImportSchema>
  <payload>
    <test>1</test>
    <test2>2</test2>
    <test3>3</test3>
    <ean>1111111111</ean>
    <productId>722619</productId>
  </payload>
</csvImportSchema>
1
votes

This XSLT should do the job and uses way more COPY tags and templates. Maybe don't do everything in one xsl:template (my opinion).

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:output method="xml" indent="yes"/>

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

    <xsl:template match="/csvImportSchema">
        <xsl:copy>
            <xsl:apply-templates select="*"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="csvImportSchema/payload/productId">
        <xsl:variable name="ean">
            <xsl:value-of select="../ean"/>
        </xsl:variable>
        <xsl:for-each select="../../product">
            <xsl:if test="ean = $ean">
                <productId><xsl:value-of select="article"/></productId>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="csvImportSchema/product">
        <!-- do not copy -->
    </xsl:template>

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