1
votes

This is similar to this question: XSLT-1.0: How to insert an element at a specific position with respect to the parent element, but the answer there doesn't cover the case where tag3 is missing in the input XML

I want to insert or update a tag (say tag4) in the an XML document that looks like the following, except that tag1, tag2, tag3, tag4 and tag5 are all optional.

<Begin>
    <tag1 value="a" />
    <tag2 value="b" />
    <tag3 value="c" />
    <tag4 value="d" />
    <tag5 value="e" />
</Begin>

I.e. the following are sample inputs and outputs

Input:

<Begin>
</Begin>

Output:

<Begin>
    <tag4 value="updated" />
</Begin>

Input:

<Begin>
    <tag4 value="d" />
</Begin>

Output:

<Begin>
    <tag4 value="updated" />
</Begin>

Input:

<Begin>
    <tag1 value="a" />
    <tag5 value="e" />
</Begin>

Output:

<Begin>
    <tag1 value="a" />
    <tag4 value="updated" />
    <tag5 value="e" />
</Begin>

Input:

<Begin>
    <tag1 value="a" />
    <tag2 value="b" />
    <tag5 value="e" />
</Begin>

Output:

<Begin>
    <tag1 value="a" />
    <tag2 value="b" />
    <tag4 value="updated" />
    <tag5 value="e" />
</Begin>

Input:

<Begin>
    <tag1 value="a" />
    <tag2 value="b" />
    <tag3 value="c" />
    <tag5 value="e" />
</Begin>

Output:

<Begin>
    <tag1 value="a" />
    <tag2 value="b" />
    <tag3 value="c" />
    <tag4 value="updated" />
    <tag5 value="e" />
</Begin>

UPDATE

I also want to be able to preserve any attributes that may already be present on the Begin or tag4 element, e.g.:

Input:

<Begin someAttribute="someValue">
    <tag1 value="a" />
    <tag2 value="b" />
    <tag3 value="c" />
    <tag4 value="d" someOtherAttribute="someOtherValue" />
    <tag5 value="e" />
</Begin>

Output:

<Begin someAttribute="someValue">
    <tag1 value="a" />
    <tag2 value="b" />
    <tag3 value="c" />
    <tag4 value="updated" someOtherAttribute="someOtherValue" />
    <tag5 value="e" />
</Begin>
2
Do you know the names of the attributes "that may already be present" on the tag4 element?michael.hor257k
@michael.hor257k - no I don't, they are subject to change. So I essentially want to copy the tag4 element, then add/update my "value" attribute if the tag4 element is present, or create it if not.Joe
Then either copy all of tag4's attributes and then override the value attribute, or copy all its attributes except value. For the Begin element, you can simply copy all its attributes before any of its children (or together with the first batch of its children)..michael.hor257k
@michael.hor257k - yes, that's essentially what I expect to do. Michael Kay has given me most of what I need and I'm sure I'll be able to work out the rest on my own (unless he's kind enough to update his answer!). Once I've done this I'll accept his answer.Joe
My suggestions are on top of the answer given by Dr. Kay. I would have given you the same answer (minus the typo on line1, hopefully). Here's an example of implementation of the 2nd option I mentioned: xsltfiddle.liberty-development.net/ehVZvvVmichael.hor257k

2 Answers

1
votes

Try:

<xsl:template match="Begin">
  <Begin>
    <xsl:copy-of select="tag1 | tag2 | tag3"/>
    <tag4 value="updated"/>
    <xsl:copy-of select="tag5"/>
  </Begin>
</xsl:template>
0
votes

Give this a try... Trace it through in a debugger to help you figure it out, if needed.

    <xsl:template match="Begin">
      <xsl:copy>
        <!-- Apply any attributes for Begin.  -->
        <xsl:apply-templates select="@*"/>

        <!-- Output the tag* nodes before tag4. -->
        <xsl:apply-templates select="tag1|tag2|tag3"/>

        <xsl:choose>
          <!-- Is there already a tag4? -->
          <xsl:when test="tag4">
            <xsl:apply-templates select="tag4"/>
          </xsl:when>
          <xsl:otherwise>
            <!-- Create tag4 -->
            <tag4 value="updated"/>
          </xsl:otherwise>
        </xsl:choose>

        <!-- Output the tag* nodes after tag4. -->
        <xsl:apply-templates select="tag5"/>
      </xsl:copy>
    </xsl:template>

    <xsl:template match="tag4">
      <xsl:copy>
        <xsl:attribute name="value">d</xsl:attribute>
        <xsl:attribute name="someOtherAttribute">someOtherValue</xsl:attribute>
        <xsl:apply-templates select="node()"/>
      </xsl:copy>
    </xsl:template>

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