Reputation: 2852
Below is my XML file, which is used to stored the data -
<Locations>
<location>
<place>Newyork</place>
<dt>01-Dec-2011</dt>
</location>
<location>
<place>Berlin</place>
<dt>02-Dec-2011</dt>
</location>
<location>
<place>Tokyo</place>
<dt>04-Dec-2011</dt>
</location>
</Location>
What I want to achieve is -
I want to replace the <dt>
tags date value, if the visit is re-scheduled. For example-
If the visit date for Berlin is changed, stored in <dt>
tags, then how to edit/replace the same
in the XML file using XSLT..? Thanks in advance - John
Upvotes: 3
Views: 3663
Reputation: 243579
This transformation shows how to use a global parameter (modelled here with an inline element) to specify (possibly multiple) updates:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:updates>
<update place="Berlin" dt="11-Dec-2011"/>
</my:updates>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"location
[place = document('')/*/my:updates/update/@place]
/dt/text()
">
<xsl:value-of select=
"document('')/*/my:updates/update
[@place = current()/../../place]
/@dt
"/>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML document (corrected to make it well-formed):
<Locations>
<location>
<place>Newyork</place>
<dt>01-Dec-2011</dt>
</location>
<location>
<place>Berlin</place>
<dt>02-Dec-2011</dt>
</location>
<location>
<place>Tokyo</place>
<dt>04-Dec-2011</dt>
</location>
</Locations>
the wanted, correct result is produced:
<Locations>
<location>
<place>Newyork</place>
<dt>01-Dec-2011</dt>
</location>
<location>
<place>Berlin</place>
<dt>11-Dec-2011</dt>
</location>
<location>
<place>Tokyo</place>
<dt>04-Dec-2011</dt>
</location>
</Locations>
Explanation:
The identity rule copies every node "as-is".
There is just one overriding template -- matching the text-node child of any dt
whose place
sibling's string value has a corresponding my:updates/update
element. In this template we output the value of the dt
attribute of this corresponding my:updates/update
element.
Do note: In a realworld transformation the inline my:updates
element will be better replaced by an external, global parameter. Read your XSLT processor's documentation how to pass an external parameter to the transformation -- this is implementation-dependent.
UPDATE: As the OP has found it difficult to convert this solution to one using global, externally passed xsl:param
, here is this converted solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pUpdates">
<update place="Berlin" dt="11-Dec-2011"/>
</xsl:param>
<xsl:variable name="vUpdates" select=
"ext:node-set($pUpdates)/*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="dt/text()">
<xsl:choose>
<xsl:when test="../../place=$vUpdates/@place">
<xsl:value-of select=
"$vUpdates[@place = current()/../../place]/@dt"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the same correct and wanted result is produced:
<Locations>
<location>
<place>Newyork</place>
<dt>01-Dec-2011</dt>
</location>
<location>
<place>Berlin</place>
<dt>11-Dec-2011</dt>
</location>
<location>
<place>Tokyo</place>
<dt>04-Dec-2011</dt>
</location>
</Locations>
Do note: In this solution the xsl:param
still has its value hardcoded and this is the only reason we are using the ext:node-set()
extension function. If the parameter is really passed from outside, then this convertion from RTF to a regular tree isn't necessary and the parameter should be referenced directly.
Also, in XSLT 1.0 we have to match more inexactly and to use comparisons (the xsl:choose
) inside the body of the template. This is so because in XSLT 1.0 it isn't allowed to reference variables/parameters inside the match-pattern.
In XSLT 2.0 this limitation has been eliminated, so we can just have a much simpler transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pUpdates">
<update place="Berlin" dt="11-Dec-2011"/>
</xsl:param>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"location[place=$pUpdates/*/@place]/dt/text()">
<xsl:value-of select=
"$pUpdates/*[@place = current()/../../place]/@dt"/>
</xsl:template>
</xsl:stylesheet>
Upvotes: 5
Reputation: 12019
The real question is, how are you gonna check if a visit is re-scheduled? The way I see it, you've got three options:
document
function to read from it;EDIT - or follow Krab's excellent suggestion: use XSLT parameters in a way that lets you pass in the data to an otherwise static stylesheet.
Upvotes: 1
Reputation: 2148
The identity template will copy the document:
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
Then you can make other templates only for the parts you want to change.
Example (untested):
<xsl:template match="//location/dt[preceding-sibling::place='Berlin']">
<dt>Your date</dt>
</xsl:template>
Upvotes: 2