XSLT: How to convert XML Node to String
Asked Answered
C

11

16
<ROOT>
   <A>
      <B>TESTING</B>
   </A>
</ROOT>

XSL:

<xsl:variable name="nodestring" select="//A"/>
<xsl:value-of select="$nodestring"/>

I am trying to convert XML nodeset to string using XSL. Any thoughts?

Cranston answered 14/7, 2011 at 16:7 Comment(4)
You want the output to be: <A><B>TESTING</B></A> ?Belch
mikey - exactly the same output i need.Cranston
Got it working on my end and posted below.Belch
In that case the output you want would be &lt;A&gt;&lt;B&lt;TESTING&gt;/B&gt;&lt;/A&gt; I think. Otherwise you can simply use xsl:copy-of.Marcille
W
13

You need to serialize the nodes. The most simple for your example would be something like

<xsl:template match="ROOT">
  <xsl:variable name="nodestring">
    <xsl:apply-templates select="//A" mode="serialize"/>
  </xsl:variable>
  <xsl:value-of select="$nodestring"/>  
</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>

The above serializer templates do not handle e.g. attributes, namespaces, or reserved characters in text nodes, but the concept should be clear. XSLT process works on a node tree and if you need to have access to "tags", you need to serialize the nodes.

Warship answered 14/7, 2011 at 19:28 Comment(1)
@Kaylan, you then will populate the attribute as follows: <input type="hidden" name="hiddenxml"> <xsl:attribute name="value"><xsl:value-of select="$nodestring"/></xsl:attribute></input>Marcille
M
12

Based on @jelovirt solution, here is a more complete piece of code:

<xsl:template match="*" mode="serialize">
    <xsl:text>&lt;</xsl:text>
    <xsl:value-of select="name()"/>
    <xsl:apply-templates select="@*" mode="serialize" />
    <xsl:choose>
        <xsl:when test="node()">
            <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:when>
        <xsl:otherwise>
            <xsl:text> /&gt;</xsl:text>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<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>

<xsl:template match="text()" mode="serialize">
    <xsl:value-of select="."/>
</xsl:template>
Mealtime answered 3/4, 2013 at 9:25 Comment(0)
C
7

In XSLT Version 3.0. See this W3 link for fn:serialize. This worked for me using SaxonPE.

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:output="http://www.w3.org/2010/xslt-xquery-serialization">
<xsl:variable name="output">
    <output:serialization-parameters>
        <output:method value="html"/>
    </output:serialization-parameters>
</xsl:variable>
    <xsl:template match="div">
        <xsl:value-of select="serialize(., $output/output:serialization-parameters)" />
    </xsl:template>
</xsl:stylesheet>
Condenser answered 22/3, 2014 at 1:16 Comment(0)
P
4
<xsl:template name="serializeNodeToString">
    <xsl:param name="node"/>
    <xsl:variable name="name" select="name($node)"/>
    <xsl:if test="$name">
        <xsl:value-of select="concat('&lt;',$name)"/>
        <xsl:for-each select="$node/@*">
            <xsl:value-of select="concat(' ',name(),'=&quot;',.,'&quot; ')"/>
        </xsl:for-each>
        <xsl:value-of select="concat('&gt;',./text())"/>
    </xsl:if>
    <xsl:for-each select="$node/*">
        <xsl:call-template name="serializeNodeToString">
            <xsl:with-param name="node" select="."/>
        </xsl:call-template>
    </xsl:for-each>
    <xsl:if test="$name">
        <xsl:value-of select="concat('&lt;/',$name,'&gt;')"/>
    </xsl:if>
</xsl:template>
Privy answered 14/12, 2016 at 14:43 Comment(1)
Sample call of this template in a message, for debugging purposes: <xsl:message><xsl:call-template name="serializeNodeToString"> <xsl:with-param name="node" select = "."/> </xsl:call-template></xsl:message>Indistinguishable
Z
3

Saxon required for following solution. I find it here

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

