tags:

views:

42

answers:

2

Consider the following XML:

<?xml version="1.0" encoding="ISO-8859-1"?>
<catalog>
    <cd>
        <title>Empire Burlesque</title>
        <artist>
            <name>Bob</name>
            <surname>Dylan</surname>
        </artist>
        <country>USA</country>
        <company>Columbia</company>
        <price>10.90</price>
        <year>1985</year>
    </cd>
</catalog>

I want to add elements to this XML using XSLT, to get the following result:

<?xml version="1.0" encoding="ISO-8859-1"?>
<catalog>
    <cd>
        <title>Empire Burlesque</title>
        <artist>
            <name>Bob</name>
            <surname>Dylan</surname>
            <!-- NEW -->
            <middlename>???</middlename>
        </artist>
        <country>USA</country>
        <company>Columbia</company>
        <price>10.90</price>
        <year>1985</year>
        <!-- NEW -->
        <comment>great one</comment>
    </cd>
    <!-- NEW -->
    <cd>
      <title>Hide your heart</title>
        <artist>
          <name>Bonnie</name>
          <surname>Tyler</surname>
        </artist>
        <country>UK</country>
        <company>CBS Records</company>
        <price>9.90</price>
        <year>1988</year>
    </cd>
</catalog>

To achieve that, I wrote the following XSLT:

<?xml version="1.0" encoding="ISO-8859-1"?>

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

  <xsl:template name="injectXml">
    <xsl:param name="whatToInject"/>
    <xsl:copy>
      <xsl:copy-of select="node() | @*"/>
      <xsl:copy-of select="$whatToInject"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="//catalog">
    <xsl:call-template name="injectXml">
      <xsl:with-param name="whatToInject">
        <cd>
          <title>Hide your heart</title>
            <artist>
              <name>Bonnie</name>
              <surname>Tyler</surname>
            </artist>
            <country>UK</country>
            <company>CBS Records</company>
            <price>9.90</price>
            <year>1988</year>
        </cd>
      </xsl:with-param>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="//cd[year=1985]">
    <xsl:call-template name="injectXml">
      <xsl:with-param name="whatToInject">
        <comment>great one</comment>
      </xsl:with-param>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="//cd[year=1985]/artist">
    <xsl:call-template name="injectXml">
      <xsl:with-param name="whatToInject">
        <middlename>???</middlename>
      </xsl:with-param>
    </xsl:call-template>
  </xsl:template>

</xsl:stylesheet>

Why it's not working? How to do it?

A: 

Have you tried

match="//cd/year/[.='1985']"

Raj
Doesn't seem to work for me. Could you please explain what it's supposed to do?
Stefan
+1  A: 

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my"
 exclude-result-prefixes="my xsl"
 >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <my:updates>
   <update num="1">
    <comment>great one</comment>
   </update>
   <update num="2">
    <middlename>XXX</middlename>
   </update>
   <update num="3">
    <cd>
      <title>Hide your heart</title>
        <artist>
          <name>Bonnie</name>
          <surname>Tyler</surname>
        </artist>
        <country>UK</country>
        <company>CBS Records</company>
        <price>9.90</price>
        <year>1988</year>
    </cd>
   </update>
 </my:updates>

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

    <xsl:template match="catalog">
      <xsl:call-template name="inject">
       <xsl:with-param name="pUpdate" select="3"/>
      </xsl:call-template>
    </xsl:template>

    <xsl:template match="cd[year=1985]">
      <xsl:call-template name="inject">
       <xsl:with-param name="pUpdate" select="1"/>
      </xsl:call-template>
    </xsl:template>

    <xsl:template match="cd[year=1985]/artist">
      <xsl:call-template name="inject">
       <xsl:with-param name="pUpdate" select="2"/>
      </xsl:call-template>
    </xsl:template>

    <xsl:template name="inject">
      <xsl:param name="pUpdate"/>

      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
       <xsl:apply-templates select=
       "document('')/*/my:updates/*[@num=$pUpdate]/node()"/>
      </xsl:copy>
    </xsl:template>

  <xsl:template match="*[ancestor::my:updates]">
        <xsl:element name="{name()}" namespace="{namespace-uri()}">
          <xsl:copy-of select=
             "namespace::*[not(name()='my' or name()='xsl')]"/>
          <xsl:apply-templates select="node()|@*"/>
        </xsl:element>
  </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<catalog>
    <cd>
        <title>Empire Burlesque</title>
        <artist>
            <name>Bob</name>
            <surname>Dylan</surname>
        </artist>
        <country>USA</country>
        <company>Columbia</company>
        <price>10.90</price>
        <year>1985</year>
    </cd>
</catalog>

produces the wanted, correct result:

<catalog>
   <cd>
      <title>Empire Burlesque</title>
      <artist>
         <name>Bob</name>
         <surname>Dylan</surname>
         <middlename>XXX</middlename>
      </artist>
      <country>USA</country>
      <company>Columbia</company>
      <price>10.90</price>
      <year>1985</year>
      <comment>great one</comment>
   </cd>
   <cd>
      <title>Hide your heart</title>
      <artist>
         <name>Bonnie</name>
         <surname>Tyler</surname>
      </artist>
      <country>UK</country>
      <company>CBS Records</company>
      <price>9.90</price>
      <year>1988</year>
   </cd>
</catalog>
Dimitre Novatchev
And I thought that XSLT is easy... Thanks for a great answer!
Stefan
Unfortunatelly, the tool I'm using doesn't support the ducument() function. Is there a workaround for this (changing XSLT)?
Stefan
@Stefan: XSLT is easy indeed. The namespace cleanup I perform in this solution is not necessary when the updates will be really in a separate document.
Dimitre Novatchev
@Stefan: Every compliant XSLT processor supports the `document()` function. Maybe you just don't know how to enable it. What XSLT processor are you using?
Dimitre Novatchev
@Dimitre: I'm using XSLT with WiX. The WiX uses .NET libraries for XSLT. The .NET libraries by default disable document() function and scripts. To enable this in WiX I need to change it's source code... I need to make this thing work, so I fixed the WiX by hacking the dlls.
Stefan