1
votes

I'm having trouble inserting a block of text when a certain condition is met into an xml document using XSLT. Suppose I have the following xml:

<oldStep Name="Step1">
    <oldItems>
        <oldItem ItemName="item1">
        </oldItem>
        <!-- want to add an extra <Item> here if Step Name == "Step1" -->
        <oldItem ItemName="Step1item">
        </oldItem>
    </oldItems>
</oldStep>
<oldStep Name="Step2">
    <oldItems>
       ...
    </oldItems>
</oldStep>

Amongst the conversion of the old node names into new ones, I want to add an extra block of text (or a manually constructed node) whenever oldStep Name is equal to a certain value (in this case, Step1). I tried using the following XSLT, but it ended up with a weird behaviour in adding the block of text to every single node (sometimes even not under a node) in the xml once its matched. Also, I'm having trouble skipping the node so the node can be inserted within Items, not directly under oldStep:

<xsl:template match="oldItems">
    <xsl:element name="Item">
        <xsl:if test="../@Name = 'Step1'>
            <xsl:call-template name = "identity"></xsl:call-template>
        </xsl:if>
        <xsl:apply-templates />
    </xsl:element>
</xsl:template>

<xsl:template match="node()" name="identity">
    <xsl:element name="Item">
        <xsl:attribute name="ItemName">Step1item</xsl:attribute>
        </xsl:apply-templates />
    </xsl:element>
</xsl:template>

I get the feeling that I've gotten the conditions wrong, but googling didn't really help (search string too vague). What am I missing in the xsl? Or, is there a better approach to this problem?

Thanks.

3
Good Question (+1). See my answer for a complete solution.Dimitre Novatchev

3 Answers

1
votes

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()|@*" name="identity">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="oldStep[@Name='Step1']/*/oldItem[1]">
  <xsl:call-template name="identity"/>

  <Item ItemName='Step1item'/>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<steps>
    <oldStep Name="Step1">
        <oldItems>
            <oldItem ItemName="item1"/>
            <!-- want to add an extra <Item> here if Step Name == "Step1" -->
            <oldItem ItemName="Step1item"/>
        </oldItems>
    </oldStep>
    <oldStep Name="Step2">
        <oldItems>
       ...
        </oldItems>
    </oldStep>
</steps>

produces the wanted result:

<steps>
   <oldStep Name="Step1">
      <oldItems>
         <oldItem ItemName="item1"/>
         <Item ItemName="Step1item"/><!-- want to add an extra <Item> here if Step Name == "Step1" -->
         <oldItem ItemName="Step1item"/>
      </oldItems>
   </oldStep>
   <oldStep Name="Step2">
      <oldItems>
       ...
        </oldItems>
   </oldStep>
</steps>
1
votes

Your template named "identity" matches any node, so will apply it for every node it processes. Don't have it match anything.

1
votes

Your description is a little hard to follow. Here's how I interpret your question:

Give me a transformer that copies everything by default, except:

  • it converts oldItem elements into Item elements
  • it adds an extra item to the oldItems element under "step 1", as the last element

And here's a solution:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <!-- by default, copy everything -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
  <!-- special handling for old items -->
  <xsl:template match="oldItems">
    <!-- transform all old items first -->
    <xsl:apply-templates select="@*|node()"/>
    <xsl:if test="../@Name = 'Step1'">
      <xsl:call-template name="add-new-step1-stuff"/>
    </xsl:if>
  </xsl:template>
  <!-- add new step 1 stuff -->
  <xsl:template name="add-new-step1-stuff">
    <xsl:element name="Item">
      <xsl:attribute name="ItemName">Step1item</xsl:attribute>
    </xsl:element>
  </xsl:template>
  <!-- converts each oldItem to Item -->
  <xsl:template match="oldItem"> 
    <xsl:element name="Item">
      <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

which, when applied to:

<?xml version="1.0" encoding="utf-8"?>
<oldSteps>
  <oldStep Name="Step1">
    <oldItems>
      <oldItem ItemName="item1"></oldItem>
    </oldItems>
  </oldStep>
  <oldStep Name="Step2">
    <oldItems>
      <oldItem ItemName="item2"></oldItem>
      <oldItem ItemName="item3"></oldItem>
    </oldItems>
  </oldStep>
</oldSteps>

(not exactly what you gave, but what I think you were going for)

yields:

<?xml version="1.0"?>
<oldSteps>
  <oldStep Name="Step1">

      <Item ItemName="item1"/>
    <Item ItemName="Step1item"/>
  </oldStep>
  <oldStep Name="Step2">

      <Item ItemName="item2"/>
      <Item ItemName="item3"/>

  </oldStep>
</oldSteps>

which needs some whitespace cleanup, but I think is the structure you're looking for.

If you wanted block text instead of an extra item, you can simply rewrite the add-new-step1-stuff template like this:

<!-- add new step 1 stuff -->
<xsl:template name="add-new-step1-stuff">
  <xsl:text>
    Here's some block text that goes at the end.
  </xsl:text>
</xsl:template>

Key differences between my stylesheet and yours:

  • Your identity (copy-by-default) transform applies to every node in your document and produces an Item, within which you're copying the rest of the contents – which is why you're seeing Items all over the place! In mine, the identity transform and the addition of an element are separate. Note that the identity transform does not need to be called explicitly if you add apply-templates at the appropriate places.
  • Your transformations are missing the copying of attributes, which I imagine you want to keep instead.
  • Your transformation will add an Item after every oldItem in oldItems underneath Step 1, which I'm also imagining is not what you want.