Adding a child attribute to the parent element in xslt 1.0

543 Views Asked by At

I have multiple elements that contain an uniqueId(generated and stored in a variable). I have recursively added the element(object class=Bundle with unique id ) with the xsl as follows

<xsl:template match="visualChildren">
    <object class="Set" >
        <installChildren>
        <xsl:call-template name="Bundle">
            <xsl:with-param name="i" select="1"/>
            <xsl:with-param name="limit" select="4" />
        </xsl:call-template>
        </installChildren>
    </object>
</xsl:template>

<xsl:template name="Bundle">
     <xsl:param name="i"/>
     <xsl:param name="limit"/>

    <xsl:variable name="BundleObjId">
         <xsl:value-of select="php:function('GenerateObjId')"/>
    </xsl:variable>

    <xsl:if test="$i &lt;= $limit">
        <object class="Bundle" objectID="{$BundleObjId}">
             <property></property>
         </object>
    </xsl:if>
    <xsl:call-template name="Bundle">
        <xsl:with-param name="i" select="$i+1"/>
        <xsl:with-param name="limit" select="$limit" />
    </xsl:call-template>
</xsl:template> 

This produces the following result

<visualChildren>
    <object class="Set" >
        <installChildren>
            <object class="Bundle" objectID="33110emc908m">
                <property></property>
            </object>
            <object class="Bundle" objectID="43110emc9667m">
                <property></property>
            </object>
        </installChildren>
    </object>
</visualChildren>

Now I need to populate the BundleObjId as the parent's sibling so that it gets referenced.

Required output is:

<visualChildren>
    <object class="Set" >
        <installChildren>
            <object class="Bundle" objectID="33110emc908m">
                <property></property>
            </object>
            <object class="Bundle" objectID="43110emc9667m">
                 <property></property>
            </object>
        </installChildren>
    </object>
    <object RefId=33110emc908m /> 
    <object RefId=43110emc9667m /> 
 </visualChildren>

Please help me achieve this with xslt 1.0 by adding to the existing xsl.

2

There are 2 best solutions below

0
On BEST ANSWER

It's difficult to provide an answer without seeing the input. I believe you need to do something like the following:

XSLT 1.0

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

<xsl:template match="visualChildren">
    <xsl:variable name="bundles">
        <xsl:call-template name="create-bundles">
            <xsl:with-param name="n" select="4" />
        </xsl:call-template>
    </xsl:variable>

    <visualChildren>
        <object class="Set" >
            <installChildren>
                <xsl:for-each select="exsl:node-set($bundles)/object">
                    <object class="Bundle" objectID="{@RefId}">
                        <property/>
                    </object>
                </xsl:for-each>
            </installChildren>
        </object>
        <xsl:copy-of select="$bundles"/>
    </visualChildren>
</xsl:template>

<xsl:template name="create-bundles">
    <xsl:param name="n"/>
    <xsl:if test="$n > 0">
        <object RefId="{php:function('GenerateObjId')}"/>
        <xsl:call-template name="create-bundles">
            <xsl:with-param name="n" select="$n - 1" />
        </xsl:call-template>
    </xsl:if>
</xsl:template> 

</xsl:stylesheet>

Given an artificial input of:

<?xml version="1.0" encoding="UTF-8"?>
<visualChildren/>

the result will be something like:

<?xml version="1.0" encoding="UTF-8"?>
<visualChildren>
  <object class="Set">
    <installChildren>
      <object class="Bundle" objectID="123456">
        <property/>
      </object>
      <object class="Bundle" objectID="987654">
        <property/>
      </object>
      <object class="Bundle" objectID="456321">
        <property/>
      </object>
      <object class="Bundle" objectID="789456">
        <property/>
      </object>
    </installChildren>
  </object>
  <object RefId="123456"/>
  <object RefId="987654"/>
  <object RefId="456321"/>
  <object RefId="789456"/>
</visualChildren>
7
On

The problem is that you iterate from 1 to N, instead of iterating over a nodeset. And within that iteration, you call a function with a return value that does not depend on the context (apparently, it is generating a random string). So you do not have any chance to repeat the same ID in 2 different iterations. And in XSLT 1.0, you cannot generate an intermediary list of IDs, then iterate over that list. I see 3 solutions:

  • if php:function('GenerateObjId') can be replaced by generate-id(), then iterate over nodes instead, and use the latter (it will generate the same ID for 2 identical nodes)

  • if your processor supports exsl:node-set() (most do, documentation here), generate the list of IDs you need, as a simple XML elements list with a single one parent element, store it in a variable, then use exsl:node-set() to iterate over (twice)

  • if you can, switch to XSLT 2.0 (where you can apply the previous solution, without the need for exsl:node-set() as, in XSLT 2.0, you can use what are result trees in XSLT 1.0 as input to XPath expressions)

Last "solution" (in case none of the above is possible):

  • apply a second transform to the result of you first transform, based on the Modified Identity Transform pattern:

``` <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

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

   <xsl:template match="object[@class = 'Set']">
      <xsl:copy-of select="."/>
      <xsl:for-each select="installChildren/object">
         <object RefId="{ @objectID }"/>
      </xsl:for-each>
   </xsl:template>

</xsl:stylesheet>

```