summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--odoo/addons/test_testing_utilities/tests/__init__.py1
-rw-r--r--odoo/addons/test_testing_utilities/tests/test_lxml.py54
-rw-r--r--odoo/tools/xml_utils.py46
3 files changed, 95 insertions, 6 deletions
diff --git a/odoo/addons/test_testing_utilities/tests/__init__.py b/odoo/addons/test_testing_utilities/tests/__init__.py
index 7be78314304..7193c4e5f94 100644
--- a/odoo/addons/test_testing_utilities/tests/__init__.py
+++ b/odoo/addons/test_testing_utilities/tests/__init__.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import test_methods
+from . import test_lxml
from . import test_form_impl
diff --git a/odoo/addons/test_testing_utilities/tests/test_lxml.py b/odoo/addons/test_testing_utilities/tests/test_lxml.py
new file mode 100644
index 00000000000..fac6013bd67
--- /dev/null
+++ b/odoo/addons/test_testing_utilities/tests/test_lxml.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.tests import common
+from odoo.tools.xml_utils import _check_with_xsd
+
+import base64
+from lxml.etree import XMLSchemaError
+
+class TestLXML(common.TransactionCase):
+ def test_lxml_import_from_filestore(self):
+ resolver_schema_int = b"""
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:etype="http://codespeak.net/lxml/test/external">
+ <xsd:import namespace="http://codespeak.net/lxml/test/external" schemaLocation="imported_schema.xsd"/>
+ <xsd:element name="a" type="etype:AType"/>
+ </xsd:schema>
+ """
+
+ incomplete_schema_int = b"""
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:etype="http://codespeak.net/lxml/test/external">
+ <xsd:import namespace="http://codespeak.net/lxml/test/external" schemaLocation="non_existing_schema.xsd"/>
+ <xsd:element name="a" type="etype:AType"/>
+ </xsd:schema>
+ """
+
+ imported_schema = b"""
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://codespeak.net/lxml/test/external">
+ <xsd:complexType name="AType">
+ <xsd:sequence><xsd:element name="b" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/></xsd:sequence>
+ </xsd:complexType>
+ </xsd:schema>
+ """
+
+ self.env['ir.attachment'].create([{
+ 'datas': base64.b64encode(resolver_schema_int),
+ 'name': 'resolver_schema_int.xsd'
+ }, {
+ 'datas': base64.b64encode(incomplete_schema_int),
+ 'name': 'incomplete_schema_int.xsd'
+ }, {
+ 'datas': base64.b64encode(imported_schema),
+ 'name': 'imported_schema.xsd'
+ }])
+
+ _check_with_xsd("<a><b></b></a>", 'resolver_schema_int.xsd', self.env)
+
+ with self.assertRaises(XMLSchemaError):
+ _check_with_xsd("<a><b></b></a>", 'incomplete_schema_int.xsd', self.env)
+
+ with self.assertRaises(FileNotFoundError):
+ _check_with_xsd("<a><b></b></a>", 'non_existing_schema.xsd', self.env)
diff --git a/odoo/tools/xml_utils.py b/odoo/tools/xml_utils.py
index eb5b8b203bf..513aa207663 100644
--- a/odoo/tools/xml_utils.py
+++ b/odoo/tools/xml_utils.py
@@ -1,22 +1,56 @@
# -*- coding: utf-8 -*-
+import base64
+from io import BytesIO
from lxml import etree
-from odoo.tools.misc import file_open
+
from odoo.exceptions import UserError
+
+class odoo_resolver(etree.Resolver):
+ """Odoo specific file resolver that can be added to the XML Parser.
+
+ It will search filenames in the ir.attachments
+ """
+
+ def __init__(self, env):
+ super().__init__()
+ self.env = env
+
+ def resolve(self, url, id, context):
+ """Search url in ``ir.attachment`` and return the resolved content."""
+ attachment = self.env['ir.attachment'].search([('name', '=', url)])
+ if attachment:
+ return self.resolve_string(base64.b64decode(attachment.datas), context)
+
+
def check_with_xsd(tree_or_str, stream):
raise UserError("Method 'check_with_xsd' deprecated ")
+def _check_with_xsd(tree_or_str, stream, env=None):
+ """Check an XML against an XSD schema.
-def _check_with_xsd(tree_or_str, stream):
+ This will raise a UserError if the XML file is not valid according to the
+ XSD file.
+ :param tree_or_str (etree, str): representation of the tree to be checked
+ :param stream (io.IOBase, str): the byte stream used to build the XSD schema.
+ If env is given, it can also be the name of an attachment in the filestore
+ :param env (odoo.api.Environment): If it is given, it enables resolving the
+ imports of the schema in the filestore with ir.attachments.
+ """
if not isinstance(tree_or_str, etree._Element):
tree_or_str = etree.fromstring(tree_or_str)
- xml_schema_doc = etree.parse(stream)
- xsd_schema = etree.XMLSchema(xml_schema_doc)
+ parser = etree.XMLParser()
+ if env:
+ parser.resolvers.add(odoo_resolver(env))
+ if isinstance(stream, str) and stream.endswith('.xsd'):
+ attachment = env['ir.attachment'].search([('name', '=', stream)])
+ if not attachment:
+ raise FileNotFoundError()
+ stream = BytesIO(base64.b64decode(attachment.datas))
+ xsd_schema = etree.XMLSchema(etree.parse(stream, parser=parser))
try:
xsd_schema.assertValid(tree_or_str)
except etree.DocumentInvalid as xml_errors:
- #import UserError only here to avoid circular import statements with tools.func being imported in exceptions.py
- from odoo.exceptions import UserError
raise UserError('\n'.join(str(e) for e in xml_errors.error_log))