Serve Laurijssen
Serve Laurijssen

Reputation: 9733

xsl:copy-of and xsl:attribute in one template

I've got quite a complex question (IMO) so I hope it will be clear what I mean.

Consider the XML with the following structure.

<products>
    <product exportName="XPOZER_90X120">
      <product-options>
        <option productOptionRef="xpozerFrame">
          <plan>
            <period startDate="2015-10-01">
              <price name="true" netBasePrice="34"/>
              <price name="false" netBasePrice="0"/>
            </period>
          </plan>
        </option>
      </product-options>
    </product>
</products>

This product has two "price" child nodes. It needs to be transformed into one "product" node for every "price" childnode there is and at the same time exportName attribute must be changed into a generated value. The rest of the nodes stay the same.

The desired output:

<products>
    <product exportName="XPOZER_WITHFRAME90X120">
      <product-options>
        <option productOptionRef="xpozerFrame">
          <plan>
            <period startDate="2015-10-01">
              <price name="true" netBasePrice="34"/>
              <price name="false" netBasePrice="0"/>
            </period>
          </plan>
        </option>
      </product-options>
    </product>
    <product exportName="XPOZER_NOFRAME90X120">
      <product-options>
        <option productOptionRef="xpozerFrame">
          <plan>
            <period startDate="2015-10-01">
              <price name="true" netBasePrice="34"/>
              <price name="false" netBasePrice="0"/>
            </period>
          </plan>
        </option>
      </product-options>
    </product>
</products>

exportname becomes 'XPOZER_NOFRAME90X120' and 'XPOZER_WITHFRAME90X120' and they are calculated with the document function in the same xslt and then doing some transformations.

I've got the product node transformations working but I'm stuck on changing the exportName attribute. The value that it must become is working already but I dont know how to actually change the exportName attribute while doing a copy.

This the xslt so far

    <xsl:stylesheet version="1.0" 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        xmlns:str="http://exslt.org/strings"
        xmlns:input="urn:input-variables"
        xmlns:my="my:my">
          <my:values>
            <xpozerFramefalse>NOFRAME</xpozerFramefalse>
            <xpozerFrametrue>WITHFRAME</xpozerFrametrue>
            <XPOZER_90X120>XPOZER_[xpozerFrame]90X120</XPOZER_90X120>
          </my:values>

  <xsl:template name="string-replace-all">
    <xsl:param name="text" />
    <xsl:param name="replace" />
    <xsl:param name="by" />
    <xsl:choose>
        <xsl:when test="contains($text, $replace)">
            <xsl:value-of select="substring-before($text,$replace)" />
            <xsl:value-of select="$by" />
            <xsl:call-template name="string-replace-all">
                <xsl:with-param name="text" select="substring-after($text,$replace)" />
                <xsl:with-param name="replace" select="$replace" />
                <xsl:with-param name="by" select="$by" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$text" />
        </xsl:otherwise>
    </xsl:choose>
  </xsl:template>


              <xsl:template match="/">
                <xsl:apply-templates select="products/product[@exportName='XPOZER_90X120']" />
              </xsl:template>  

              <xsl:template match="product[product-options/option/plan/period/price]">
                <xsl:for-each select="product-options/option/plan/period/price">
                    <xsl:variable name="vNewExportName">
                      <xsl:call-template name="priceNode" />
                    </xsl:variable>         
                    <xsl:copy-of select="../../../../.." />
                </xsl:for-each>
              </xsl:template>

              <xsl:template name="priceNode">

               <xsl:variable name="vProductOptionRef" select="../../../@productOptionRef" />
                <xsl:variable name="vSelector" select="concat($vProductOptionRef, @name)" />
                <xsl:variable name="vExportname" select="../../../../../@exportName" />
                <xsl:variable name="vNewExportName">
                    <xsl:call-template name="string-replace-all">
                        <xsl:with-param name="text" select="document('')//my:values/*[name()=$vExportname]" />
                        <xsl:with-param name="replace" select="concat(concat('[', $vProductOptionRef), ']')" />
                        <xsl:with-param name="by" select="document('')//my:values/*[name()=$vSelector]" />
                    </xsl:call-template>
                </xsl:variable>
                <xsl:value-of select="$vNewExportName" />
              </xsl:template>
    </xsl:stylesheet>

