Simon Capriles
Simon Capriles

Reputation: 183

How to validate XML with multiple XSD stored in Odoo modules

I need to validate an xml that I generate with two XSD files provided. I am struggling because one of the XSD files references the other one and it looks that I am getting errors because of that.

The following is the "Invoice schema":

<?xml version="1.0" encoding="utf-8"?>
<!-- XSD ver.23/08/2021 -->
<xs:schema xmlns:ds="http://www.w3.org/2000/09/xmldsig#" attributeFormDefault="unqualified"
           elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="../SignatureSchema.xsd"/>
    <xs:element name="facturaElectronicaCompraVenta">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="cabecera">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="nitEmisor">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="1"/>
                                        <xs:maxInclusive value="9999999999999"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="razonSocialEmisor">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="200"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="municipio">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="25"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="telefono" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="25"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="numeroFactura">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="1"/>
                                        <xs:maxInclusive value="9999999999"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="cuf">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="100"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="cufd">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="100"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="codigoSucursal">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="0"/>
                                        <xs:maxInclusive value="9999"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="direccion">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="500"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="codigoPuntoVenta" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="0"/>
                                        <xs:maxInclusive value="9999"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="fechaEmision" type="xs:dateTime"/>
                            <xs:element name="nombreRazonSocial" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="500"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="codigoTipoDocumentoIdentidad">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="1"/>
                                        <xs:maxInclusive value="5"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="numeroDocumento">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="20"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="complemento" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:maxLength value="5"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="codigoCliente">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="100"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="codigoMetodoPago">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="1"/>
                                        <xs:maxInclusive value="308"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="numeroTarjeta" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="0"/>
                                        <xs:maxInclusive value="9999999999999999"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="montoTotal">
                                <xs:simpleType>
                                    <xs:restriction base="xs:decimal">
                                        <xs:totalDigits value="17"/>
                                        <xs:fractionDigits value="2"/>
                                        <xs:minExclusive value="0"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="montoTotalSujetoIva">
                                <xs:simpleType>
                                    <xs:restriction base="xs:decimal">
                                        <xs:totalDigits value="17"/>
                                        <xs:fractionDigits value="2"/>
                                        <xs:minInclusive value="0"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="codigoMoneda">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="1"/>
                                        <xs:maxInclusive value="154"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="tipoCambio">
                                <xs:simpleType>
                                    <xs:restriction base="xs:decimal">
                                        <xs:totalDigits value="17"/>
                                        <xs:fractionDigits value="2"/>
                                        <xs:minExclusive value="0"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="montoTotalMoneda">
                                <xs:simpleType>
                                    <xs:restriction base="xs:decimal">
                                        <xs:totalDigits value="17"/>
                                        <xs:fractionDigits value="2"/>
                                        <xs:minExclusive value="0"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="montoGiftCard" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:decimal">
                                        <xs:totalDigits value="17"/>
                                        <xs:fractionDigits value="2"/>
                                        <xs:minInclusive value="0"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="descuentoAdicional" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:decimal">
                                        <xs:totalDigits value="17"/>
                                        <xs:fractionDigits value="2"/>
                                        <xs:minInclusive value="0"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="codigoExcepcion" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="0"/>
                                        <xs:maxInclusive value="1"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="cafc" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="50"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="leyenda">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="200"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="usuario">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="100"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="codigoDocumentoSector" type="xs:integer" fixed="1"/>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
                <xs:element name="detalle" minOccurs="1" maxOccurs="500">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="actividadEconomica">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="10"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="codigoProductoSin">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="1"/>
                                        <xs:maxInclusive value="99999999"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="codigoProducto">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="50"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="descripcion">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="1"/>
                                        <xs:maxLength value="500"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="cantidad">
                                <xs:simpleType>
                                    <xs:restriction base="xs:decimal">
                                        <xs:totalDigits value="17"/>
                                        <xs:fractionDigits value="2"/>
                                        <xs:minExclusive value="0"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="unidadMedida">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="1"/>
                                        <xs:maxInclusive value="86"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="precioUnitario">
                                <xs:simpleType>
                                    <xs:restriction base="xs:decimal">
                                        <xs:totalDigits value="17"/>
                                        <xs:fractionDigits value="2"/>
                                        <xs:minExclusive value="0"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="montoDescuento" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:decimal">
                                        <xs:totalDigits value="17"/>
                                        <xs:fractionDigits value="2"/>
                                        <xs:minInclusive value="0"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="subTotal">
                                <xs:simpleType>
                                    <xs:restriction base="xs:decimal">
                                        <xs:totalDigits value="17"/>
                                        <xs:fractionDigits value="2"/>
                                        <xs:minExclusive value="0"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="numeroSerie" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="0"/>
                                        <xs:maxLength value="1500"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="numeroImei" nillable="true">
                                <xs:simpleType>
                                    <xs:restriction base="xs:string">
                                        <xs:minLength value="0"/>
                                        <xs:maxLength value="1500"/>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
                <xs:element ref="ds:Signature"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

