Skip to content

Commit

Permalink
edi_xml_oca: replace xmlschema by lxml
Browse files Browse the repository at this point in the history
  • Loading branch information
Ricardoalso committed Dec 23, 2024
1 parent 162d74d commit 7acec16
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 34 deletions.
2 changes: 1 addition & 1 deletion edi_xml_oca/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"maintainers": ["simahawk"],
"website": "https://github.com/OCA/edi-framework",
"depends": ["edi_oca", "component"],
"external_dependencies": {"python": ["xmlschema"]},
"external_dependencies": {"python": ["xmltodict"]},
}
65 changes: 36 additions & 29 deletions edi_xml_oca/components/xml_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
# @author: Simone Orsi <[email protected]>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import io
from contextlib import closing

import xmlschema
import xmltodict
from lxml import etree

from odoo import modules
from odoo.tools import DotDict
from odoo.exceptions import UserError
from odoo.tools.xml_utils import _check_with_xsd

from odoo.addons.component.core import Component

Expand All @@ -28,52 +27,60 @@ def __init__(self, work_context):
if not hasattr(work_context, key):
raise AttributeError(f"`{key}` is required for this component!")

self.schema = xmlschema.XMLSchema(self._get_xsd_schema_path())
self.schema_path, self.schema = self._get_xsd_schema()

def _get_xsd_schema_path(self):
"""Lookup for XSD schema."""
def _get_xsd_schema(self):
"""Lookup and parse the XSD schema."""
try:
mod_name, path = self.work.schema_path.split(":")
except ValueError as exc:
raise ValueError("Path must be in the form `module:path`") from exc
return modules.get_resource_path(mod_name, path)

def _parse_xml(self, file_obj, **kw):
schema_path = modules.get_resource_path(mod_name, path)
if not schema_path:
return UserError(f"XSD schema file not found: {self.work.schema_path}")

Check warning on line 41 in edi_xml_oca/components/xml_handler.py

View check run for this annotation

Codecov / codecov/patch

edi_xml_oca/components/xml_handler.py#L41

Added line #L41 was not covered by tests

with open(schema_path, "r") as schema_file:
return schema_path, etree.XMLSchema(etree.parse(schema_file))

def _xml_string_to_dict(self, xml_string, **kw):
"""Read xml_content and return a data dict.
:param file_obj: file obj of XML file
:param xml_string: str of XML file
"""
return DotDict(self.schema.to_dict(file_obj, **kw))
parsed_dict = xmltodict.parse(xml_string, **kw)
root_node = next(iter(parsed_dict))
return parsed_dict[root_node]

def parse_xml(self, file_content, **kw):
"""Read XML content.
:param file_content: str of XML file
:return: dict with final data
"""
with closing(io.StringIO(file_content)) as fd:
return self._parse_xml(fd)
return self._xml_string_to_dict(file_content, **kw)

def validate(self, xml_content, raise_on_fail=False):
"""Validate XML content against XSD schema.
Raises `XMLSchemaValidationError` if `raise_on_fail` is True.
:param xml_content: str containing xml data to validate
:raise_on_fail: turn on/off validation error exception on fail
:param raise_on_fail: turn on/off validation error exception on fail
:return:
* None if validation is ok
* error string if `raise_on_fail` is False
* None if validation is ok or skipped
* error string if `raise_on_fail` is False and validation fails
"""
try:
return self.schema.validate(xml_content)
except self._validate_swallable_exceptions() as err:
if raise_on_fail:
raise
return str(err)

def _validate_swallable_exceptions(self):
return (
xmlschema.exceptions.XMLSchemaValueError,
xmlschema.validators.exceptions.XMLSchemaValidationError,
xml_content = (
xml_content.encode("utf-8") if isinstance(xml_content, str) else xml_content
)
try:
with open(self.schema_path, "r") as xsd_stream:
_check_with_xsd(xml_content, xsd_stream)
except FileNotFoundError as exc:
if raise_on_fail:
raise exc
return "XSD schema file not found: %s" % self.schema_path

Check warning on line 82 in edi_xml_oca/components/xml_handler.py

View check run for this annotation

Codecov / codecov/patch

edi_xml_oca/components/xml_handler.py#L81-L82

Added lines #L81 - L82 were not covered by tests
except Exception as exc:
if raise_on_fail:
raise exc
return str(exc)

Check warning on line 86 in edi_xml_oca/components/xml_handler.py

View check run for this annotation

Codecov / codecov/patch

edi_xml_oca/components/xml_handler.py#L86

Added line #L86 was not covered by tests
12 changes: 12 additions & 0 deletions edi_xml_oca/tests/fixtures/simple_schema.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Person">
<xs:complexType>
<xs:sequence>
<xs:element name="Name" type="xs:string" />
<xs:element name="Age" type="xs:integer" />
<xs:element name="Email" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
26 changes: 23 additions & 3 deletions edi_xml_oca/tests/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# @author: Simone Orsi <[email protected]>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo.exceptions import UserError

from odoo.addons.component.tests.common import TransactionComponentCase

from .common import XMLTestCaseMixin
Expand Down Expand Up @@ -36,15 +38,33 @@ def test_xml_schema_fail(self):
self.backend._name, ["edi.xml"], work_ctx={"no_schema": "Nothing"}
)

def test_xml_schema_validation(self):
with self.assertRaises(UserError):
self.handler.validate(TEST_XML, raise_on_fail=True)

self.handler = self.backend._find_component(
self.backend._name,
["edi.xml"],
work_ctx={"schema_path": "edi_xml_oca:tests/fixtures/simple_schema.xsd"},
)

SIMPLE_XML = """<?xml version="1.0" encoding="UTF-8"?>
<Person>
<Name>Mitchell Admin</Name>
<Age>30</Age>
<Email>[email protected]</Email>
</Person>
"""
# Valid XML raises no exception
self.handler.validate(SIMPLE_XML, raise_on_fail=True)

def test_xml(self):
data = self.handler.parse_xml(TEST_XML)
self.assertEqual(
data,
{
"@abstract": False,
"@xmlns:xs": "http://www.w3.org/2001/XMLSchema",
"@name": "shoesize",
"@nillable": False,
"@type": "shoetype",
"@xmlns:xs": "http://www.w3.org/2001/XMLSchema",
},
)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# generated from manifests external_dependencies
PyYAML
xmlschema
xmltodict

0 comments on commit 7acec16

Please sign in to comment.