Does XSLT create an element in order when sibling elements arbitrarily exist?
Suppose you have an element with a content model like this:
<!ELEMENT wrapper (a*,b*,c*,d*,e*,f*,g*,h*,i*,j*,k*,l*,m*,n*,o*,p*,q*,r*,s*,t*,u*,v*,w*,x*,y*,z*)>
In other words, within a wrapper element, there is a specific order of children that can exist arbitrarily.
You need to create a new element (like m) inside the wrapper, keep what was already there, and make sure the result matches the content model.
This is a kind of solution:
<xsl:template match="@*|node()">
<xsl:sequence select="."/>
</xsl:template>
<xsl:template match="wrapper">
<xsl:copy>
<xsl:apply-templates select="a,b,c,d,e,f,g,h,i,j,k,l,m"/>
<m>This is new</m>
<xsl:apply-templates select="n,o,p,q,r,s,t,u,v,w,x,y,z"/>
</xsl:copy>
</xsl:template>
However, this solution will remove all whitespace, comment, or handling elements inside the wrapper element. I've come up with some solutions that don't throw these things, but none that I enjoy.
What's the most elegant solution to this problem that won't delete nodes? XSLT 3 solutions and schemas are well known.
Here are some examples of inputs and outputs:
<!-- Input -->
<wrapper/>
<!-- Output -->
<wrapper><m>This is new</m></wrapper>
<!-- Input -->
<wrapper><a/><z/></wrapper>
<!-- Output -->
<wrapper><a/><m>This is new</m><z/></wrapper>
<!-- Input -->
<wrapper><m/></wrapper>
<!-- Output -->
<wrapper><m/><m>This is new</m></wrapper>
<!-- Input -->
<wrapper>
<a/>
<!-- put m here -->
<z/>
</wrapper>
<!-- Output -->
<!-- NB: The comment and whitespace could come after the inserted element instead. This case is ambiguous -->
<wrapper>
<a/>
<!-- put m here --><m>This is new</m>
<z/>
</wrapper>
<!-- Input -->
<wrapper>
<a/>
<b/>
<c/>
<n/>
<o/>
<!-- p is not here -->
<?do not drop this?>
</wrapper>
<!-- Output -->
<wrapper>
<a/>
<b/>
<c/><m>This is new</m>
<n/>
<o/>
<!-- p is not here -->
<?do not drop this?>
</wrapper>
It is not critical that non-element nodes around the inserted element come before or after, they just are not discarded and their order relative to the original elements is preserved.
source to share
You can look at it here:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="wrapper">
<xsl:variable name="all" select="node()"/>
<xsl:variable name="middle" select="(n,o,p,q,r,s,t,u,v,w,x,y,z)[1]"/>
<xsl:variable name="i" select="if($middle) then count($all[. << $middle]) else count($all)"/>
<xsl:variable name="new">
<m>This is new</m>
</xsl:variable>
<xsl:copy>
<xsl:apply-templates select="insert-before($all, $i+1, $new) "/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Or, if you prefer:
<xsl:template match="wrapper">
<xsl:variable name="all" select="node()"/>
<xsl:variable name="middle" select="(a,b,c,d,e,f,g,h,i,j,k,l,m)[last()]"/>
<xsl:variable name="i" select="if($middle) then index-of($all/generate-id(), generate-id($middle)) else 0"/>
<xsl:variable name="new">
<m>This is new</m>
</xsl:variable>
<xsl:copy>
<xsl:apply-templates select="insert-before($all, $i+1, $new) "/>
</xsl:copy>
</xsl:template>
source to share