I am new to xsl. I want to transform an xml from,
<result name="response" numFound="1" start="0">
<doc>
<str name="q">what</str>
<arr name="suggestion">
<str>what1</str>
<str>what2</str>
</arr>
</doc>
</result>
to,
<result name="response" numFound="2" start="0">
<doc>
<str name="q">what</str>
<str name="suggestion">what1</str>
</doc>
<doc>
<str name="q">what</str>
<str name="suggestion">what2</str>
</doc>
</result>
I could extract the texts, "what1" and "what2" using,
<xsl:template match="/response/result[@name='response']">
<xsl:for-each select="./doc/arr[@name='suggestion']/str">
<xsl:value-of select="normalize-space(.)"/>
<xsl:value-of select="$endl"/>
</xsl:for-each>
</xsl:template>
But I don't know how to enclose the same into output xml format. Can anyone please help...
Additional feature:
Can anyone please tell if I could add a field to the output doc called <float name="score">
which would get incremented by 100 with each doc?
eg)
output:
<response>
<lst name="responseHeader">
<int name="status">0</int>
<int name="QTime">1</int>
<lst name="params">
<str name="indent">on</str>
<str name="q">"what"</str>
</lst>
</lst>
<result numFound="2" name="response" start="0">
<doc>
<str name="query">what</str>
<str>what1</str>
<float name="score">100</float>
</doc>
<doc>
<str name="query">what</str>
<str>what2</str>
<float name="score">200</float>
</doc>
<doc>
<str name="query">what</str>
<str>what3</str>
<float name="score">300</float>
</doc>
</result>
</response>
Can you please tell what function do I suppose to use?
Here is a solution to the OP's comment that updates the question:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="result">
<result numFound="{count(doc/arr/str)}">
<xsl:apply-templates select="@*[not(name()='numFound')]"/>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="arr[@name='suggestion']/str">
<doc>
<xsl:copy-of select="../../str"/>
<xsl:copy-of select="."/>
</doc>
</xsl:template>
<xsl:template match="doc|doc/arr">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="doc/str "/>
</xsl:stylesheet>
when applied on the following (provided in the OP's comment) XML document:
<response>
<lst name="responseHeader">
<int name="status">0</int>
<int name="QTime">1</int>
<lst name="params">
<str name="indent">on</str>
<str name="q">"what"</str>
</lst>
</lst>
<result name="response" numFound="1" start="0">
<doc>
<str name="q">what</str>
<arr name="suggestion">
<str>what1</str>
<str>what2</str>
</arr>
</doc>
</result>
</response>
produces the wanted, correct result:
<response>
<lst name="responseHeader">
<int name="status">0</int>
<int name="QTime">1</int>
<lst name="params">
<str name="indent">on</str>
<str name="q">"what"</str>
</lst>
</lst>
<result numFound="2" name="response" start="0">
<doc>
<str name="q">what</str>
<str>what1</str>
</doc>
<doc>
<str name="q">what</str>
<str>what2</str>
</doc>
</result>
</response>
Update:
In an update to his question, the OP requested:
Can anyone please tell if I could add a field to the output doc called
<float name="score">
which would get incremented by 100 with each
doc?
This is extremely easy to achieve. We add just three lines of code to the existing transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="result">
<result numFound="{count(doc/arr/str)}">
<xsl:apply-templates select="@*[not(name()='numFound')]"/>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="arr[@name='suggestion']/str">
<doc>
<xsl:copy-of select="../../str"/>
<xsl:copy-of select="."/>
<float name="score">
<xsl:value-of select="100*position()"/>
</float>
</doc>
</xsl:template>
<xsl:template match="doc|doc/arr">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="doc/str "/>
</xsl:stylesheet>
When this transformation is applied to this XML document:
<response>
<lst name="responseHeader">
<int name="status">0</int>
<int name="QTime">1</int>
<lst name="params">
<str name="indent">on</str>
<str name="q">"what"</str>
</lst>
</lst>
<result name="response" numFound="1" start="0">
<doc>
<str name="q">what</str>
<arr name="suggestion">
<str>what1</str>
<str>what2</str>
<str>what3</str>
</arr>
</doc>
</result>
</response>
the wanted, correct result is produced:
<response>
<lst name="responseHeader">
<int name="status">0</int>
<int name="QTime">1</int>
<lst name="params">
<str name="indent">on</str>
<str name="q">"what"</str>
</lst>
</lst>
<result numFound="3" name="response" start="0">
<doc>
<str name="q">what</str>
<str>what1</str>
<float name="score">100</float>
</doc>
<doc>
<str name="q">what</str>
<str>what2</str>
<float name="score">200</float>
</doc>
<doc>
<str name="q">what</str>
<str>what3</str>
<float name="score">300</float>
</doc>
</result>
</response>
The following stylesheet produces the desired result from your input. It might be too lenient or too strict, but since I don't know your exact requirements or have a schema for the input, it's a best effort. Please use this as a starting point but do proper testing and investigate the meaning of what I provide.
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:attribute name="numFound"><xsl:value-of select="count(doc/arr[@name='suggestion']/str)" /></xsl:attribute>
<xsl:apply-templates select="doc/arr[@name='suggestion']" />
</xsl:copy>
</xsl:template>
<xsl:template match="arr[@name='suggestion']">
<xsl:for-each select="str">
<doc>
<xsl:copy-of select="parent::node()/preceding-sibling:: str[@name='q']" />
<xsl:variable name="nameAttributeValue" select="parent::node()/@name" />
<xsl:element name="str"><xsl:attribute name="name"><xsl:value-of select="$nameAttributeValue" /></xsl:attribute><xsl:value-of select="." /></xsl:element>
</doc>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EDIT: In response to your comment: notice how my first template matches on /*
. This comes down to "any root element". If an XPath expression starts with a single /
, it's always relative to the document root. If it starts with a double //
it basically means anywhere in the current node. If it doesn't start with a slash, it's relative to the current node.
In order to process the XML as in your comment, you'd need to change the stylesheet to...
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="node() | @*">
<xsl:apply-templates select="node() | @*" />
</xsl:template>
<xsl:template match="/response/result">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:attribute name="numFound"><xsl:value-of select="count(doc/arr[@name='suggestion']/str)" /></xsl:attribute>
<xsl:apply-templates select="doc/arr[@name='suggestion']" />
</xsl:copy>
</xsl:template>
<xsl:template match="arr[@name='suggestion']">
<xsl:for-each select="str">
<doc>
<xsl:copy-of select="parent::node()/preceding-sibling:: str[@name='q']" />
<xsl:variable name="nameAttributeValue" select="parent::node()/@name" />
<xsl:element name="str"><xsl:attribute name="name"><xsl:value-of select="$nameAttributeValue" /></xsl:attribute><xsl:value-of select="." /></xsl:element>
</doc>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
That first template I added is to make sure we override some default template hidden in XSLT. Mind that this will only provide correct output if there's only a single result
element in response
.
Like I stated, please learn the use of XSLT and XPath expressions to make sure you understand what's going on in the stylesheet.