Saxon Extension Functions

XPath Extension Functions in Saxon

Saxon 726,956 's own functions live in http://saxon.sf.net/ 6,423 (saxon:highest(), saxon:index(), saxon:evaluate() and dozens more), next to the EXPath File, Binary and Archive modules. All need Saxon-PE or EE; HE rejects them with XPST0017 and the message "Saxon extension functions are not available under Saxon-HE".

HE does support integrated extension functions: a Java object that declares a name, argument types and result type, registered on the Processor before compiling. This one adds bn:slug(), which strips accents, apostrophes and punctuation from a title to make a URL slug:

Slug.java: an integrated extension function registered with Saxon-HEJava
import java.io.File;
import java.text.Normalizer;
import net.sf.saxon.s9api.*;
public class Slug {
  public static void main(String[] args) throws SaxonApiException {
    Processor proc = new Processor(false);              // false: Saxon-HE, no licence
    SequenceType str = SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ONE);
    proc.registerExtensionFunction(new ExtensionFunction() {
      public QName getName() { return new QName("urn:booknest:fn", "slug"); }
      public SequenceType getResultType() { return str; }
      public SequenceType[] getArgumentTypes() { return new SequenceType[] {str}; }
      public XdmValue call(XdmValue[] a) {
        String s = Normalizer.normalize(a[0].itemAt(0).getStringValue(), Normalizer.Form.NFD);
        return new XdmAtomicValue(s.toLowerCase().replaceAll("\\p{M}|'", "")
            .replaceAll("[^a-z0-9]+", "-").replaceAll("^-|-$", ""));
      }
    });
    XPathCompiler xpath = proc.newXPathCompiler();
    xpath.declareNamespace("bn", "urn:booknest:fn");
    XdmNode doc = proc.newDocumentBuilder().build(new File(args[0]));
    for (XdmItem i : xpath.evaluate(args[1], doc)) System.out.println(i.getStringValue());
  }
}

Java 11 and later run a single source file directly; this needs the JDK from XML in the Data Pipeline, not just a JRE:

Calling bn:slug() from XPathShell
java -cp ~/xml-tools/saxon/saxon-he-12.10.jar Slug.java booknest-catalog.xml \
  '//book[position() ge 5]/title ! bn:slug(.), bn:slug("Café Noir: Tome 2")'
Output
the-clockmakers-paradox
gardens-in-glass
cafe-noir-tome-2

Your own namespace keeps queries portable, since another processor (BaseX 693,830 has Java modules) can supply its own bn:slug(); probe for it with function-lookup().