################################ Validate against an XML schema ################################ .. currentmodule:: turbohtml.validate Parse the XML with :func:`turbohtml.parse_xml`, compile the schema once, then validate. :class:`XMLSchema` takes XSD 1.0 text (or a parsed schema document) and :class:`RelaxNG` takes RELAX NG XML syntax; both expose the same :meth:`~XMLSchema.validate`, :meth:`~XMLSchema.is_valid`, and :meth:`~XMLSchema.assert_valid` surface. ***************** Get every error ***************** :meth:`~XMLSchema.validate` returns a :class:`ValidationResult`. It is truthy when the document is valid, and its ``errors`` tuple holds one :class:`ValidationError` per violation -- each with a ``message``, the ``/root/child`` document ``path`` that located it, a ``line`` (``0`` when the source carries no positions), and a coarse ``type`` of ``"structure"``, ``"datatype"``, or ``"facet"``: .. testcode:: from turbohtml import parse_xml from turbohtml.validate import XMLSchema schema = XMLSchema( '' '' '' '' "" ) result = schema.validate(parse_xml("One")) print(result.valid) for error in result.errors: print(error.type, error.path, "--", error.message) .. testoutput:: False structure /book -- required attribute 'isbn' is missing *********** Fail fast *********** When you only care whether the document conforms, :meth:`~XMLSchema.is_valid` returns the bool, and :meth:`~XMLSchema.assert_valid` raises :class:`SchemaValidationError` (carrying the errors) on the first invalid document, so it drops into a pipeline that expects an exception: .. testcode:: from turbohtml import parse_xml from turbohtml.validate import RelaxNG, SchemaValidationError schema = RelaxNG( '' '' ) print(schema.is_valid(parse_xml("hi"))) try: schema.assert_valid(parse_xml("")) except SchemaValidationError as error: print("rejected:", error.errors[0].path) .. testoutput:: True rejected: /note