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 11, 2024
1 parent 162d74d commit d1d9eb3
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 31 deletions.
54 changes: 26 additions & 28 deletions edi_xml_oca/components/xml_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
# @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.tools import validate_xml_from_attachment

from odoo.addons.component.core import Component

Expand All @@ -28,35 +26,39 @@ 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)
with open(schema_path, "rb") as schema_file:
return schema_path, etree.XMLSchema(etree.parse(schema_file))

def _xml_string_to_dict(self, xml_string):
"""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))
return xmltodict.parse(xml_string)["xs:element"]

def parse_xml(self, file_content, **kw):
def parse_xml(self, file_content):
"""Read XML content.
:param file_content: str of XML file
:param file_content: unicode str of XML file
:return: dict with final data
"""
with closing(io.StringIO(file_content)) as fd:
return self._parse_xml(fd)
tree = etree.XML(file_content.encode("utf-8"))
xml_string = etree.tostring(tree).decode("utf-8")
return self._xml_string_to_dict(xml_string)

def validate(self, xml_content, raise_on_fail=False):
"""Validate XML content against XSD schema.
Raises `XMLSchemaValidationError` if `raise_on_fail` is True.
Raises `etree.DocumentInvalid` 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
Expand All @@ -65,15 +67,11 @@ def validate(self, xml_content, raise_on_fail=False):
* None if validation is ok
* error string if `raise_on_fail` is False
"""
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,
xsd_name = self.schema_path
xml_content = (
xml_content.encode("utf-8") if isinstance(xml_content, str) else xml_content
)
res = validate_xml_from_attachment(self.env, xml_content, xsd_name)
if raise_on_fail and res:
raise ValueError(res)
return res
17 changes: 14 additions & 3 deletions edi_xml_oca/tests/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,26 @@ def test_xml_schema_fail(self):
self.backend._name, ["edi.xml"], work_ctx={"no_schema": "Nothing"}
)

def test_xml_schema_validation(self):
self.assertIsNone(
self.handler.validate(
"""<?xml version="1.0" encoding="UTF-8"?>
<StandardBusinessDocumentHeader xmlns="http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader">
<Shoetype country="USA">42</Shoetype>
</StandardBusinessDocumentHeader>
"""
)
)

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",
},
)

0 comments on commit d1d9eb3

Please sign in to comment.