diff --git a/odoo/addons/test_testing_utilities/tests/__init__.py b/odoo/addons/test_testing_utilities/tests/__init__.py
index 7be7831430492ebb80643d6db2a2262c5189c27a..7193c4e5f94f3d7bb931dce6730d01d2d66d4307 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 0000000000000000000000000000000000000000..fac6013bd675f9c5884e1d4ffc8296862e573f55
--- /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 eb5b8b203bff0020bce3cf4350f04a16290779d8..513aa20766352a6879f27a04deced200c9673538 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))