XQuery to generate a catalog of EXPath package XSLT stylesheets

Tuesday, 13 October 2026 · 1 message
CA
Clark, Ash13 Oct 2026 18:18 #

Hi all,

Sharing this in case it helps anyone else!

I have a semi-public EXPath package<https://wwp.northeastern.edu/utils/lib/expath-pkg.xml> that is mainly XQuery functions, with a few of those functions also implemented in XSLT. Last week I ran into an issue where having the XSLT implementation of a function would help immensely, so I set out to figure out how to make BaseX recognize the XSLT import. To my great pleasure, BaseX's documentation on catalog resolvers<https://docs.basex.org/main/Catalog_Resolver> showed me exactly what I needed.

I wrote an XQuery to generate an XML catalog for all EXPath packages with defined XSLT components in a BaseX repository. I ran it in the DBA editor, saved the catalog to my BaseX directory, then updated the CATALOG option in .basex . After a restart of BaseX, Saxon HE was able to import my package's XSLT using its URI. I'm so pleased — it's been years since I set up the XSLT implementation and this is the first time I've been able to use its functions!

The XQuery is in a gist<https://gist.github.com/amclark42/a34501034d2b3fc13b5fce3b77605dc0>, and I've copied it below. Feel free to use! Note that BaseX comes with the FunctX 1.0 package — you could absolutely use this approach to get at the XSLT functions with a simple <xsl:import href="http://www.functx.com/functx.xsl"/>.

A caveat: Whenever my EXPath package is updated, I'll have to remove the old package and regenerate the catalog to reflect the new path. The added overhead is still worth it, I feel.

Warmly, Ash Clark

xquery version "3.1";

(:  LIBRARIES  :)
  declare namespace db="http://basex.org/modules/db";
  declare namespace file="http://expath.org/ns/file";
(:  NAMESPACES  :)
  declare default element namespace "urn:oasis:names:tc:entity:xmlns:xml:catalog";
  declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization";
  declare namespace pkg="http://expath.org/ns/pkg";
(:  OPTIONS  :)
  declare option output:indent "yes";
  declare option output:method "xml";

(:~
  Find all EXPath Package <xslt>s in a BaseX Repository and generate an XML catalog to map their
  filesystem paths to their URIs. With a catalog saved to the filesystem, you can include it in the
  BaseX "CATALOG" option (https://docs.basex.org/main/Options#catalog). You may need to place Norm
  Tovey-Walsh's Catalog Resolver on your classpath in order to use the catalog entry URIs in
  <xsl:import>s (see https://docs.basex.org/main/Catalog_Resolver for more information).

  EXPath Packaging System specification: https://expath.org/spec/pkg

  @author Ash Clark
  @since 2026
 :)

(:
    MAIN QUERY
 :)

(: Get the path to BaseX's repository. :)
let $repoDir :=
  let $optStr := db:option('repopath')
  return
    (: Make sure the path ends with "/". :)
    if ( ends-with($optStr, '/') ) then $optStr else $optStr||'/'
let $xslPackages :=
  (: Identify the directories within the repository. Each directory should correspond with a package,
    either EXPath or internal. We're only interested in the EXPath ones. :)
  for $packagePath in file:children($repoDir)[ends-with(., '/')]
  (: Try to identify the EXPath package descriptor in the package directory. The EXPath package standard
    says the descriptor should always be in the outermost directory and named "expath-pkg.xml". If the
    file isn't there, we can ignore this directory. :)
  let $packageDescriptor :=
    let $path := concat($packagePath,'expath-pkg.xml')
    return
      if ( not(doc-available($path)) ) then () else
        (: We can also ignore this directory if it HAS a package descriptor but no <xslt> listing. :)
        doc($path)/pkg:package[pkg:xslt]
  (: Filter out directories so we're only looking at EXPath packages with defined XSLT components. :)
  where exists($packageDescriptor)
  (: Gather information on the XSLT available through this XSLT package. :)
  let $packageUri := $packageDescriptor/@name/data(.)
  (: Get a sequence of subdirectories within the package directory for later testing. :)
  let $packageSubdirs := file:children($packagePath)[ends-with(., '/')]
  (: The EXPath package spec allows multiple <xslt>s can be defined. Each one should get its own catalog
    entry. :)
  for $xsltConfig in $packageDescriptor/pkg:xslt
  let $stylesheetPath :=
    let $relativePath := $xsltConfig/pkg:file/data(.)
    (: According to the EXPath package spec, the usable components of the package should be placed in a
      content/ directory alongside the package descriptor. However, FunctX's package (at least in BaseX)
      uses a directory named functx/ instead. Since <file> is supposed to be relative to the content
      directory (it doesn't specify which directory it's in), we need to try to identify where the
      stylesheet is stored in order to generate a catalog entry for its filesystem path. :)
    return
      (: Try using the base package path too, just in case. While there's a spec for the EXPath
        packaging system, I get the sense that people don't always use it as intended. (For years I
        didn't!) Someone may have decided to use a relative path from the EXPath descriptor file's
        directory, or placed the XSLT in that directory. :)
      for $directory in ($packagePath, $packageSubdirs)
      let $tryStylesheetPath := 'file://'||$directory||$relativePath
      return
        if ( doc-available($tryStylesheetPath) ) then
          $tryStylesheetPath
        else ()
  return
    if ( empty($stylesheetPath) ) then () else
      <uri name="{ $xsltConfig/pkg:import-uri/data(.) }" uri="{ $stylesheetPath }"/>
return
  (: Gather any catalog entries into an XML catalog. :)
  if ( empty($xslPackages) ) then () else
    <catalog>
      { $xslPackages }
    </catalog>

⚡Generated with XQuery