XQuery to generate a catalog of EXPath package XSLT stylesheets
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>Written for BaseX 12.4. BaseX may have changed since: please check the current documentation.