<!-- To serialize with saxon:serialize() -->
<xsl:output name="default" indent="yes"
    omit-xml-declaration="yes" />

<xsl:template match="*">
    <xsl:variable name="node-set">
        <xsl:element name="level1">
            <xsl:element name="level2" />
            <xsl:element name="level2" />
        </xsl:element>
    </xsl:variable>

    <xsl:element name="input">
        <xsl:copy-of select="$node-set" />
    </xsl:element>

    <xsl:element name="output">
        <xsl:value-of select="saxon:serialize($node-set, 'default')" />
    </xsl:element>
</xsl:template>

</xsl:stylesheet>
Zwickau answered 13/8, 2011 at 17:20 Comment(2)
This feature needs the paid version of Saxon.Micky
The feature is also available in Saxon-BIncredible
B
2
<xsl:template match="A">
  <xsl:variable name="nodes" select="." />
  <xsl:copy-of select="$nodes"/>
</xsl:template>

Updated based on comments..

OK I've never done exactly what you need before, so take this with that grain of salt (I'm winging it). Basically you need to be very concerned about 2 things: characters that require escaping and white space. In this case, the string that empo gave you in the comments above is more what you're after. Below is one way that you can make your XSL output that:

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

<xsl:template match="A">

  <input type="hidden" name="hiddenxml">
    <xsl:attribute name="value">
      <xsl:apply-templates select="." mode="id" />
    </xsl:attribute>
  </input>

</xsl:template>

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

</xsl:stylesheet>

You still need to be concerned with other characters that require escaping like " ' & I believe you can use translate or replace for those

Belch answered 14/7, 2011 at 16:26 Comment(4)
Thanks mikey. I want to store the output of <xsl:copy-of> to a variable. As soon as I store it, it loses the tag information.Cranston
Well we are putting the nodes into the variable. To output the tag info, you can do it with copy-of. It is not lost it just depends how you display it. value-of will only display the tag values not the tags themselves.Belch
<input type="hidden" name="hiddenxml"> <xsl:attribute name="value"><xsl:copy-of select="$nodes"/></xsl:attribute></input>. If I do this, only the value comes out, instead of entire xml stringCranston
@Kalyan, that's correct. You can't copy nodes into an attribute as value. You need to correctly escape the tags.Marcille
C
2

About "convert Node to String"

With XSLT 1.0, you can use the XPath1.0 string() function of the Core Function Library, that converts a node to a string:

<xsl:template match="A">
  <xsl:variable name="nodeAsStr" select="string(.)" />
  <xsl:copy-of select="$nodeAsStr"/><!-- or value-of -->
</xsl:template>

See "Function: string string(object)" at section 4.3.

About "convert Node to XML pretty-printer"

It this another question, about "XML pretty-printer" or "XML dump" ... See good answers here.

Coastal answered 15/4, 2013 at 16:25 Comment(0)
T
2

My solution:

<xsl:template name="serializeNodeToString">
    <xsl:param name="node" />
    <xsl:variable name="name" select="name($node)" />

    <xsl:text>&lt;</xsl:text>
    <xsl:value-of select="$name" />
    <xsl:for-each select="$node/@*">
        <xsl:text> </xsl:text>
        <xsl:value-of select="name()" /><xsl:text>=&quot;</xsl:text>
            <xsl:value-of select="." /> 
        <xsl:text>&quot;</xsl:text>
        <xsl:text> </xsl:text>
    </xsl:for-each>
    <xsl:text>&gt;</xsl:text>
    <xsl:value-of select="./text()" />
    <xsl:for-each select="$node/*">
        <xsl:call-template name="serializeNodeToString">
            <xsl:with-param name="node" select="."/>
        </xsl:call-template>
    </xsl:for-each>

    <xsl:text>&lt;/</xsl:text>
    <xsl:value-of select="$name" />
    <xsl:text>&gt;</xsl:text>
</xsl:template>
Tartaglia answered 2/12, 2015 at 14:50 Comment(0)
I
1