And the following one is the "Signature schema":

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns="http://www.w3.org/2000/09/xmldsig#" attributeFormDefault="unqualified"
           elementFormDefault="qualified" targetNamespace="http://www.w3.org/2000/09/xmldsig#"
           xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Signature">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="SignedInfo">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="CanonicalizationMethod">
                                <xs:complexType>
                                    <xs:attribute name="Algorithm" type="xs:string" use="required"/>
                                </xs:complexType>
                            </xs:element>
                            <xs:element name="SignatureMethod">
                                <xs:complexType>
                                    <xs:attribute name="Algorithm" type="xs:string" use="required"/>
                                </xs:complexType>
                            </xs:element>
                            <xs:element name="Reference">
                                <xs:complexType>
                                    <xs:sequence>
                                        <xs:element name="Transforms">
                                            <xs:complexType>
                                                <xs:sequence>
                                                    <xs:element maxOccurs="unbounded" name="Transform">
                                                        <xs:complexType>
                                                            <xs:attribute name="Algorithm" type="xs:string"
                                                                          use="required"/>
                                                        </xs:complexType>
                                                    </xs:element>
                                                </xs:sequence>
                                            </xs:complexType>
                                        </xs:element>
                                        <xs:element name="DigestMethod">
                                            <xs:complexType>
                                                <xs:attribute name="Algorithm" type="xs:string" use="required"/>
                                            </xs:complexType>
                                        </xs:element>
                                        <xs:element name="DigestValue" type="xs:string"/>
                                    </xs:sequence>
                                    <xs:attribute name="URI" type="xs:string" use="required"/>
                                </xs:complexType>
                            </xs:element>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
                <xs:element name="SignatureValue" type="xs:string"/>
                <xs:element name="KeyInfo">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="X509Data">
                                <xs:complexType>
                                    <xs:sequence>
                                        <xs:element name="X509Certificate" type="xs:string"/>
                                    </xs:sequence>
                                </xs:complexType>
                            </xs:element>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

The "Invoice schema" references the "Signature schema" with the namespace "schemaLocation" to the following location "../SignatureSchema.xsd". I am working with Odoo and I am storing the "Invoice schema" file under "MyModule/data/xsd/sale_purchase" and the "Signature schema" uder "MyModule/data/xsd/".

So I validate my files with the following code:

    def _validate_invoice_xml(self):
        root = self._get_xml_tree_root()
        # Validate generated xml
        xsd_file_path = get_module_resource('MyModule', 'data/xsd/sale_purchase', 'InvoiceSchema.xsd')
        with open(xsd_file_path) as xsd_file:
            invoice_schema = xsd_file.read()
        # Decode XSD file
        invoice_xsd = ET.fromstring(invoice_schema.encode('ascii'))
        xmlschema = ET.XMLSchema(invoice_xsd)
        validation_result = xmlschema.validate(root)
        if not validation_result:
            log = xmlschema.error_log
            error = log.last_error
            raise exceptions.ValidationError('XML file validation error:' + '\n' + str(error.message))

But when I run my code I get the following error:

lxml.etree.XMLSchemaParseError: Element '{http://www.w3.org/2001/XMLSchema}element', attribute 'ref': The QName value '{http://www.w3.org/2000/09/xmldsig#}Signature' does not resolve to a(n) element declaration., line 344

I believe that the error is related with the referencing of the "Signaute schema" with schemaLocation="../SignatureSchema.xsd". But I am not sure, the files are located where they should.

Why am I getting this error and how can I avoid it?

Upvotes: 0

Views: 374

Answers (1)

Simon Capriles
Simon Capriles

Reputation: 183

As Michael Kay commented, I needed to get the absolute path to the XSD file. In odoo I have managed to do it by using the following code:

import os

# This gets the location of the absolute location of the python file running
current_location = os.path.dirname(os.path.realpath(__file__))
# Replacing part of the path to point to the XSD file location
schema_file_location = current_location.replace('models', 'data/pathToMyXSDfile.xsd')
# Validate XML file
xsd_file = ET.parse(schema_file_location)
xmlschema = ET.XMLSchema(xsd_file)
validation_result = xmlschema.validate(root)

Upvotes: 1

Related Questions