String Split to new Elements using XSL 1.0

2019-08-17 13:06发布

Can any guide me to split the given xml element values into multiple child elements based on a token. Here is my sample input xml and desired output. I have a limitation to use xsl 1.0. Thank you.

Input XML:

    <?xml version='1.0' encoding='UTF-8'?>
<SQLResults>
    <SQLResult>
        <ACTION1>Action1</ACTION1>
        <ACTION2>Action2</ACTION2>
        <Encrypt>Program=GPG;Code=23FCS;</Encrypt>
        <SENDER>Program=WebPost;Protocol=WS;Path=/home/Inbound</SENDER>
    </SQLResult>
</SQLResults>

Output XML:

<?xml version='1.0' encoding='UTF-8'?>
<SQLResults>
    <SQLResult>
        <ACTION1>Action1</ACTION1>
        <ACTION2>Action2</ACTION2>
        <Encrypt>
            <Program>GPG</Program>
            <Code>23FCS</Code>
        </Encrypt>
        <SENDER>
            <Program>Action4</Program>
            <Protocol>WS</Protocol>
            <Path>/home/Inbound</Path>
        </SENDER>
    </SQLResult>
</SQLResults>

标签: xslt
1条回答
欢心
2楼-- · 2019-08-17 13:49

In XSLT 2 it would be easy, just with the following template:

<xsl:template match="Encrypt|SENDER">
  <xsl:copy>
    <xsl:analyze-string select="." regex="(\w+)=([\w/]+);?">
      <xsl:matching-substring>
        <element name="{regex-group(1)}">
          <xsl:value-of select="regex-group(2)"/>
        </element>
      </xsl:matching-substring>
    </xsl:analyze-string>
  </xsl:copy>
</xsl:template>

Because you want to do it in XSLT 1, you have to express it another way.

Instead of analyze-string you have to:

  • Tokenize the content into non-empty tokens contained between ; chars. You have to add tokenize template.
  • Each such token divide into 2 substrings, before and after = char.
  • Create an element with the name equal to the first substring.
  • Write the content of this element - the second substring.

XSLT 1 has also such limitation that the result of the tokenize template is a result tree fragment (RTF) not the node set and thus it cannot be used in XPath expressions.

To circumvent this limitation, you must use exsl:node-set function.

So the whole script looks like below:

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

  <xsl:template match="Encrypt|SENDER">
    <xsl:copy>
      <xsl:variable name="tokens">
        <xsl:call-template name="tokenize">
          <xsl:with-param name="txt" select="."/>
          <xsl:with-param name="delim" select="';'"/>
        </xsl:call-template> 
      </xsl:variable>
      <xsl:for-each select="exsl:node-set($tokens)/token">
        <xsl:variable name="t1" select="substring-before(., '=')"/>
        <xsl:variable name="t2" select="substring-after(., '=')"/>
        <xsl:element name="{$t1}">
          <xsl:value-of select="$t2" />
        </xsl:element>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>

  <xsl:template name="tokenize">
    <xsl:param name="txt" />
    <xsl:param name="delim" select="' '" />
    <xsl:choose>
      <xsl:when test="$delim and contains($txt, $delim)">
        <token>
          <xsl:value-of select="substring-before($txt, $delim)" />
        </token>
        <xsl:call-template name="tokenize">
          <xsl:with-param name="txt" select="substring-after($txt, $delim)" />
          <xsl:with-param name="delim" select="$delim" />
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="$txt">
        <token><xsl:value-of select="$txt" /></token>
      </xsl:when>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
  </xsl:template>
</xsl:transform>
查看更多
登录 后发表回答