Edit and Test XML Catalogue Mappings with xmlcatalog
One missing flag and xmlcatalog will happily overwrite the working catalogue you meant to only preview. You will create a catalogue, map an identifier to a URI, verify the lookup, then remove the entry without clobbering the file. The examples use xmlcatalog from libxml2-utils, version 2.9.14+dfsg-1.3ubuntu3.9.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell and a writable directory. Nothing here needs sudo; use elevated privileges only if the catalogue deliberately lives somewhere your account cannot write, and review the target path first.
1. Create a working catalogue
Start in a scratch directory, not a real one. --create writes a new catalogue to standard output by default; add --noout when you actually want it saved to the filename you gave.
$ workdir=$(mktemp -d)
$ catalog="$workdir/catalog.xml"
$ xmlcatalog --create --noout "$catalog"
$ sed -n '1,8p' "$catalog"
<?xml version="1.0"?>
<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"/>
Checkpoint
The file should hold an empty catalogue with the XML catalogue namespace. Omit --noout and the same document just prints to standard output instead: the named file is never created or replaced.
2. Add a URI mapping
--add takes three arguments for an XML entry: the type, the original identifier, and its replacement URI. A uri mapping earns its keep when software asks libxml2 to resolve a known identifier and you want that redirected to a local or controlled resource.
$ xmlcatalog --add uri urn:example:doc https://example.test/doc.xml --noout "$catalog"
$ sed -n '1,12p' "$catalog"
<?xml version="1.0"?>
<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<uri name="urn:example:doc" uri="https://example.test/doc.xml"/>
</catalog>
Safety warning
Never use a production catalogue as your first test target. Saving an entry changes resolver behaviour for every application that reads that file. Keep a backup before editing an existing one:
$ cp --preserve=all /path/to/catalog.xml /path/to/catalog.xml.bak
Run the command without --noout and it is a preview: the changed catalogue prints to standard output, the input file stays untouched. Worth doing before you save anything:
$ xmlcatalog --add uri urn:example:next https://example.test/next.xml "$catalog"
Add --noout only once the displayed change is the one you actually want to keep.
3. Verify the mapping and its exit status
Give xmlcatalog the catalogue followed by the identifier and, with no --add or --del, it just performs a lookup:
$ xmlcatalog "$catalog" urn:example:doc
No entry for SYSTEM urn:example:doc
https://example.test/doc.xml
$ printf '%s\n' "$?"
0
The extra system lookup message is normal for a URI-only entry; the URI result and status 0 are what actually matter. A missing identifier produces messages like No entry for URI and returns status 4 instead.
In a script, grab the status immediately after the lookup:
if resolved=$(xmlcatalog "$catalog" urn:example:doc 2>/dev/null); then
printf '%s\n' "$resolved"
else
status=$?
printf 'catalog lookup failed with status %s\n' "$status" >&2
exit "$status"
fi
Do not assume the first output line is the replacement: a catalogue can report more than one attempted lookup. Need a quiet, machine-friendly interface instead? Use libxml2's resolver in your application and treat this command as an inspection tool, not a library.
4. Remove an entry without guessing
--del takes the value to remove, and like --add it prints the resulting catalogue unless you supply --noout. Preview the deletion first:
$ xmlcatalog --del urn:example:doc "$catalog"
<?xml version="1.0"?>
...
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"/>
$ xmlcatalog "$catalog" urn:example:doc
No entry for SYSTEM urn:example:doc
No entry for URI urn:example:doc
Destructive action
The preview above did not touch the file. This one does:
$ xmlcatalog --del urn:example:doc --noout "$catalog"
$ xmlcatalog "$catalog" urn:example:doc
No entry for SYSTEM urn:example:doc
No entry for URI urn:example:doc
Removed the wrong entry? Restore the backup you made before editing:
$ cp --preserve=all /path/to/catalog.xml.bak /path/to/catalog.xml
For a shared catalogue, coordinate the change with the applications reading it. There is no transaction or service reload built into xmlcatalog: a process may see the old or new contents purely depending on when it happened to open the file.
5. Inspect a catalogue interactively
Reach for --shell when you want several read or edit operations in one session. Its commands include dump, public, system, resolve, add, del, debug, quiet, and exit.
$ xmlcatalog --shell "$catalog"
> dump
<?xml version="1.0"?>
...
> system "-//Example//DTD Widget 1.0//EN"
> exit
Quote identifiers that contain spaces or punctuation. This shell still operates on the named catalogue, so treat its add and del as state-changing, and keep the backup until you have verified the result.
6. Check environment and common failures
libxml2 also consults the XML_CATALOG_FILES environment variable: a space-separated list of catalogues, with percent-encoding for spaces and other special characters. Set it to empty and you disable loading the default /etc/xml/catalog for the relevant resolver behaviour.
$ printf '%s\n' "${XML_CATALOG_FILES-}<unset>"
$ env XML_CATALOG_FILES= xmlcatalog "" urn:example:doc
No entry for SYSTEM urn:example:doc
No entry for URI urn:example:doc
That last command deliberately queries the default system catalogue shortcut with the override empty. Do not set this variable globally just to make one test pass: it changes how applications resolve external entities and schemas everywhere.
- Status 1: removal failed.
- Status 2: saving failed, often because the file is read-only or the directory is not writable. Check with
ls -landtest -wbefore reaching forsudo; ownership or permissions changes are a separate administrative decision. - Status 3: adding failed.
- Status 4: lookup failed.
Done means
- Backed up first: the catalogue was created or backed up before editing.
- Change previewed and verified: a mapping was previewed, saved with
--noout, then confirmed by a lookup. - Failures distinguished: missing lookups and save or add failures show different exit statuses.
- Deletion checked: it was previewed before saving, and the recovery copy still exists.
- Environment checked:
XML_CATALOG_FILESwas ruled out before chasing unexpected resolver behaviour.