[xsl] How to extract an element plus all the elements it references plus all the elements referenced by the referenced elements plus ...

Subject: [xsl] How to extract an element plus all the elements it references plus all the elements referenced by the referenced elements plus ...
From: "Costello, Roger L. costello@xxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Fri, 8 Jun 2018 18:18:48 -0000
Hi Folks,

I have instance documents containing a bunch of <airport> elements. I want to
extract one of the <airport> elements. But the <airport> element might contain
IDREF elements, so the result document must include the <airport> element and
the referenced elements. Those referenced elements might contain IDREF
elements, so the result document must include those referenced elements as
well. And on and on and on. Further, while collecting those referenced
elements, there might be a reference to an element that has already been
collected; I don't want to collect elements twice.

Here is a sample instance document:

<Airports>
    <airport id="A">
        <ICAO>KBOS</ICAO>
        <ref>A-1</ref>
    </airport>
    <airport id="B">
        <ICAO>KSTL</ICAO>
        <ref>B-1</ref>
    </airport>
    <runways id="A-1">
        <runway>
            <ref>A-1-1</ref>
        </runway>
        <runway>
            <ref>A-1-2</ref>
        </runway>
    </runways>
    <runways id="B-1">
        <runway>
            <ref>B-1-1</ref>
        </runway>
    </runways>
    <gate id="A-1-1">
        <ref>A</ref>
    </gate>
    <gate id="A-1-2" />
    <gate id="B-1-1" />
</Airports>

I would like to extract the <airport> element with ICAO = KBOS (Boston
airport). The following result document contains the desired <airport> element
plus all the recursively referenced elements:

<Result>
    <airport id="A">
        <ICAO>KBOS</ICAO>
        <ref>A-1</ref>
    </airport>
    <runways id="A-1">
        <runway>
            <ref>A-1-1</ref>
        </runway>
        <runway>
            <ref>A-1-2</ref>
        </runway>
    </runways>
    <gate id="A-1-1">
        <ref>A</ref>
    </gate>
    <gate id="A-1-2" />
</Result>

I've written an XSLT program to do the job. See below. I am wondering if there
is a better (simpler, clearer) solution?

/Roger

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
    xmlns:f="function"
    exclude-result-prefixes="f"
    version="2.0">

    <xsl:output method="xml" />

    <xsl:variable name="doc" select="/"/>

    <xsl:template match="Airports">
        <Results>
            <xsl:sequence
select="f:get-elements-and-their-referenced-elements(airport[ICAO eq 'KBOS'],
foo)" />
        </Results>
    </xsl:template>

    <xsl:function name="f:get-elements-and-their-referenced-elements"
as="element()*">
        <xsl:param name="elements-to-process" as="element()*" />
        <xsl:param name="processed-elements" as="element()*" />

        <xsl:choose>
            <xsl:when test="not($elements-to-process)">
                <xsl:sequence select="$processed-elements" />
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="referenced-elements" as="element()*">
                    <xsl:for-each select="$elements-to-process//ref">
                        <xsl:variable name="ref" select="." />
                        <xsl:sequence select="$doc//*[@id eq $ref]" />
                    </xsl:for-each>
                </xsl:variable>
                <xsl:variable name="new-elements-to-process"
select="$referenced-elements except $processed-elements" />
                <xsl:variable name="new-processed-elements"
select="$processed-elements union $new-elements-to-process" />
                <xsl:sequence
select="f:get-elements-and-their-referenced-elements($new-elements-to-process,
$new-processed-elements)" />
            </xsl:otherwise>
        </xsl:choose>

    </xsl:function>

</xsl:stylesheet>

Current Thread