John
John

Reputation: 2852

Replacing the Node text with XSLT?

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

Answers (3)

Dimitre Novatchev
Dimitre Novatchev

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:

  1. The identity rule copies every node "as-is".

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

G_H
G_H

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:

  • Store the re-scheduled dates with places in a secondary XML and use the XPath document function to read from it;
  • generate an XSLT stylesheet programmatically with the corrections already in place or
  • use an XSLT extension function or extension element (function would probably suffice) to perform the checks in some other language. For example Java.

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

Krab
Krab

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

Related Questions