Bulk XML schema validation for California Revealed preservation packages. A single cross-platform Python tool that recursively scans a directory (typically an external hard drive containing vendor deliverables), validates every metadata record it finds against the appropriate schema, and writes both a combined CSV and a human-readable validation report per partner organization.
xmlvalidate.py replaces the earlier zsh-based DCandPBCore_validation script and runs identically on Apple silicon macOS, Intel macOS, and Windows.
The tool discovers and validates two profiles of XML files anywhere under the target directory:
| Files matching | Validated against | Profile name |
|---|---|---|
*metadata.xml |
CA-R Dublin Core entry-point schema (CA-R_metadata.xsd, which imports CA-Rdqdcterms.xsd) |
metadata |
*pbcore.xml |
PBCore 2.1 schema (pbcore-2_1.xsd) |
pbcore |
Validation is fully offline. The bundled schemas/catalog.xml redirects every remote schema import (dublincore.org, w3.org, pbcore.org) to the local copies in the schemas/ folder, so the tool works with no network connection and never silently fetches anything from the internet.
macOS and Windows filesystem clutter is skipped automatically: .Trashes, .Spotlight-V100, .fseventsd, $RECYCLE.BIN, System Volume Information, AppleDouble ._* files, .DS_Store, Thumbs.db, and anything else hidden.
xmlvalidate/
├── xmlvalidate.py The tool. One file, no editing required.
├── schemas/
│ ├── catalog.xml Portable XML catalog (relative paths — do not edit)
│ ├── CA-R_metadata.xsd Entry-point schema for DC metadata records (accepts both root styles)
│ ├── CA-Rdqdcterms.xsd CA-R qualified Dublin Core schema (imported by the above)
│ ├── pbcore-2_1.xsd PBCore 2.1 schema
│ ├── dc.xsd Dublin Core elements (imported)
│ ├── dcterms.xsd Dublin Core terms (imported)
│ ├── dcmitype.xsd DCMI type vocabulary (imported)
│ └── xml.xsd W3C xml namespace schema (imported)
├── README.md
└── CHANGELOG.md
Keep xmlvalidate.py and the schemas/ folder together. The script finds its schemas relative to its own location, so the whole folder can live anywhere — a Tools directory, a Desktop, even the external drive itself.
Requirements: Python 3.8 or newer, plus the lxml package. No virtual environment is needed.
macOS (Apple silicon and Intel). Recent macOS does not ship Python 3 by itself; install it once from python.org (the universal installer covers both architectures), or via Homebrew. Then:
pip3 install lxml
If Homebrew's Python refuses with an "externally managed environment" message, use pip3 install lxml --break-system-packages (safe for a single, widely-used package like lxml) or pip3 install --user lxml.
Windows. Install Python 3 from python.org and check "Add python.exe to PATH" during setup. Then in PowerShell or Command Prompt:
py -m pip install lxml
That's the entire installation. To get the tool itself, either clone this repository or download it as a ZIP (green Code button → Download ZIP) and unzip it anywhere.
Run the script with no arguments and it will prompt you, just like the original zsh tool:
macOS: python3 xmlvalidate.py
Windows: py xmlvalidate.py
Enter the directory to scan: /Volumes/CA-R_Drive/CA-R2082
Combined CSV path [/Volumes/CA-R_Drive/CA-R2082/XMLvalidationCombined_2026-07-27.csv]:
Press Enter at the CSV prompt to accept the default. Paths dragged into the terminal from Finder or File Explorer work fine, including paths with spaces.
python3 xmlvalidate.py --dir "/Volumes/CA-R_Drive/CA-R2082"
py xmlvalidate.py --dir "E:\CA-R2082" --csv "E:\reports\combined.csv"
| Flag | Purpose |
|---|---|
-d, --dir PATH |
Directory to scan recursively |
--csv PATH |
Where to write the combined CSV (default: inside the scanned directory, dated) |
--dry-run |
List the files that would be validated; validate nothing, write nothing |
--no-log |
Skip the per-MARC-code .txt report files |
-q, --quiet |
Suppress per-file progress lines; print only the summary |
--schemas-dir PATH |
Use a different schemas folder |
--schema-metadata / --schema-pbcore / --catalog PATH |
Override individual schema or catalog paths |
--version |
Print the tool version |
Combined CSV — one row per file with columns profile, file, status, errors. Written to the scanned directory by default (e.g., XMLvalidationCombined_2026-07-27.csv), or wherever --csv points.
Per-partner validation reports — one plain-text report per MARC organization code found in the scan, written to the parent directory of that MARC code's folder. Every run gets its own report file, named with the run's date and time:
[marc]_XMLvalidationReport_[YYYY-MM-DD]_[HHMMSS].txt
For example, scanning a shipment folder CA-R2082/ containing camtrcsp/ and chi/ subfolders produces:
CA-R2082/
├── camtrcsp_XMLvalidationReport_2026-07-27_163009.txt
├── chi_XMLvalidationReport_2026-07-27_163009.txt
├── camtrcsp/
│ └── camtrcsp_000001/ ...
└── chi/
└── chi_000039/ ...
Each report records the run timestamp, the scanned path, the schemas used, a line per file with its VALID/INVALID status, any validation errors (with line numbers), and a summary count. Because the run time is part of the file name, repeated runs never overwrite earlier reports — each run's results stay in their own file. The MARC code is read from the file names themselves (e.g., camtrcsp_000123_metadata.xml → camtrcsp); if a file's location doesn't include a folder named for its MARC code, its report falls back to the scanned directory.
Consistent with California Revealed's other command-line tools:
0— every file validated successfully1— one or more files were invalid or not well-formed2— configuration or usage error (missing directory, missing schemas, lxml not installed)
This makes the tool safe to chain in scripts: python3 xmlvalidate.py --dir "$DIR" && echo "All valid".
"The lxml package is not installed." Run the pip command from the Installation section for your platform. On Windows, if py isn't recognized, reinstall Python and enable "Add python.exe to PATH."
"Required metadata schema not found." The schemas/ folder isn't next to xmlvalidate.py. Keep the repository folder intact, or point at the schemas explicitly with --schemas-dir.
Zero files found. Check that the file names end in metadata.xml or pbcore.xml (case-insensitive) and that the drive is actually mounted at the path you entered. Use --dry-run to see exactly what the tool discovers.
Report can't be written (read-only or locked drive). The tool warns you and writes the report into the scanned directory instead; validation results are never lost.
A file passes validation that "looks wrong." The entry-point schema validates the metadata root's children laxly: elements declared in the dc/dcterms/dcmitype schemas are fully validated (including typed content such as W3CDTF dates), while undeclared elements are skipped. Schema-valid is not the same as content-correct. To require that every child element be declared, change processContents="lax" to "strict" in CA-R_metadata.xsd.
All metadata files fail with "No matching global declaration available for the validation root." You are probably validating against CA-Rdqdcterms.xsd directly (e.g., via --schema-metadata). Vendor records use an unqualified <metadata> root, which that schema alone cannot match; validate against CA-R_metadata.xsd (the default), which accepts both unqualified and dcterms-namespaced roots.
One schema carries a documented California Revealed modification: dc.xsd adds a single optional type attribute to the DCMI SimpleLiteral type, because CA-R vendor records qualify Dublin Core elements this way (e.g., <dc:date type="Published">, <dc:identifier type="CDNP identifier">). The change is marked with a comment in the file; deleting that one attribute line restores canonical DCMI behavior.
The catalog uses relative paths that resolve against the catalog file's own location, which is what makes the folder portable across operating systems, users, and drive letters. If you replace a schema (for example, a future PBCore revision), keep the filename or update both the script defaults and catalog.xml. The catalog covers http and https variants of every import URL used by the schemas.
See LICENSE.