Search for "XML pretty-printer". Or just have a look at the XSLT code of my XPath Visualizer (though it produces XML representation to be displayed in a browser, but you'll get the idea).

Ious answered 15/7, 2011 at 13:40 Comment(0)
M
1

My solution is for Saxon HE, and have this advantages:

  • it doesn't require licensing
  • supports namespaces, CDATA, escaping of special characters and many advanced XML features.

I've tried successfully with Saxon HE 9.5.X.

It is about registering a custom extension function with these contents:

import java.io.StringWriter;   
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.lib.ExtensionFunctionCall;
import net.sf.saxon.lib.ExtensionFunctionDefinition;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.XdmValue;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.StringValue;

@SuppressWarnings("serial")
public class XmlSerializer extends ExtensionFunctionDefinition {
    @Override
    public StructuredQName getFunctionQName() {
        return new StructuredQName("vis", "my.custom.uri", "serialize-xml");
    }

    @Override
    public SequenceType[] getArgumentTypes() {
        return new SequenceType[] { SequenceType.SINGLE_NODE };
    }

    @Override
    public SequenceType getResultType(SequenceType[] sequenceTypes) {
        return SequenceType.SINGLE_STRING;
    }

    @Override
    public ExtensionFunctionCall makeCallExpression() {
        return new ExtensionFunctionCall() {
            @Override
            public Sequence call(XPathContext ctx, Sequence[] secs) throws XPathException {
                StringWriter escr = new StringWriter();
                try {
                    if (secs.length == 0) {
                        throw new XPathException("Missing argument");
                    } else {
                        Serializer serializer = new Processor(ctx.getConfiguration()).newSerializer(escr);
                        serializador.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes");
                        serializer.serializeXdmValue(XdmValue.wrap(secs[0]));
                    }
                    return new StringValue(escr.toString());
                } catch (SaxonApiException ex) {
                    throw new XPathException("Error when invoking serialize-xml()", ex);
                }
            }
        };
    }
}

You can use this function as follows:

<xs:value-of xmlns:vis="my.custom.uri" select="vis:serialize-xml(someNode)"/>

The inverse process is documented here.

Micky answered 1/7, 2014 at 9:53 Comment(0)
B
0

All solutions miss text after node and attributes in single quotes. Example

<b f1='"' f2="'">one</b>
,
<b>two</b>

My solution based on @Ilya-Kharlamov

<xsl:template name="f_serialize_node_to_string" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
    <xsl:param name="node"/>

    <xsl:variable name="node_" select="exsl:node-set($node)"/>

    <xsl:variable name="name" select="name($node_)"/>
    <xsl:variable name="q">'</xsl:variable>
    <xsl:variable name="qq">"</xsl:variable>

    <xsl:if test="$name">
        <xsl:value-of select="concat('&lt;',$name)"/>
        <xsl:for-each select="$node_/@*">
            <xsl:choose>
              <xsl:when test="contains(., $qq)">
                    <xsl:value-of select="concat(' ',name(),'=',$q,.,$q,' ')"/>
                </xsl:when>
              <xsl:otherwise>
                    <xsl:value-of select="concat(' ',name(),'=&quot;',.,'&quot; ')"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
        <xsl:value-of select="concat('&gt;', ./text())"/>
    </xsl:if>
    <xsl:for-each select="$node_/*">
        <xsl:call-template name="f_serialize_node_to_string">
            <xsl:with-param name="node" select="."/>
        </xsl:call-template>
    </xsl:for-each>
    <xsl:if test="$name">
        <xsl:value-of select="concat('&lt;/',$name,'&gt;')"/>
    </xsl:if>
    <xsl:if test="$node_/following-sibling::text()">
        <xsl:value-of select="$node_/following-sibling::text()" />
    </xsl:if>
</xsl:template>
Batory answered 3/5, 2019 at 14:28 Comment(1)
In order to show you how complex this task might become, check how this well formed XML <test a='1"2'/> is wrongly handled by your solution.Wallywalnut

© 2022 - 2024 — McMap. All rights reserved.