Sowmiya
Sowmiya

Reputation: 25

XSLT transformation to dynamically add empty elements based on the maximum count of element present

Following XML is my Input

<?xml version="1.0" encoding="UTF-8"?>
<Item>
   <Description>
      <BB>Main1</BB>
      <CC>
         <DD>Content1</DD>
         <EE>
            <EE1>Answer1</EE1>
         </EE>
      </CC>
   </Description>
   <Description>
      <BB>Main2</BB>
      <CC>
         <DD>Content2</DD>
         <EE>
            <EE1>Answer2.1</EE1>
         </EE>
         <EE>
            <EE1>Answer2.2</EE1>
         </EE>
         <EE>
            <EE1>Answer2.3</EE1>
         </EE>
      </CC>
   </Description>
   <Description>
      <BB>Main3</BB>
      <CC>
         <DD>Content3</DD>
         <EE>
            <EE1>Answer3</EE1>
         </EE>
      </CC>
   </Description>
</Item>

count of node EE should be equal in all CC parent tag (this count is the maximum of EE within the CC)

In my example the maximum count of EE tag is 3. Is it possible to dynamically add EE elements by getting this count? If there is no value, then i will populate with non breking white space. so after transformation my xml should be

<?xml version="1.0" encoding="UTF-8"?>
<Item>
   <Description>
      <BB>Main1</BB>
      <CC>
         <DD>Content1</DD>
         <EE>
            <EE1>Answer1</EE1>
         </EE>
         <EE>
            <EE2>       </EE2>
         </EE>
         <EE>
            <EE3>       </EE3>
         </EE>
      </CC>
   </Description>
   <Description>
      <BB>Main2</BB>
      <CC>
         <DD>Content2</DD>
         <EE>
            <EE1>Answer2.1</EE1>
         </EE>
         <EE>
            <EE2>Answer2.2</EE2>
         </EE>
         <EE>
            <EE3>Answer2.3</EE3>
         </EE>
      </CC>
   </Description>
   <Description>
      <BB>Main3</BB>
      <CC>
         <DD>Content3</DD>
         <EE>
            <EE1>Answer3</EE1>
         </EE>
         <EE>
            <EE2>       </EE2>
         </EE>
         <EE>
            <EE3>        </EE3>
         </EE>
      </CC>
   </Description>
</Item>

Any help is appreciated. Thanks in advance.

Upvotes: 1

Views: 780

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 117073

Try it this way:

XSLT 1.0

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

<xsl:variable name="max-ee">
    <xsl:for-each select="/Item/Description/CC">
        <xsl:sort select="count(EE)" data-type="number" order="descending"/>
        <xsl:if test="position() = 1">
            <xsl:value-of select="count(EE)"/>
        </xsl:if>
    </xsl:for-each>
</xsl:variable>

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

<xsl:template match="CC">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
        <xsl:call-template name="gen-ee">
            <xsl:with-param name="i" select="count(EE)"/>
        </xsl:call-template>
    </xsl:copy>
</xsl:template>

<xsl:template name="gen-ee">
    <xsl:param name="i" />
    <xsl:if test="$i &lt; $max-ee">
        <EE/>
        <xsl:call-template name="gen-ee">
            <xsl:with-param name="i" select="$i +1"/>
        </xsl:call-template>        
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

Added:

To produce the output in your modified question, change this line in the last template:

<EE/>

to:

<EE>
    <xsl:element name="EE{$i + 1}">&#160;</xsl:element>
</EE>

Note that the existing EE1 elements will not be renumbered - although that too would be rather trivial to do by adding a template:

<xsl:template match="EE1">
    <xsl:element name="EE{count(../preceding-sibling::EE) + 1}">
        <xsl:apply-templates />
   </xsl:element>
</xsl:template>

Upvotes: 3

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243529

Here is one correct solution:

<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:variable name="vMax">
    <xsl:for-each select="/*/*/CC">
      <xsl:sort select="count(EE)" data-type="number" order="descending"/>
      <xsl:if test="position() = 1">
        <xsl:value-of select="count(EE)"/>
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="vNodes" select="(//*)[not(position() > $vMax)]"/>

  <xsl:variable name="vEmpty" select="'&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'"/>

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

  <xsl:template match="CC">
    <xsl:copy>
      <xsl:apply-templates/>
      <xsl:apply-templates select=
          "$vNodes[not(position() > $vMax - count(current()/EE))]" mode="picture">
        <xsl:with-param name="pStart" select="count(EE)"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="CC/EE/EE1">
    <xsl:element name="EE{count(../preceding-sibling::EE)+1}">
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="*" mode="picture">
    <xsl:param name="pStart" select="0"/>
    <EE>
      <xsl:element 
        name="EE{$pStart + position()}"><xsl:value-of select="$vEmpty"/></xsl:element>
    </EE>
  </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<Item>
   <Description>
      <BB>Main1</BB>
      <CC>
         <DD>Content1</DD>
         <EE>
            <EE1>Answer1</EE1>
         </EE>
      </CC>
   </Description>
   <Description>
      <BB>Main2</BB>
      <CC>
         <DD>Content2</DD>
         <EE>
            <EE1>Answer2.1</EE1>
         </EE>
         <EE>
            <EE1>Answer2.2</EE1>
         </EE>
         <EE>
            <EE1>Answer2.3</EE1>
         </EE>
      </CC>
   </Description>
   <Description>
      <BB>Main3</BB>
      <CC>
         <DD>Content3</DD>
         <EE>
            <EE1>Answer3</EE1>
         </EE>
      </CC>
   </Description>
</Item>

the exactly-wanted, correct result is produced:

<Item>
   <Description>
      <BB>Main1</BB>
      <CC>
         <DD>Content1</DD>
         <EE>
            <EE1>Answer1</EE1>
         </EE>
         <EE>
            <EE2>       </EE2>
         </EE>
         <EE>
            <EE3>       </EE3>
         </EE>
      </CC>
   </Description>
   <Description>
      <BB>Main2</BB>
      <CC>
         <DD>Content2</DD>
         <EE>
            <EE1>Answer2.1</EE1>
         </EE>
         <EE>
            <EE2>Answer2.2</EE2>
         </EE>
         <EE>
            <EE3>Answer2.3</EE3>
         </EE>
      </CC>
   </Description>
   <Description>
      <BB>Main3</BB>
      <CC>
         <DD>Content3</DD>
         <EE>
            <EE1>Answer3</EE1>
         </EE>
         <EE>
            <EE2>       </EE2>
         </EE>
         <EE>
            <EE3>       </EE3>
         </EE>
      </CC>
   </Description>
</Item>

Upvotes: 0

Related Questions