views:

475

answers:

2

Hi Everyone, I have a small question regarding XSLT template overriding. For this segment of my XML:

<record>
  <medication>
    <medicine>
      <name>penicillin G</name>
      <strength>500 mg</strength>
    </medicine>
  </medication>
</record>

In my XSLT sheet, I have two templates in the following order:

 <xsl:template match="medication">
   <xsl:copy-of select="." />
   </xsl:template>
   <xsl:template match="medicine/name">
   <text>!unauthorized information!</text>
 </xsl:template>

What I want to do is to copy everything under the medication element to the output other than the "name" element (or any other element that I explicitly define). The final xml will be shown to the user in RAW XML form. In other words, the result I want is:

<record>
  <medication>
    <medicine>
      <text>! unauthorized information!</text>
      <strength>500 mg</strength>
    </medicine>
  </medication>
</record>

Whereas I am getting the same XML as input, i.e. without the element replaced by text. Any ideas why the second template match is not overriding the name element in the first one? Thanks in advance

-- Ali

+2  A: 

Add

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

to your <xsl:template match="medicine/name">

And remove <xsl:template match="medication"> altogether!

<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="1.0">

 <xsl:template match="medicine/name">
   <text>!unauthorized information!</text>
 </xsl:template>

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

</xsl:stylesheet>
alamar
Thanks for the reply alamar. I cannot remove the medication template as this is only a chunk of a bigger XML with many other elements. So first I want to say display all of the medication element as it is, BUT if any template rule follows, like the name one, then replace that by text. Hope that clarifies.
Well, you can as well state <xsl:template match="medication"> <medication> <xsl:apply-templates select="*" /> </medication> </xsl:template>
alamar
+1  A: 

Template order does not matter. The only case it possibly becomes considered (and this is processor-dependent) is when you have an un-resolvable conflict, i.e. an error condition. In that case, it's legal for the XSLT processor to recover from the error by picking the one that comes last. However, you should never write code that depends on this behavior.

In your case, template priority isn't even the issue. You have two different template rules, one matching <medication> elements and one matching <name> elements. These will never collide, so it's not a question of template priority or overriding. The issue is that your code never actually applies templates to the <name> element. When you say <xsl:copy-of select="."/> on <medication>, you're saying: "perform a deep copy of <medication>". The only way any of the template rules will fire for descendant nodes is if you explicitly apply templates (using <xsl:apply-templates/>.

The solution I have for you is basically the same as alamar's, except that it uses a separate processing "mode", which isolates the rules from all other rules in your stylesheet. The generic match="@* | node()" template causes template rules to be recursively applied to children (and attributes), which gives you the opportunity to override the behavior for certain nodes.

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

  <!-- ...placeholder for the rest of your code... -->
  <xsl:template match="/record">
    <record>
      <xsl:apply-templates/>
    </record>
  </xsl:template>
  <!-- end of placeholder -->

  <xsl:template match="medication">
    <!-- Instead of copy-of, whose behavior is to always perform
         a deep copy and cannot be customized, define your own
         processing mode. Rules with this mode name are isolated
         from the rest of your code. -->
    <xsl:apply-templates mode="copy-medication" select="."/>
  </xsl:template>

          <!-- By default, copy all nodes and their descendants -->
          <xsl:template mode="copy-medication" match="@* | node()">
            <xsl:copy>
              <xsl:apply-templates mode="copy-medication" select="@* | node()"/>
            </xsl:copy>
          </xsl:template>

          <!-- But replace <name> -->
          <xsl:template mode="copy-medication" match="medicine/name">
            <text>!unauthorized information!</text>
          </xsl:template>

</xsl:stylesheet>

The rule for "medicine/name" overrides the rule for "@* | node()", because the format of the pattern (which contains a "/") makes its default priority (0.5) higher than the default priority of "node()" (-1.0).

A complete but concise description of how template priority works can be found in "How XSLT Works" on my website.

Finally, I noticed you mentioned you want to display "RAW XML" to the user. Does that mean you want to display, for example, the XML, with all the start and end tags, in a browser? In that case, you'd need to escape all markup (e.g., "&lt;" for "<"). Check out the XML-to-string utility on my website. Let me know if you need an example of how to use it.

Evan Lenz