Search code examples
xpathxquerybasex

XQuery / BaseX - Limit depth of result


When using XPath or XQuery, is there a way to limit the depth of the result?

I am using BaseX, which supports XQuery 3.1 and XSLT 2.0.

For example, given this input document:

<country name="United States">
  <state name="California">
    <county name="Alameda" >
      <city name="Alameda" />
      <city name="Oakland" />
      <city name="Piedmont" />
    </county>
    <county name="Los Angeles">
      <city name="Los Angeles" />
      <city name="Malibu" />
      <city name="Burbank" />
    </county>
    <county name="Marin">
      <city name="Fairfax" />
      <city name="Larkspur" />
      <city name="Ross" />
    </county>
    <county name="Sacramento">
      <city name="Folsom" />
      <city name="Elk Grove" />
      <city name="Sacramento" />
    </county>
  </state>
</country>

If I execute this query: /country/state, I get the following result:

<state name="California">
  <county name="Alameda">
    <city name="Alameda"/>
    <city name="Oakland"/>
    <city name="Piedmont"/>
  </county>
  <county name="Los Angeles">
    <city name="Los Angeles"/>
    <city name="Malibu"/>
    <city name="Burbank"/>
  </county>
  <county name="Marin">
    <city name="Fairfax"/>
    <city name="Larkspur"/>
    <city name="Ross"/>
  </county>
  <county name="Sacramento">
    <city name="Folsom"/>
    <city name="Elk Grove"/>
    <city name="Sacramento"/>
  </county>
</state>

I would like to limit the depth of the result. Ideally, there'd be a way for me to specify the depth, rather than hard-coding an XPath query.

As an example, I would like to limit the result to the result nodes and its children, but not including the grandchildren, so the result would be:

<state name="California">
  <county name="Alameda" />
  <county name="Los Angeles" />
  <county name="Marin" />
  <county name="Sacramento" />
</state>

Solution

  • One easy and straightforward way is to use XSLT-2.0 with an empty template cancelling all children of <county>. The <xsl:strip-space> removes the space that would have been used by the children.

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
      <xsl:strip-space elements="*" />
     
      <!-- Identity template -->
      <xsl:template match="@* | node()">
        <xsl:copy>
          <xsl:apply-templates select="@* | node()" />
        </xsl:copy>
      </xsl:template>
      
      <xsl:template match="/">
          <xsl:apply-templates select="/country/state" />
      </xsl:template>
      
      <xsl:template match="county/*" />
      
    </xsl:stylesheet>
    

    Output is:

    <?xml version="1.0" encoding="UTF-8"?>
    <state name="California">
        <county name="Alameda"/>
        <county name="Los Angeles"/>
        <county name="Marin"/>
        <county name="Sacramento"/>
    </state>
    

    With XQuery, a solution could look like this:

    for $st in doc("b.xml")/country/state return
      element { node-name($st) } { $st/@*,
      for $ct in $st/county return 
        element { node-name($ct) } { $ct/@* }
      }
    

    The output is the same.