Validating FGDC Metadata Against XML Schemas
Validating FGDC CSDGM metadata against its XSD requires loading fgdc-std-001-1998.xsd from a local cache, parsing the target document with lxml.etree.XMLSchema, and calling assertValid() — any structural deviation returns a machine-readable error trace rather than silently passing.
This operation is non-trivial because FGDC’s Content Standard for Digital Geospatial Metadata (CSDGM) is a modular schema distributed across multiple include files, was authored for a 1998 toolchain, and is routinely exported by legacy software with byte-order marks, encoding drift, and relaxed element ordering. Before any document can be promoted through FGDC to ISO 19115 Conversion Pipelines, it must pass XSD validation — otherwise field-mapping transforms silently consume malformed source data and produce defective ISO output. This step fits into the broader quality gate architecture described under Automated Metadata Generation & Schema Mapping, where structural validation is the first control before semantic enrichment begins.
Validation Pipeline Overview
The diagram below shows where XSD validation sits in the FGDC processing sequence — between raw file ingestion and any transformation or publishing step.
Automated Python Implementation
The script below validates a single FGDC XML file against a locally cached XSD, captures structured error traces, and exits with a non-zero code on failure. It uses lxml>=4.9.0 for XML Schema 1.0 compliance, sets no_network=True to prevent remote resolution, and handles the four distinct failure modes (schema parse error, invalid document, malformed XML, unexpected exception) separately so callers can route each type to the appropriate remediation step.
#!/usr/bin/env python3
"""
validate_fgdc.py — Validate an FGDC CSDGM XML file against a local XSD.
Usage:
python validate_fgdc.py <xml_file> <xsd_file>
Exit codes:
0 — document is valid
1 — document is invalid or an error occurred
"""
import sys
from pathlib import Path
from lxml import etree # pip install lxml>=4.9.0
def strip_bom(path: Path) -> bytes:
"""Read file bytes and remove a UTF-8 BOM if present."""
raw = path.read_bytes()
return raw.lstrip(b"\xef\xbb\xbf") # UTF-8 BOM: EF BB BF
def validate_fgdc(xml_path: str, schema_path: str) -> dict:
"""
Validate an FGDC CSDGM XML file against the official XSD.
Returns a dict with:
valid (bool) — True if the document passes XSD validation
errors (list) — validation or parse error messages
warnings (list) — reserved for future semantic checks
"""
xml_file = Path(xml_path)
schema_file = Path(schema_path)
if not xml_file.is_file():
return {"valid": False, "errors": [f"XML file not found: {xml_path}"], "warnings": []}
if not schema_file.is_file():
return {"valid": False, "errors": [f"Schema file not found: {schema_path}"], "warnings": []}
try:
# Load schema — schema_file must be in the same directory as its xs:include companions
schema_doc = etree.XMLSchema(etree.parse(str(schema_file)))
except etree.XMLSchemaParseError as exc:
return {
"valid": False,
"errors": [f"Schema parse error (check xs:include companions are present): {exc}"],
"warnings": [],
}
# Strip BOM before parsing — Windows tools frequently prepend it
cleaned_bytes = strip_bom(xml_file)
try:
# recover=False: reject malformed XML rather than silently guess at intent
# no_network=True: block any remote entity/schema resolution
parser = etree.XMLParser(recover=False, no_network=True, encoding="utf-8")
doc = etree.fromstring(cleaned_bytes, parser=parser)
except etree.XMLSyntaxError as exc:
return {"valid": False, "errors": [f"Malformed XML: {exc}"], "warnings": []}
try:
schema_doc.assertValid(doc)
return {"valid": True, "errors": [], "warnings": []}
except etree.DocumentInvalid as exc:
# lxml returns a multi-line error string; split into individual actionable lines
raw = str(exc).strip().splitlines()
cleaned = [line.strip() for line in raw if line.strip()]
return {"valid": False, "errors": cleaned, "warnings": []}
except Exception as exc: # noqa: BLE001
return {"valid": False, "errors": [f"Unexpected failure: {exc}"], "warnings": []}
def main() -> None:
if len(sys.argv) != 3:
print("Usage: python validate_fgdc.py <xml_file> <xsd_file>", file=sys.stderr)
sys.exit(1)
result = validate_fgdc(sys.argv[1], sys.argv[2])
if result["valid"]:
print("Validation passed.")
else:
print(f"Validation failed ({len(result['errors'])} error(s)):", file=sys.stderr)
for err in result["errors"]:
print(f" - {err}", file=sys.stderr)
sys.exit(0 if result["valid"] else 1)
if __name__ == "__main__":
main()
Schema acquisition note. Download fgdc-std-001-1998.xsd and every companion file distributed alongside it to a version-controlled directory. Never resolve the schema from a remote URL at validation time — network timeouts and deprecated endpoints will silently break pipelines. Reference schemas via an absolute filesystem path and keep the original directory structure intact so xs:include directives resolve correctly.
pip install "lxml>=4.9.0"
Validation and Pipeline Integration
Resolving Common CSDGM Validation Errors
FGDC metadata fails strict XSD validation more often than not when sourced from legacy GIS export tools. Address these four patterns before scaling to batch processing:
| Error pattern | Root cause | Resolution |
|---|---|---|
XMLSyntaxError on line 1 |
UTF-8 BOM prepended by Windows editors | strip_bom() as shown above, or open with encoding="utf-8-sig" |
XMLSchemaParseError on schema load |
xs:include companions missing from schema directory |
Restore original directory layout; never move the root XSD in isolation |
Element 'dataqual' unexpected |
Strict element sequence violated (1998 standard enforces fixed order) | Use error line numbers to locate swapped blocks; reorder <idinfo>, <dataqual>, <spdoinfo>, <spref>, <eainfo>, <distinfo>, <metainfo> to the canonical sequence |
Attribute 'xsi:noNamespaceSchemaLocation' not expected |
Some legacy tools inject the attribute but CSDGM root has no schema hint declaration | Strip the attribute or add xmlns:xsi and xsi:noNamespaceSchemaLocation to the root <metadata> element manually before parsing |
CLI Validation with xmllint
For shell pipelines or containerized runners where a Python environment is impractical, xmllint provides fast, dependency-light validation:
# Install on Debian/Ubuntu
sudo apt-get install -y libxml2-utils
# Validate a single file
xmllint --noout --schema schemas/fgdc-std-001-1998.xsd metadata.xml
# Batch-validate all XML files in a directory
find ./metadata -name "*.xml" -print0 \
| xargs -0 xmllint --noout --schema schemas/fgdc-std-001-1998.xsd
--noout suppresses stdout; only errors appear on stderr and the exit code is non-zero on failure. This integrates directly with any CI runner that gates on exit codes.
GitHub Actions CI Gate
The following workflow snippet runs XSD validation on every push and pull request, blocking merges when any metadata file in ./metadata/ fails. It pairs naturally with the spatial data schema linting in CI patterns used elsewhere in the pipeline.
# .github/workflows/validate-fgdc-metadata.yml
name: Validate FGDC Metadata
on:
push:
paths:
- "metadata/**"
- "schemas/**"
pull_request:
paths:
- "metadata/**"
- "schemas/**"
jobs:
validate-fgdc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install libxml2-utils
run: sudo apt-get install -y libxml2-utils
- name: Validate all FGDC XML files
run: |
find ./metadata -name "*.xml" -print0 \
| xargs -0 xmllint --noout \
--schema schemas/fgdc-std-001-1998.xsd
- name: Upload validation report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: fgdc-validation-errors
path: /tmp/fgdc-errors.log
For Python-based pipelines that need structured error output (e.g., for routing errors to a ticketing system or compliance dashboard), replace the xmllint step with a call to validate_fgdc.py and redirect stderr to a JSON log for downstream consumption. This is especially important when feeding validation results into metadata schema validation and linting workflows that aggregate error patterns across large dataset collections.
Long-Term Compliance Best Practices
- Version-pin the schema alongside your code. Store
fgdc-std-001-1998.xsdand all companion files in aschemas/subdirectory under version control. Pin the commit reference in CI configuration so schema drift never silently changes validation behavior across runs. - Validate before every transformation, not just at ingestion. Re-validate after any programmatic patch (BOM removal, attribute injection, element reordering) to confirm the fix did not introduce a new structural violation. Use this as the pre-condition for every stage in FGDC to ISO 19115 Conversion Pipelines.
- Log structured error traces to a durable store. Route validation errors to JSON or CSV with at minimum
file_path,timestamp,error_count, andfirst_errorfields. Aggregating these across datasets surfaces systemic authoring-tool defects that a single-file fix will never catch. - Pair XSD validation with semantic checks. XSD confirms structure; it does not verify that bounding coordinates are geographically plausible, that
<pubdate>is a real date, or that thematic keywords match an approved vocabulary. Add a lightweight semantic pass after XSD gates clear. - Instrument batch validation for error rate trending. Track the ratio of valid-to-invalid files over time. A rising error rate after a software upgrade or data migration signals a systemic encoding or export configuration change, not individual authoring mistakes.
- Use
recover=Falsein production,recover=Trueonly in triage mode. Recovery mode can obscure the true error location by auto-correcting malformed markup. Reserve it for interactive diagnosis; all automated pipeline runs should enforce strict parsing.
Related
- FGDC to ISO 19115 Conversion Pipelines — parent cluster covering the full CSDGM-to-ISO crosswalk, field mapping tables, and transformation architecture
- Metadata Schema Validation and Linting — broader validation patterns covering ISO 19115, DCAT-AP, and STAC alongside FGDC
- Spatial Data Schema Linting in CI — CI/CD enforcement gates for spatial metadata quality, including pre-commit hooks and GitHub Actions patterns
- Setting Up GitHub Actions for ISO 19115 Validation — companion how-to for ISO-side schema validation in the same CI environment