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 13, 2024
1 parent 162d74d commit ee5cf16
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 41 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"]},
}
71 changes: 39 additions & 32 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 xmltodict
from lxml import etree

import xmlschema

from odoo import modules
from odoo.tools import DotDict
from odoo import _, modules
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):
"""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)
root_node = next(iter(parsed_dict))
return parsed_dict[root_node]

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)
return self._xml_string_to_dict(file_content)

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 UserError(_("XSD schema file not found")) from 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 UserError(_("XML validation error")) from 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
11 changes: 7 additions & 4 deletions edi_xml_oca/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

/*
:Author: David Goodger ([email protected])
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.

Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.

See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
Expand Down Expand Up @@ -274,7 +275,7 @@
margin-left: 2em ;
margin-right: 2em }

pre.code .ln { color: grey; } /* line numbers */
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
Expand All @@ -300,7 +301,7 @@
span.pre {
white-space: pre }

span.problematic {
span.problematic, pre.problematic {
color: red }

span.section-subtitle {
Expand Down Expand Up @@ -423,7 +424,9 @@ <h2><a class="toc-backref" href="#toc-entry-5">Other credits</a></h2>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
Expand Down
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 ee5cf16

Please sign in to comment.