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>