So the for-each with copy-of takes care of copying the node twice into the transformed XML and the value that exportname should be changed into is found correctly by retrieving the values from "my:values". But I really don't know how to apply the value to the actual attribute.

Can anybody help me with this? is this even possible without falling back on coding?

Upvotes: 1

Views: 321

Answers (1)

michael.hor257k
michael.hor257k

Reputation: 116959

Edited in response to clarifications:

As far as I can tell, you want to do something like this:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my"
exclude-result-prefixes="my">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<my:values>
    <xpozerFramefalse>NOFRAME</xpozerFramefalse>
    <xpozerFrametrue>WITHFRAME</xpozerFrametrue>
    <XPOZER_90X120>XPOZER_[xpozerFrame]90X120</XPOZER_90X120>
</my:values>

<xsl:template match="/products">
    <xsl:copy>
        <xsl:apply-templates/>
    </xsl:copy>
</xsl:template>  

<xsl:template match="product[product-options]">
    <xsl:for-each select="product-options/option/plan/period/price">
        <xsl:variable name="exportName" select="ancestor::product/@exportName" />
        <xsl:variable name="productOptionRef" select="ancestor::option/@productOptionRef" />
        <xsl:variable name="lookup-values" select="document('')//my:values/*" />
        <xsl:variable name="replace-src" select="concat($productOptionRef, @name)"/>

        <xsl:variable name="newExportName">
            <xsl:call-template name="replace">
                <xsl:with-param name="string" select="$lookup-values[name()=$exportName]"/>
                <xsl:with-param name="search-string" select="concat('[', $productOptionRef, ']')"/>
                <xsl:with-param name="replace-string" select="$lookup-values[name()=$replace-src]"/>
            </xsl:call-template>
        </xsl:variable>

        <product exportName="{$newExportName}">
            <product-options>
                <option productOptionRef="{$productOptionRef}">
                    <plan>
                        <period startDate="{../@startDate}">
                            <xsl:copy-of select="."/>
                        </period>
                    </plan>
                </option>
          </product-options>
        </product>
    </xsl:for-each>
</xsl:template>

<xsl:template name="replace">
    <xsl:param name="string"/>
    <xsl:param name="search-string"/>
    <xsl:param name="replace-string"/>
    <xsl:choose>
        <xsl:when test="contains($string, $search-string)">
            <xsl:value-of select="substring-before($string, $search-string)"/>
            <xsl:value-of select="$replace-string"/>
            <xsl:call-template name="replace">
                <xsl:with-param name="string" select="substring-after($string, $search-string)"/>
                <xsl:with-param name="search-string" select="$search-string"/>
                <xsl:with-param name="replace-string" select="$replace-string"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$string"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Applied to your input example, the result here will be:

<?xml version="1.0" encoding="UTF-8"?>
<products>
   <product exportName="XPOZER_WITHFRAME90X120">
      <product-options>
         <option productOptionRef="xpozerFrame">
            <plan>
               <period startDate="2015-10-01">
                  <price name="true" netBasePrice="34"/>
               </period>
            </plan>
         </option>
      </product-options>
   </product>
   <product exportName="XPOZER_NOFRAME90X120">
      <product-options>
         <option productOptionRef="xpozerFrame">
            <plan>
               <period startDate="2015-10-01">
                  <price name="false" netBasePrice="0"/>
               </period>
            </plan>
         </option>
      </product-options>
   </product>
</products>

Note: I would suggest using a key to lookup the values from my:values.

Upvotes: 1

Related Questions