XLST Recursive Coalesce?

XLST Recursive Coalesce?

Problem Description:

I have a sparse dataset where three related elements should be coalesced into one. In this example they are a, b, and c, and the order of preference is also a, b, and c. An element is skipped if it missing or has empty-ish content.

Source Data

<?xml version="1.0" encoding="utf-8"?>
<root>
  <row>
    <a>foo1</a>
    <b>foo1.1</b>
    <c>foo1.1</c>
  </row>
  <row>
    <b>foo2.1</b>
    <c>foo2.2</c>
  </row>
  <row>
    <a></a>
    <b>foo3.2</b>
  </row>
  <row>
    <c>foo4.3</c>
  </row>
</root>

Ideal Output

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <row>
      <a>foo1</a>
   </row>
   <row>
      <a>foo2.1</a>
   </row>
   <row>
      <a>foo3.2</a>
   </row>
   <row>
      <a>foo4.3</a> <!-- My current stylesheet emits 'b' here -->
   </row>
</root>

Acceptable Output
Redundant values left in document.. they’ll be ignored anyway

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <row>
      <a>foo1</a>
      <b>foo1.1</b>
      <c>foo1.1</c>
   </row>
   <row>
      <a>foo2.1</a>
      <c>foo2.2</c>
   </row>
   <row>
      <a>foo3.2</a>
   </row>
   <row>
      <a>foo4.3</a>
   </row>
</root>

My Stylesheet

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >

    <xsl:output method="xml" indent="yes"/>
    
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    
    <!-- throw out blanks -->
    <xsl:template match="b[normalize-space(text()) = '']" />
    <xsl:template match="a[normalize-space(text()) = '']" />

  <!-- bubble c up to b -->
  <xsl:template match="c[normalize-space(../b) = '']">
        <b>
            <xsl:apply-templates />
        </b>
    </xsl:template>
    

  <!-- bubble b up to a -->
    <xsl:template match="b[normalize-space(../a) = '']">
        <a>
            <xsl:apply-templates />
        </a>
    </xsl:template>
    
</xsl:stylesheet>

I assumed that the functional nature of XSLT would cause the new instances of b and c to be run through the preceding template, "bubbling" them to the top. If in the xsl:apply-templates element I could specify the new name I think I’d be in business.

My problem space does have a fixed recursion depth of three so I could solve this problem with an additional template which promotes c up to c

Context: Saxon-HE v10 (so XLST 3.0)

XSLT Fiddle is a great tool for working with this stuff.

Solution – 1

I don’t know what "recursive coalesce" or "bubbling" means. It seems to me that the result you show could be produced simply by:

XSLT 3.0

<xsl:stylesheet version="3.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:mode on-no-match="shallow-copy"/>

<xsl:template match="row">
    <xsl:copy>
        <a>
            <xsl:value-of select="(a, b, c)[normalize-space(.)][1]"/>
        </a>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>
Rate this post
We use cookies in order to give you the best possible experience on our website. By continuing to use this site, you agree to our use of cookies.
Accept
Reject