Insert XML node at a specific position of an existing document Insert XML node at a specific position of an existing document xml xml

Insert XML node at a specific position of an existing document


[Replaced my last answer. Now I understand better what you need.]

Here's an XSLT 2.0 solution:

<xsl:stylesheet version="2.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">  <xsl:template match="/root">    <xsl:variable name="elements-after" select="t|u|v|w|x|y|z"/>    <xsl:copy>      <xsl:copy-of select="* except $elements-after"/>      <s>new node</s>      <xsl:copy-of select="$elements-after"/>    </xsl:copy>  </xsl:template></xsl:stylesheet>

You have to explicitly list either the elements that come after or the elements that come before. (You don't have to list both.) I would tend to choose the shorter of the two lists (hence "t" - "z" in the above example, instead of "a" - "r").

OPTIONAL ENHANCEMENT:

This gets the job done, but now you need to maintain the list of element names in two different places (in the XSLT and in the schema). If it changes much, then they might get out of sync. If you add a new element to the schema but forget to add it to the XSLT, then it won't get copied through. If you're worried about this, you can implement your own sort of schema awareness. Let's say your schema looks like this:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">  <xs:element name="root">    <xs:complexType>      <xs:sequence>        <xs:element name="a" type="xs:string"/>        <xs:element name="r" type="xs:string"/>        <xs:element name="s" type="xs:string"/>        <xs:element name="t" type="xs:string"/>        <xs:element name="z" type="xs:string"/>      </xs:sequence>    </xs:complexType>  </xs:element></xs:schema>

Now all you need to do is change your definition of the $elements-after variable:

  <xsl:variable name="elements-after" as="element()*">    <xsl:variable name="root-decl" select="document('root.xsd')/*/xs:element[@name eq 'root']"/>    <xsl:variable name="child-decls" select="$root-decl/xs:complexType/xs:sequence/xs:element"/>    <xsl:variable name="decls-after" select="$child-decls[preceding-sibling::xs:element[@name eq 's']]"/>    <xsl:sequence select="*[local-name() = $decls-after/@name]"/>  </xsl:variable>

This is obviously more complicated, but now you don't have to list any elements (other than "s") in your code. The script's behavior will automatically update whenever you change the schema (in particular, if you were to add new elements). Whether this is overkill or not depends on your project. I offer it simply as an optional add-on. :-)


You must use a brute force search since you have no static path to find the insert location. My approach would be to use a SAX parser and read the document. All nodes are copied to the output unmodified.

You'll need a flag sWasWritten which is why you can't use a normal XSLT tool; you need one where you can modify variables.

As soon as I see a node > r (t, u, ..., z) or the end-tag of the root node, I'd write the s node unless sWasWritten was true and set the flag sWasWritten.


An XPath solution:

/root/(.|a|r)[position()=last()]

You must explicitly include all the nodes up to the one you want, so that you'll need a different XPath expression for each node you want to insert after. For example, to place it immediately after <t> (if it exists):

/root/(.|a|r|t)[position()=last()]

Note the special case of when none of the preceding nodes are present: it returns <root> (the "."). You'll need to check for this, and insert the new node as the first child of root, instead of after it (the usual case). This isn't so bad: you'd have to handle this special case in some way, anyway. Another way to handle this special case is the following, which returns 0 nodes if there are no preceding nodes.

/root/(.|a|r|t)[position()=last() and position()!=1]

Challenge: can you find a better way to handle this special case?