Shikha
Shikha

Reputation: 13

Convert a variable length delimited list to XML using XSLT 1.0

The text input should be converted to the XML output using XSLT 1.0. The list is of variable length with the delimiter being |.

Input:

name=IMON_EVENT;next_state=SET_IMON;is_enabled=true; | name=MAIN_BATCH;next_state=BATCH01;is_enabled=false;priority=9;

Expected output:

<time-triggers>
    <trigger>
        <name>IMON_EVENT</name>
        <next_state>SET_IMON</next_state>
        <is_enabled>true</is_enabled>
    </trigger>
    <trigger>
        <name>MAIN_BATCH</name>
        <next_state>BATCH01</next_state>
        <is_enabled>false</is_enabled>
        <priority>9</priority>
    </trigger>
</time-triggers>

Upvotes: 1

Views: 154

Answers (2)

Mads Hansen
Mads Hansen

Reputation: 66781

For an XSLT 1.0, you can use recursive template calls that test for the presence of the delimiters and use substring-before() and substring-after()

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

    <xsl:output indent="yes"/>

    <xsl:template match="/">
        <xsl:variable name="triggers" select="'name=IMON_EVENT;next_state=SET_IMON;is_enabled=true; | name=MAIN_BATCH;next_state=BATCH01;is_enabled=false;priority=9;'"/>

        <triggers>
            <xsl:call-template name="make-trigger">
                <xsl:with-param name="val" select="$triggers"/>
            </xsl:call-template> 
        </triggers>   
    </xsl:template>

    <xsl:template name="make-trigger">
        <xsl:param name="val"/>

        <xsl:if test="normalize-space($val)">
            <xsl:choose>
                <xsl:when test="contains($val, '|')">
                    <trigger>
                    <xsl:call-template name="make-elements">
                        <xsl:with-param name="val" select="substring-before($val, '|')"/>
                    </xsl:call-template>
                    </trigger>
                    <xsl:call-template name="make-trigger">
                        <xsl:with-param name="val" select="substring-after($val, '|')"/>
                    </xsl:call-template>
                </xsl:when>
                <xsl:otherwise>
                    <trigger>
                    <xsl:call-template name="make-elements">
                        <xsl:with-param name="val" select="$val"/>
                    </xsl:call-template>
                    </trigger>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:if>
    </xsl:template>

    <xsl:template name="make-elements">
        <xsl:param name="val"/>

        <xsl:if test="contains($val, '=')">
            <xsl:choose>
                <xsl:when test="contains($val, ';')">
                    <xsl:call-template name="make-element">
                        <xsl:with-param name="val" select="substring-before($val, ';')"/>
                    </xsl:call-template>
                    <xsl:call-template name="make-elements">
                        <xsl:with-param name="val" select="substring-after($val, ';')"/>
                    </xsl:call-template>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:call-template name="make-element">
                        <xsl:with-param name="val" select="$val"/>
                    </xsl:call-template>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:if>
    </xsl:template>

    <xsl:template name="make-element">
        <xsl:param name="val"/>

        <xsl:element name="{normalize-space(substring-before($val, '='))}">
            <xsl:value-of select="substring-after($val, '=')"/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

Below is an XSLT 2.0 solution that uses the tokenize() function to split the values by | and ; delimiters with nested xsl:for-each to process the sequence of values, xsl:analyze-string to capture the name and value between the =, and xsl:element to create dynamically named elements from the regex capture groups.

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

  <xsl:output indent="yes"/>

  <xsl:template match="/">
    <xsl:variable name="triggers" select="'name=IMON_EVENT;next_state=SET_IMON;is_enabled=true; | name=MAIN_BATCH;next_state=BATCH01;is_enabled=false;priority=9;'"/>

    <triggers>
      <xsl:for-each  select="tokenize($triggers, '\s*\|\s*')">
        <trigger>
          <xsl:for-each select="tokenize(., '\s*;\s*')">
              <xsl:analyze-string select="." regex="(.+)=(.*)">
                  <xsl:matching-substring>
                      <xsl:element name="{regex-group(1)}">
                          <xsl:value-of select="regex-group(2)"/>
                      </xsl:element>
                  </xsl:matching-substring>
              </xsl:analyze-string> 
          </xsl:for-each>
        </trigger>  
      </xsl:for-each>
    </triggers>

  </xsl:template>

</xsl:stylesheet>

Upvotes: 0

Valdi_Bo
Valdi_Bo

Reputation: 31011

Placement of the source string in XSLT script is a bad practice, especially if you want to process various inputs.

If you can use XSLT version 2.0, you should rather use the following functions:

  • unparsed-text-available - to check whether an input file exists,
  • unparsed-text - to read the content of this file into a variable.

The rest of the script (how to process the content read) can be as in the other answer.

Upvotes: 0

Related Questions