xslt convert xml string in xml elements

2019-07-04 06:15发布

问题:

here's a tricky one.

I have the following XML

<test>
     <testElement SomeAttribute="<otherxml><otherElement>test test</otherElement></otherxml>">
      </testElement>
</test>

Using XSLT, I want to transform this XML to have the following result.

<test>
     <testElement>
        <SomeAttributeTransformedToElement>
          <otherxml>
               <otherElement>test test</otherElement>
          </otherxml>
        </SomeAttributeTransformedToElement>
      </testElement>
</test>

Basically, some text in an attribute must be transformed to actual elements in the final XML

Any ideas how to achieve that in XSLT?

Alex

回答1:

You can achieve that by disabling output escaping. However, note that your input document is not a valid XML document (< is illegal in attribute values and needs escaping). I therefore changed your input document as follows:

Input document

<?xml version="1.0" encoding="utf-8"?>
<test>
  <testElement SomeAttribute="&lt;otherxml>&lt;otherElement>test test&lt;/otherElement>&lt;/otherxml>">
  </testElement>
</test>

XSLT

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" indent="yes"/>

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

  <xsl:template match="@SomeAttribute">
    <SomeAttributeTransformedToElement>
      <xsl:value-of select="." disable-output-escaping="yes"/>
    </SomeAttributeTransformedToElement>
  </xsl:template>
</xsl:stylesheet>

Be aware that with disable-output-escaping="yes" there is no longer a guarantee that the produced output document is a well-formed XML document.



回答2:

I had the same need, and I finally managed to build something working.

However, it's far from perfect. But as this solution works for me (and is very useful in my case), I give it them to share with anyone who could be interrested too.

I hope that XSL-purists will forgive me for this :

This XSLT named-template takes a text variable as input, and generates XML elements on the output. Warning : There are some limitations :

  • The XML must not have self-contained elements (<a><b><a>...</a></b></a> is forbidden, as the 'a' element contains another 'a')
  • The XML must be normalized (a single space between the element name and the attributes, so <elmt attr1="1" attr2="2"> is allowed, but not <elmt      attr1="1"     attr2="2"> (this can be fixed)
  • &ltelement /> style is not handled yet (sorry, I didn't need that, and I'm kinda busy on y current project), but this can easily be fixed.

So you've been warned : don't put this in production before having tested it and being sure that it's illegible to your case. Also, if you happen to fix or improve this script, please let me know.

Here are the templates :

<xsl:template name="t-convert">
    <xsl:param name="TEXT"/>
    <xsl:choose>
        <xsl:when test="starts-with($TEXT,'&lt;?')">
            <xsl:call-template name="t-convert">
                <xsl:with-param name="TEXT" select="substring-after($TEXT,'?&gt;')"/>
            </xsl:call-template>
        </xsl:when>
        <!-- Si le texte contient encore des elements -->
        <xsl:when test="contains($TEXT,'&lt;')">
            <xsl:variable name="before-first-open" select="substring-before($TEXT,'&lt;')"/>
            <xsl:variable name="after-first-open" select="substring-after($TEXT,'&lt;')"/>
            <!-- On ecrit le texte qui précéde l'élément -->
            <xsl:value-of select="$before-first-open"/>
            <!-- Le nom de l'élément -->
            <xsl:variable name="TRAD" select="translate($after-first-open,'&gt;',' ')"/>
            <!--  TODO : Gere le cas <ELEMENT />   -->
            <xsl:variable name="ELEMENT" select="substring-before($TRAD,' ')"/>
            <xsl:variable name="suite" select="substring-after($after-first-open,$ELEMENT)"/>
            <xsl:variable name="DEFINITION" select="substring-before($suite,'&gt;')"/>
            <xsl:variable name="CONTENT" select="substring-after(substring-before($suite,concat('&lt;/',$ELEMENT)),concat($DEFINITION,'&gt;'))"/>
            <xsl:variable name="FOLLOWING">
                <xsl:choose>
                    <xsl:when test="substring($DEFINITION,string-length($DEFINITION))='/'"><!--  ends-with($DEFINITION,'/') not compatible with all XSLT version -->
                        <xsl:value-of select="substring-after($suite,'/&gt;')"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="substring-after(substring-after($suite,concat('&lt;/',$ELEMENT)),'&gt;')"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:variable>
            <xsl:element name="{$ELEMENT}">
                <xsl:call-template name="t-attribs">
                    <xsl:with-param name="TEXT" select="$DEFINITION"/>
                </xsl:call-template>
                <xsl:call-template name="t-convert">
                    <xsl:with-param name="TEXT" select="$CONTENT"/>
                </xsl:call-template>
            </xsl:element>
            <xsl:call-template name="t-convert">
                <xsl:with-param name="TEXT" select="$FOLLOWING"/>
            </xsl:call-template>
        </xsl:when>
        <!-- no more element -->
        <xsl:otherwise>
            <xsl:value-of select="$TEXT"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template name="t-attribs">
    <xsl:param name="TEXT"/>
    <xsl:if test="contains($TEXT,'=')">
        <!-- Assert TEXT=' NAME="VALUE".*' -->
        <xsl:variable name="NAME" select="substring-after(substring-before($TEXT,'='),' ')"/>
        <xsl:variable name="afterName" select="substring-after($TEXT,'=&quot;')"/>
        <xsl:variable name="VALUE" select="substring-before($afterName,'&quot;')"/>
        <xsl:variable name="FOLLOWING" select="substring-after($afterName,'&quot;')"/>
        <xsl:attribute name="{$NAME}">
            <xsl:value-of select="$VALUE"/>
        </xsl:attribute>
        <xsl:call-template name="t-attribs">
            <xsl:with-param name="TEXT" select="FOLLOWING"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

And it's called with :

<xsl:call-template name="t-convert">
        <xsl:with-param name="TEXT" select="//content"/>
</xsl:call-template>

I hope this will be helful to at least one perso in the world (it was for me !)



标签: xslt