| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- ====================
- Validation with lxml
- ====================
- Apart from the built-in DTD support in parsers, lxml currently supports three
- schema languages: DTD_, `Relax NG`_ and `XML Schema`_. All three provide
- identical APIs in lxml, represented by validator classes with the obvious
- names.
- .. _DTD: http://en.wikipedia.org/wiki/Document_Type_Definition
- .. _`Relax NG`: http://www.relaxng.org/
- .. _`XML Schema`: http://www.w3.org/XML/Schema
- There is also initial support for Schematron_. However, it does not currently
- support error reporting in the validation phase due to insufficiencies in the
- implementation as of libxml2 2.6.30.
- .. _Schematron: http://www.ascc.net/xml/schematron
- .. contents::
- ..
- 1 Validation at parse time
- 2 DTD
- 3 RelaxNG
- 4 XMLSchema
- 5 Schematron
- The usual setup procedure:
- .. sourcecode:: pycon
- >>> from lxml import etree
- ..
- >>> try: from StringIO import StringIO
- ... except ImportError:
- ... from io import BytesIO
- ... def StringIO(s):
- ... if isinstance(s, str): s = s.encode("UTF-8")
- ... return BytesIO(s)
- Validation at parse time
- ------------------------
- The parser in lxml can do on-the-fly validation of a document against
- a DTD or an XML schema. The DTD is retrieved automatically based on
- the DOCTYPE of the parsed document. All you have to do is use a
- parser that has DTD validation enabled:
- .. sourcecode:: pycon
- >>> parser = etree.XMLParser(dtd_validation=True)
- Obviously, a request for validation enables the DTD loading feature.
- There are two other options that enable loading the DTD, but that do
- not perform any validation. The first is the ``load_dtd`` keyword
- option, which simply loads the DTD into the parser and makes it
- available to the document as external subset. You can retrieve the
- DTD from the parsed document using the ``docinfo`` property of the
- result ElementTree object. The internal subset is available as
- ``internalDTD``, the external subset is provided as ``externalDTD``.
- The third way way to activate DTD loading is with the
- ``attribute_defaults`` option, which loads the DTD and weaves
- attribute default values into the document. Again, no validation is
- performed unless explicitly requested.
- XML schema is supported in a similar way, but requires an explicit
- schema to be provided:
- .. sourcecode:: pycon
- >>> schema_root = etree.XML('''\
- ... <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
- ... <xsd:element name="a" type="xsd:integer"/>
- ... </xsd:schema>
- ... ''')
- >>> schema = etree.XMLSchema(schema_root)
- >>> parser = etree.XMLParser(schema = schema)
- >>> root = etree.fromstring("<a>5</a>", parser)
- If the validation fails (be it for a DTD or an XML schema), the parser
- will raise an exception:
- .. sourcecode:: pycon
- >>> root = etree.fromstring("<a>no int</a>", parser)
- Traceback (most recent call last):
- lxml.etree.XMLSyntaxError: Element 'a': 'no int' is not a valid value of the atomic type 'xs:integer'.
- If you want the parser to succeed regardless of the outcome of the
- validation, you should use a non validating parser and run the
- validation separately after parsing the document.
- DTD
- ---
- As described above, the parser support for DTDs depends on internal or
- external subsets of the XML file. This means that the XML file itself
- must either contain a DTD or must reference a DTD to make this work.
- If you want to validate an XML document against a DTD that is not
- referenced by the document itself, you can use the ``DTD`` class.
- To use the ``DTD`` class, you must first pass a filename or file-like object
- into the constructor to parse a DTD:
- .. sourcecode:: pycon
- >>> f = StringIO("<!ELEMENT b EMPTY>")
- >>> dtd = etree.DTD(f)
- Now you can use it to validate documents:
- .. sourcecode:: pycon
- >>> root = etree.XML("<b/>")
- >>> print(dtd.validate(root))
- True
- >>> root = etree.XML("<b><a/></b>")
- >>> print(dtd.validate(root))
- False
- The reason for the validation failure can be found in the error log:
- .. sourcecode:: pycon
- >>> print(dtd.error_log.filter_from_errors()[0])
- <string>:1:0:ERROR:VALID:DTD_NOT_EMPTY: Element b was declared EMPTY this one has content
- As an alternative to parsing from a file, you can use the
- ``external_id`` keyword argument to parse from a catalog. The
- following example reads the DocBook DTD in version 4.2, if available
- in the system catalog:
- .. sourcecode:: python
- dtd = etree.DTD(external_id = "-//OASIS//DTD DocBook XML V4.2//EN")
- RelaxNG
- -------
- The ``RelaxNG`` class takes an ElementTree object to construct a Relax NG
- validator:
- .. sourcecode:: pycon
- >>> f = StringIO('''\
- ... <element name="a" xmlns="http://relaxng.org/ns/structure/1.0">
- ... <zeroOrMore>
- ... <element name="b">
- ... <text />
- ... </element>
- ... </zeroOrMore>
- ... </element>
- ... ''')
- >>> relaxng_doc = etree.parse(f)
- >>> relaxng = etree.RelaxNG(relaxng_doc)
- Alternatively, pass a filename to the ``file`` keyword argument to parse from
- a file. This also enables correct handling of include files from within the
- RelaxNG parser.
- You can then validate some ElementTree document against the schema. You'll get
- back True if the document is valid against the Relax NG schema, and False if
- not:
- .. sourcecode:: pycon
- >>> valid = StringIO('<a><b></b></a>')
- >>> doc = etree.parse(valid)
- >>> relaxng.validate(doc)
- True
- >>> invalid = StringIO('<a><c></c></a>')
- >>> doc2 = etree.parse(invalid)
- >>> relaxng.validate(doc2)
- False
- Calling the schema object has the same effect as calling its validate
- method. This is sometimes used in conditional statements:
- .. sourcecode:: pycon
- >>> invalid = StringIO('<a><c></c></a>')
- >>> doc2 = etree.parse(invalid)
- >>> if not relaxng(doc2):
- ... print("invalid!")
- invalid!
- If you prefer getting an exception when validating, you can use the
- ``assert_`` or ``assertValid`` methods:
- .. sourcecode:: pycon
- >>> relaxng.assertValid(doc2)
- Traceback (most recent call last):
- ...
- lxml.etree.DocumentInvalid: Did not expect element c there, line 1
- >>> relaxng.assert_(doc2)
- Traceback (most recent call last):
- ...
- AssertionError: Did not expect element c there, line 1
- If you want to find out why the validation failed in the second case, you can
- look up the error log of the validation process and check it for relevant
- messages:
- .. sourcecode:: pycon
- >>> log = relaxng.error_log
- >>> print(log.last_error)
- <string>:1:0:ERROR:RELAXNGV:RELAXNG_ERR_ELEMWRONG: Did not expect element c there
- You can see that the error (ERROR) happened during RelaxNG validation
- (RELAXNGV). The message then tells you what went wrong. You can also
- look at the error domain and its type directly:
- .. sourcecode:: pycon
- >>> error = log.last_error
- >>> print(error.domain_name)
- RELAXNGV
- >>> print(error.type_name)
- RELAXNG_ERR_ELEMWRONG
- Note that this error log is local to the RelaxNG object. It will only
- contain log entries that appeared during the validation.
- Similar to XSLT, there's also a less efficient but easier shortcut method to
- do one-shot RelaxNG validation:
- .. sourcecode:: pycon
- >>> doc.relaxng(relaxng_doc)
- True
- >>> doc2.relaxng(relaxng_doc)
- False
- libxml2 does not currently support the `RelaxNG Compact Syntax`_.
- However, the trang_ translator can convert the compact syntax to the
- XML syntax, which can then be used with lxml.
- .. _`RelaxNG Compact Syntax`:
- .. _trang: http://www.thaiopensource.com/relaxng/trang.html
- XMLSchema
- ---------
- lxml.etree also has XML Schema (XSD) support, using the class
- lxml.etree.XMLSchema. The API is very similar to the Relax NG and DTD
- classes. Pass an ElementTree object to construct a XMLSchema validator:
- .. sourcecode:: pycon
- >>> f = StringIO('''\
- ... <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
- ... <xsd:element name="a" type="AType"/>
- ... <xsd:complexType name="AType">
- ... <xsd:sequence>
- ... <xsd:element name="b" type="xsd:string" />
- ... </xsd:sequence>
- ... </xsd:complexType>
- ... </xsd:schema>
- ... ''')
- >>> xmlschema_doc = etree.parse(f)
- >>> xmlschema = etree.XMLSchema(xmlschema_doc)
- You can then validate some ElementTree document with this. Like with RelaxNG,
- you'll get back true if the document is valid against the XML schema, and
- false if not:
- .. sourcecode:: pycon
- >>> valid = StringIO('<a><b></b></a>')
- >>> doc = etree.parse(valid)
- >>> xmlschema.validate(doc)
- True
- >>> invalid = StringIO('<a><c></c></a>')
- >>> doc2 = etree.parse(invalid)
- >>> xmlschema.validate(doc2)
- False
- Calling the schema object has the same effect as calling its validate method.
- This is sometimes used in conditional statements:
- .. sourcecode:: pycon
- >>> invalid = StringIO('<a><c></c></a>')
- >>> doc2 = etree.parse(invalid)
- >>> if not xmlschema(doc2):
- ... print("invalid!")
- invalid!
- If you prefer getting an exception when validating, you can use the
- ``assert_`` or ``assertValid`` methods:
- .. sourcecode:: pycon
- >>> xmlschema.assertValid(doc2)
- Traceback (most recent call last):
- ...
- lxml.etree.DocumentInvalid: Element 'c': This element is not expected. Expected is ( b )., line 1
- >>> xmlschema.assert_(doc2)
- Traceback (most recent call last):
- ...
- AssertionError: Element 'c': This element is not expected. Expected is ( b )., line 1
- Error reporting works as for the RelaxNG class:
- .. sourcecode:: pycon
- >>> log = xmlschema.error_log
- >>> error = log.last_error
- >>> print(error.domain_name)
- SCHEMASV
- >>> print(error.type_name)
- SCHEMAV_ELEMENT_CONTENT
- If you were to print this log entry, you would get something like the
- following. Note that the error message depends on the libxml2 version in
- use::
- <string>:1:ERROR::SCHEMAV_ELEMENT_CONTENT: Element 'c': This element is not expected. Expected is ( b ).
- Similar to XSLT and RelaxNG, there's also a less efficient but easier shortcut
- method to do XML Schema validation:
- .. sourcecode:: pycon
- >>> doc.xmlschema(xmlschema_doc)
- True
- >>> doc2.xmlschema(xmlschema_doc)
- False
- Schematron
- ----------
- Since version 2.0, lxml.etree features Schematron_ support, using the
- class lxml.etree.Schematron. It requires at least libxml2 2.6.21 to
- work. The API is the same as for the other validators. Pass an
- ElementTree object to construct a Schematron validator:
- .. sourcecode:: pycon
- >>> f = StringIO('''\
- ... <schema xmlns="http://www.ascc.net/xml/schematron" >
- ... <pattern name="Sum equals 100%.">
- ... <rule context="Total">
- ... <assert test="sum(//Percent)=100">Sum is not 100%.</assert>
- ... </rule>
- ... </pattern>
- ... </schema>
- ... ''')
- >>> sct_doc = etree.parse(f)
- >>> schematron = etree.Schematron(sct_doc)
- You can then validate some ElementTree document with this. Like with RelaxNG,
- you'll get back true if the document is valid against the schema, and false if
- not:
- .. sourcecode:: pycon
- >>> valid = StringIO('''\
- ... <Total>
- ... <Percent>20</Percent>
- ... <Percent>30</Percent>
- ... <Percent>50</Percent>
- ... </Total>
- ... ''')
- >>> doc = etree.parse(valid)
- >>> schematron.validate(doc)
- True
- >>> etree.SubElement(doc.getroot(), "Percent").text = "10"
- >>> schematron.validate(doc)
- False
- Calling the schema object has the same effect as calling its validate method.
- This is sometimes used in conditional statements:
- .. sourcecode:: pycon
- >>> is_valid = etree.Schematron(sct_doc)
- >>> if not is_valid(doc):
- ... print("invalid!")
- invalid!
- Note that libxml2 restricts error reporting to the parsing step (when creating
- the Schematron instance). There is not currently any support for error
- reporting during validation.
|