Search code examples
xsltsaxon

XSLT injects text between nodes


Given input file

<propertyDescriptor>
  <name repeat="1">enable_decline_rule</name>
  <description>text</description>
  <type>BOOLEAN</type>
  <defaultValue>false</defaultValue>
  <dependentPropertyDescriptors>
    <dependentPropertyDescriptor>
      <value>true</value>
      <propertyDescriptor>
        <name repeat="1">decline_rule_identifier</name>
        <description>This is some random text.</description>
        <type>STRING</type>
      </propertyDescriptor>
    </dependentPropertyDescriptor>
  </dependentPropertyDescriptors>
</propertyDescriptor>

and stylesheet

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

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

    <!-- match the tree that needs to be repeated -->
    <xsl:template match="propertyDescriptor[name='enable_decline_rule']">
        <xsl:call-template name="block-generator">
            <xsl:with-param name="N" select="1"/>
        </xsl:call-template>
    </xsl:template>

    <xsl:template name="block-generator">
        <xsl:param name="N"/>
        <xsl:param name="i" select="1"/>
        <xsl:if test="$N >= $i">
            <!-- generate a block -->
            <xsl:copy>
                <xsl:call-template name="new-descriptor">
                    <xsl:with-param name="N" select="$i"/>
                </xsl:call-template>
            </xsl:copy>
            <!-- recursive call -->
            <xsl:call-template name="block-generator">
                <xsl:with-param name="N" select="$N"/>
                <xsl:with-param name="i" select="$i + 1"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

    <xsl:template name="new-descriptor">
        <xsl:param name="N"/>
        <xsl:for-each select="node()">
            <xsl:call-template name="append_name">
                <xsl:with-param name="N" select="$N"/>
            </xsl:call-template>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="append_name" >
        <xsl:param name="N"/>
        <xsl:choose>
            <!-- tags with a repeat attribute get a _N -->
            <xsl:when test="$N >= 1 and @repeat">
                <name>
                    <xsl:value-of select="text()"/>_<xsl:value-of select="$N"/>
                </name>
            </xsl:when>
            <xsl:when test="$N >= 1">
                <xsl:copy>
                    <xsl:value-of select="node()"/>
                    <xsl:for-each select="./*">
                        <xsl:call-template name="append_name">
                            <xsl:with-param name="N" select="$N"/>
                        </xsl:call-template>
                    </xsl:for-each>
                </xsl:copy>
            </xsl:when>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

I get this output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<propertyDescriptor>
    <name>enable_decline_rule_1</name>
    <description>text</description>
    <type>BOOLEAN</type>
    <defaultValue>false</defaultValue>
    <dependentPropertyDescriptors>
        truedecline_rule_identifierThis is some random text.STRING
        <dependentPropertyDescriptor>
            true
            <value>true</value>
            <propertyDescriptor>
                decline_rule_identifier
                <name>decline_rule_identifier_1</name>
                <description>This is some random text.</description>
                <type>STRING</type>
            </propertyDescriptor>
        </dependentPropertyDescriptor>
    </dependentPropertyDescriptors>
</propertyDescriptor>

But remove the <xsl:strip-space elements="*"/> tag and the output is clean. I also get corrupted data if I use net.sf.saxon:Saxon-HE:12.0

UPDATE

To clarify my requirements, I have to duplicate the propertyDescriptor element (which element to duplicate by how many times is fed to the xslt as 2 params. I hardcoded these to simply the problem)

It not enough to simply duplicate propertyDescriptor elements, the name must also be unique. Expected output for 2 counts:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<propertyDescriptor>
    <name>enable_decline_rule_1</name>
    <description>text</description>
    <type>BOOLEAN</type>
    <defaultValue>false</defaultValue>
    <dependentPropertyDescriptors>
        <dependentPropertyDescriptor>
            <value>true</value>
            <propertyDescriptor>
                <name>decline_rule_identifier_1</name>
                <description>This is some random text.</description>
                <type>STRING</type>
            </propertyDescriptor>
        </dependentPropertyDescriptor>
    </dependentPropertyDescriptors>
</propertyDescriptor>
<propertyDescriptor>
    <name>enable_decline_rule_2</name>
    <description>text</description>
    <type>BOOLEAN</type>
    <defaultValue>false</defaultValue>
    <dependentPropertyDescriptors>
        <dependentPropertyDescriptor>
            <value>true</value>
            <propertyDescriptor>
                <name>decline_rule_identifier_2</name>
                <description>This is some random text.</description>
                <type>STRING</type>
            </propertyDescriptor>
        </dependentPropertyDescriptor>
    </dependentPropertyDescriptors>
</propertyDescriptor>

Solution

  • It seems with XSLT 3 you can use the to operator e.g.

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        exclude-result-prefixes="#all"
        expand-text="yes"
        version="3.0">
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:template match="propertyDescriptor[name/@repeat]">
        <xsl:for-each select="(0 to name/@repeat)!current()">
          <xsl:apply-templates select="." mode="copy">
            <xsl:with-param name="id" select="position()" tunnel="yes"/>
          </xsl:apply-templates>
        </xsl:for-each>
      </xsl:template>
      
      <xsl:mode name="copy" on-no-match="shallow-copy"/>
      
      <xsl:template mode="copy" match="name[@repeat]">
        <xsl:param name="id" tunnel="yes"/>
        <xsl:copy>{.}_{$id}</xsl:copy>
      </xsl:template>
      
      <xsl:output indent="yes"/>
      
    </xsl:stylesheet>
    

    will produce the output

    <propertyDescriptor>
       <name>enable_decline_rule_1</name>
       <description>text</description>
       <type>BOOLEAN</type>
       <defaultValue>false</defaultValue>
       <dependentPropertyDescriptors>
          <dependentPropertyDescriptor>
             <value>true</value>
             <propertyDescriptor>
                <name>decline_rule_identifier_1</name>
                <description>This is some random text.</description>
                <type>STRING</type>
             </propertyDescriptor>
          </dependentPropertyDescriptor>
       </dependentPropertyDescriptors>
    </propertyDescriptor>
    <propertyDescriptor>
       <name>enable_decline_rule_2</name>
       <description>text</description>
       <type>BOOLEAN</type>
       <defaultValue>false</defaultValue>
       <dependentPropertyDescriptors>
          <dependentPropertyDescriptor>
             <value>true</value>
             <propertyDescriptor>
                <name>decline_rule_identifier_2</name>
                <description>This is some random text.</description>
                <type>STRING</type>
             </propertyDescriptor>
          </dependentPropertyDescriptor>
       </dependentPropertyDescriptors>
    </propertyDescriptor>
    

    from the input

    <propertyDescriptor>
      <name repeat="1">enable_decline_rule</name>
      <description>text</description>
      <type>BOOLEAN</type>
      <defaultValue>false</defaultValue>
      <dependentPropertyDescriptors>
        <dependentPropertyDescriptor>
          <value>true</value>
          <propertyDescriptor>
            <name repeat="1">decline_rule_identifier</name>
            <description>This is some random text.</description>
            <type>STRING</type>
          </propertyDescriptor>
        </dependentPropertyDescriptor>
      </dependentPropertyDescriptors>
    </propertyDescriptor>