Register XML DTDs with update-xmlcatalog

An XML tool suddenly unable to find a DTD it found yesterday usually means the catalogue was hand-edited, and update-xmlcatalog is the fix.

It updates catalogue files used by XML tools, not the DTD or schema file itself.

Allow about 15 minutes for a single entry, including a backup and verification. This guide describes xml-core version 0.19 on the local system. The installed man page is dated 12 December 2023, so treat the exact command behaviour below as version-specific.

1. Check the package and choose the catalogue scope

Confirm the command is installed before changing anything:

$ dpkg-query -W -f='${Package} ${Version}\n' xml-core
xml-core 0.19
$ command -v update-xmlcatalog
/usr/sbin/update-xmlcatalog

The entry type must be one of public, system or uri. The identifier is the exact public identifier, system URL or URI an XML consumer will request: keep the spelling and capitalisation exact.

Checkpoint: write down the identifier, type, package name and catalogue path before continuing. Most mistakes here are mismatched identifiers, not XML syntax errors.

2. Back up the system catalogue

Adding a root entry changes a system file and normally requires root privileges. Make a timestamped copy first; this command only reads the existing file and writes the backup:

$ sudo cp --preserve=all /etc/xml/catalog /etc/xml/catalog.before-update-xmlcatalog

If the file does not exist, stop and inspect the XML package installation instead of inventing a replacement. Do not copy a catalogue from another machine: its entries may describe packages and paths that do not exist here.

3. Register the package's local catalogue

For a package called example-xml, with a local catalogue at /usr/share/xml/schema/example/catalog.xml, add a public identifier like this:

$ sudo update-xmlcatalog --verbose --add \
    --package example-xml \
    --local /usr/share/xml/schema/example/catalog.xml \
    --type public \
    --id '-//EXAMPLE//DTD EXAMPLE 1.0//EN'

--add creates the XML catalogue if it does not exist. The example uses a package-owned catalogue, so the package name is part of the bookkeeping. Replace every placeholder with values from the package's installation instructions or maintainer script.

For a system identifier, change only the type and identifier:

$ sudo update-xmlcatalog --verbose --add \
    --package example-xml \
    --local /usr/share/xml/schema/example/catalog.xml \
    --type system \
    --id 'https://example.invalid/dtd/example.dtd'

Tip: use --verbose while testing. Its output is diagnostic; the durable result is the catalogue content and the command's exit status.

4. Add the entry to the root catalogue

Now connect the package entry to Debian's root catalogue:

$ sudo update-xmlcatalog --verbose --add \
    --root \
    --package example-xml \
    --type public \
    --id '-//EXAMPLE//DTD EXAMPLE 1.0//EN'

Check the resulting entry without editing the file by hand:

$ sudo grep -F -- '-//EXAMPLE//DTD EXAMPLE 1.0//EN' /etc/xml/catalog
    <public publicId="-//EXAMPLE//DTD EXAMPLE 1.0//EN" uri="..."/>

The exact indentation and generated URI can vary. The useful check is that the identifier is present and points at the intended catalogue. To inspect the whole file, use less /etc/xml/catalog.

5. Understand the overwrite boundary

update-xmlcatalog keeps an internal database of the entries it manages and regenerates an indicated catalogue from that data. xmlcatalog, from libxml2-utils, edits a catalogue file directly. Do not use both tools for the same managed catalogue.

Warning: a direct xmlcatalog edit can appear to work and then vanish when update-xmlcatalog next updates that file. If an existing entry is missing, find the package or maintainer script that owns it and register it through update-xmlcatalog instead. Back up any manual work before experimenting.

The optional --sort flag sorts manipulated catalogue content. To sort without changing an entry, add an entry that is already present, with the same type, identifier and ownership details. Do not assume sorting alone repairs an incomplete database.

6. Remove an entry and recover

Deletion is also a state-changing operation. For a root entry, use the documented root form:

$ sudo update-xmlcatalog --verbose --del \
    --root \
    --type public \
    --id '-//EXAMPLE//DTD EXAMPLE 1.0//EN'

A resulting empty XML catalogue is not automatically removed from disk. Check both the command status and the file:

$ sudo grep -F -- '-//EXAMPLE//DTD EXAMPLE 1.0//EN' /etc/xml/catalog || echo 'entry absent'

Recovery: if you need to undo an incorrect root change, restore the backup only when you are sure no legitimate catalogue updates happened after it was made:

$ sudo cp --preserve=all /etc/xml/catalog.before-update-xmlcatalog /etc/xml/catalog

That restore is deliberately conservative and can discard later changes. In a package-managed system, the safer long-term repair is to remove the bad registration with --del, then re-run the package's correct registration commands.

Done means