Re: [xsl] inserting a child element while honoring the parent element's content model

Subject: Re: [xsl] inserting a child element while honoring the parent element's content model
From: "Chris Papademetrious christopher.papademetrious@xxxxxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Sat, 25 Feb 2023 02:05:26 -0000
Hi everyone,

Here is a template to perform content-model-aware element insertion (more to
come):


  <!-- perform content-aware insertion on an element -->
  <xsl:template match="*" mode="insert-stuff">
    <xsl:param name="path" as="xs:string*"/>
    <xsl:param name="content" as="element()*" tunnel="yes"/>

    <!-- if path levels remain to recurse, get the next path level -->
    <xsl:variable name="path-element-name" select="head($path)"
as="xs:string?"/>
    <xsl:variable name="path-element-original" select="*[name() eq
$path-element-name]" as="element()?"/>

    <!-- choose what to insert in this current element (the "parent") -->
    <xsl:variable name="parent-name" select="name()" as="xs:string"/>
    <xsl:variable name="insert-in-parent" as="node()*">
      <xsl:choose>
        <xsl:when test="$path-element-name">
          <!-- we are still recursing the path - create or modify the next
path element -->
          <xsl:variable name="path-element-to-process" as="element()">
            <xsl:choose>
              <xsl:when test="$path-element-original">
                <!-- the path element exists at this level; use it -->
                <xsl:sequence select="$path-element-original"/>
              </xsl:when>
              <xsl:otherwise>
                <!-- the path element does not exist at this level; create it
-->
                <xsl:element name="{$path-element-name}"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:variable>
          <!-- recursively process this path element, then insert it into its
parent below -->
          <xsl:apply-templates select="$path-element-to-process"
mode="#current">
            <xsl:with-param name="path" select="tail($path)"
as="xs:string*"/>
          </xsl:apply-templates>
        </xsl:when>
        <xsl:otherwise>
          <!-- we've reached the end of the path - insert the content at this
level -->
          <xsl:sequence select="$content"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <!-- get the content model we must respect in this current element (the
"parent") -->
    <xsl:if test="empty($content-models($parent-name))">
      <xsl:message terminate="yes" expand-text="yes">No content model found
for '{$parent-name}'.</xsl:message>
    </xsl:if>
    <xsl:variable name="content-model" select="$content-models($parent-name)"
as="xs:string+"/>

    <!--iteratively insert one element at a time into the parent -->
    <xsl:iterate select="$insert-in-parent">
      <xsl:param name="current-parent" select="." as="element()"/>
      <xsl:on-completion select="$current-parent"/>
      <xsl:variable name="this-element-to-insert" select="." as="element()"/>

      <!-- compute an updated parent element with this content element
inserted -->
      <xsl:variable name="new-parent" as="element()">
        <!-- get all content up to (and including) the current insertion
element type, per the content model -->
        <xsl:variable name="this-element-name"
select="name($this-element-to-insert)" as="xs:string"/>
        <xsl:if test="empty(index-of($content-model, $this-element-name))">
          <xsl:message terminate="yes"
expand-text="yes">'{$this-element-name}' not found in content model for
'{$parent-name}'.</xsl:message>
        </xsl:if>
        <xsl:variable name="preceding-elements"
select="$current-parent/*[name() = $content-model[position() &lt;=
index-of($content-model, $this-element-name)]]" as="element()*"/>
        <xsl:variable name="preceding-nodes"
select="$preceding-elements/(.|preceding-sibling::node())" as="node()*"/>
        <!-- create the updated parent element -->
        <xsl:copy select="$current-parent">
          <!-- include the preceding content (if we updated an existing path
element, don't include the original) -->
          <xsl:sequence select="@*, $preceding-nodes except
$path-element-original"/>
          <!-- insert this element -->
          <xsl:sequence select="$this-element-to-insert"/>
          <!-- include the following content -->
          <xsl:sequence select="node() except ($preceding-nodes)"/>
        </xsl:copy>
      </xsl:variable>
      <xsl:next-iteration>
        <xsl:with-param name="current-parent" select="$new-parent"/>
      </xsl:next-iteration>
    </xsl:iterate>
  </xsl:template>

Current Thread