Each schema language checks different things, so pick the validator for the language your contract uses, and know what it cannot see. The same feed with a negative price, through every validator on this machine:
# One feed, every validator on this machine; bad.xml has a negative price
sed 's/>39.50</>-39.50</' booknest-catalog.xml > bad.xml
sed '1a <!DOCTYPE catalog SYSTEM "booknest.dtd">' bad.xml > bad-dtd.xml
v() { printf '%-22s ' "$1"; shift; "$@" > /dev/null 2>&1 && echo valid || echo INVALID; }
v "xmllint, DTD" xmllint --noout --valid bad-dtd.xml
v "xmllint, XSD 1.0" xmllint --noout --schema catalog.xsd bad.xml
v "Jing, RELAX NG" jing booknest.rng bad.xml
v "xmlschema, XSD 1.1" ~/de-venv/bin/xmlschema-validate --version 1.1 \
--schema catalog11.xsd bad.xml
xslt3 -xsl:$HOME/xml-tools/schxslt2/schxslt2-1.11.2/transpile.xsl -s:booknest.sch -o:sch.xsl
v "SchXslt2, Schematron" sh -c '! xslt3 -xsl:sch.xsl -s:bad.xml | grep -q failed-assert'xmllint, DTD valid xmllint, XSD 1.0 INVALID Jing, RELAX NG INVALID xmlschema, XSD 1.1 INVALID SchXslt2, Schematron valid
The DTD types the price as text, and the Schematron rules of Schematron test record counts and dates, not prices, so both passed the bad value; XSD 1.0, RELAX NG and XSD 1.1 caught it. Layer them: a grammar (XSD or RELAX NG) for structure and types, Schematron for business rules. Open-source choices: xmllint 3,427 and lxml 3,063 (DTD, XSD 1.0, RELAX NG), Jing 258 (RELAX NG), xmlschema 476 (XSD 1.1), Xerces-J (XSD 1.1), SchXslt2 on Saxon-HE 726,956 (Schematron); Saxon-EE validates XSD 1.1 commercially.