XSLT - XML into CDATA

2019-06-13 08:23发布

I have a mule flow that transforms a XML:

HTTP Listener > Logger > XSLT > Logger

This is the original message:

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope/" xmlns:pref="URI_SOAP_WS">
   <soap:Body>
      <entryXML>
         <note>
            <to>Totest</to>
            <from>Fromtest</from>
            <heading>Query</heading>
            <body>Update Windows 10</body>
         </note>
      </entryXML>
   </soap:Body>
</soap:Envelope>

I want to transform with XSLT to this:

<entryXML>
   &lt;note&gt;
    &lt;to&gt;Totest&lt;/to&gt;
    &lt;from&gt;Fromtest&lt;/from&gt;
    &lt;heading&gt;Query&lt;/heading&gt;
    &lt;body&gt;Update Windows 10&lt;/body&gt;
    &lt;/note&gt;
<entryXML>

I tried with this template:

<xsl:stylesheet version="3.0" xmlns:saxon="http://saxon.sf.net/"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:pref="URI_SOAP_WS">

    <xsl:output method="xml" version="1.0" encoding="UTF-8"
        indent="yes" />
    <xsl:strip-space elements="*" />

    <xsl:template match="/*">
        <xsl:value-of select="serialize(.)" />
    </xsl:template>

</xsl:stylesheet>

But it transform from <entryXML> to </soap:Envolve>.

How can I transform only the content of <entryXML></entryXML>?

1条回答
何必那么认真
2楼-- · 2019-06-13 08:33

The following solution is based on the answer by jelovirt that Kevin Brown has linked to. You could use the XSLT 3.0 (XPath 3.0) serialize function or the Saxon extension function that is called saxon:serialize, but the solution below is more portable because it works with XSLT 1.0.

Start with an identity template to copy everything that is not matched by a more specific template. In your example, this would match the outer SOAP elements.

Then match the entryXML element as the starting point of the special serialization mode. Any content inside entryXML will be processed in a mode other than the default, and only templates with this mode can be matched against the input.

XSLT Stylesheet

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

    <xsl:output method="xml" encoding="UTF-8" indent="yes" />

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

    <xsl:template match="entryXML">
        <xsl:copy>
            <xsl:variable name="nodestring">
                <xsl:apply-templates select="@*|node()" mode="serialize"/>
            </xsl:variable>
            <xsl:value-of select="$nodestring"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*" mode="serialize">
        <xsl:text>&lt;</xsl:text>
        <xsl:value-of select="name()"/>
        <xsl:text>&gt;</xsl:text>
        <xsl:apply-templates mode="serialize"/>
        <xsl:text>&lt;/</xsl:text>
        <xsl:value-of select="name()"/>
        <xsl:text>&gt;</xsl:text>
    </xsl:template>

    <xsl:template match="text()" mode="serialize">
        <xsl:value-of select="."/>
    </xsl:template>

</xsl:transform>

XML Output

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope/"
               xmlns:pref="URI_SOAP_WS"
               xmlns:saxon="http://saxon.sf.net/">
   <soap:Body>
      <entryXML>
         &lt;note&gt;
            &lt;to&gt;Tove&lt;/to&gt;
            &lt;from&gt;Jani&lt;/from&gt;
            &lt;heading&gt;Reminder&lt;/heading&gt;
            &lt;body&gt;Don't forget me this weekend!&lt;/body&gt;
         &lt;/note&gt;
      </entryXML>
   </soap:Body>
</soap:Envelope>

EDIT 1

The approach above does not serialize attributes in the message, if there are any. If you also need to preserve attributes, e.g. in a message like

<entryXML>
     <note>
        <to with="love">Tove</to>
            ^^^^^^^^^^^               attribute

you would need to add a template along the lines of

<xsl:template match="@*" mode="serialize">
    <xsl:text> </xsl:text>
    <xsl:value-of select="name()"/>
    <xsl:text>="</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>"</xsl:text>
</xsl:template>

and also change the template that matches * in the serialize mode:

<xsl:template match="*" mode="serialize">
  <xsl:text>&lt;</xsl:text>
  <xsl:value-of select="name()"/>
  <xsl:apply-templates select="@*" mode="serialize"/>
  <xsl:text>&gt;</xsl:text>
  <xsl:apply-templates mode="serialize"/>
  <xsl:text>&lt;/</xsl:text>
  <xsl:value-of select="name()"/>
  <xsl:text>&gt;</xsl:text>
</xsl:template>

EDIT 2

Caution: The solution above is XSLT 1.0 only, but it also has shortcomings and there is no guarantee that it will serialize correctly in every possible case.

It works in a simple case like yours, but robust, general serialization "requires more effort", as Martin Honnen has put it. See e.g. Evan Lenz's stylesheet for a more sophisticated approach.

Alternatively, you can modify your original XSLT 3.0 stylesheet to (borrowing some of the ideas above)

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

    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />

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

    <xsl:template match="entryXML/*">
            <xsl:value-of select="serialize(.)" />
    </xsl:template>

</xsl:stylesheet>

which will also lead to a more reliable serialization.

查看更多
登录 后发表回答