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 cd9d00e
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 36 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", "lxml"]},
}
55 changes: 28 additions & 27 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 import validate_xml_from_attachment

from odoo.addons.component.core import Component

Expand All @@ -28,35 +27,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 +68,13 @@ def validate(self, xml_content, raise_on_fail=False):
* None if validation is ok
* error string if `raise_on_fail` is False
"""
xsd_name = self.schema_path
xml_content = (
xml_content.encode("utf-8") if isinstance(xml_content, str) else xml_content
)
try:
return self.schema.validate(xml_content)
except self._validate_swallable_exceptions() as err:
validate_xml_from_attachment(self.env, xml_content, xsd_name)
except UserError as exc:
if raise_on_fail:
raise
return str(err)

def _validate_swallable_exceptions(self):
return (
xmlschema.exceptions.XMLSchemaValueError,
xmlschema.validators.exceptions.XMLSchemaValidationError,
)
raise exc

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

View check run for this annotation

Codecov / codecov/patch

edi_xml_oca/components/xml_handler.py#L79

Added line #L79 was not covered by tests
return str(exc)
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
32 changes: 29 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.tools import file_open

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

from .common import XMLTestCaseMixin
Expand Down Expand Up @@ -36,15 +38,39 @@ 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(TEST_XML))
XML_FAULTY = """<?xml version="1.0" encoding="UTF-8"?>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" name="shoesize" type="shoetype">
<xs:element name="test" type="test" />
</xs:element>
"""

schema_path = self.handler.schema_path
with file_open(schema_path, "rb") as f:
schema_xml = f.read()
# store the schema in ir.attachment as it is searched
# inside _check_with_xsd method defined in odoo.tools
self.env["ir.attachment"].create(
{
"name": schema_path,
"datas": schema_xml,
"type": "binary",
"res_model": "edi.exchange.template.output",
"res_id": 1,
"raw": schema_xml,
}
)

self.assertIsNotNone(self.handler.validate(XML_FAULTY))

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",
},
)
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# generated from manifests external_dependencies
lxml
PyYAML
xmlschema
xmltodict

0 comments on commit cd9d00e

Please sign in to comment.