Skip to content

Commit 6416e48

Browse files
committed
Add XML parser
1 parent 1ce1b96 commit 6416e48

File tree

8 files changed

+71
-4
lines changed

8 files changed

+71
-4
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ This web service implements a **[Data Validation API](#API)** being specified as
1313
- [From sources](#from-sources)
1414
- [With Docker](#with-docker)
1515
- [Configuration](#configuration)
16+
- [Profiles](#profiles)
17+
- [Checks](#checks)
1618
- [API](#api)
1719
- [GET /profiles](#get-profiles)
1820
- [GET /{profile}/validate](#get-profilevalidate)
@@ -65,6 +67,16 @@ The [default configuration](config.default.json) contains some base formats. To
6567

6668
Each application profile is configured with a JSON object having a unique `id`, a list of `checks`, and additional metadata. See [profiles configuration JSON Schema](lib/validate/profiles-schema.json) for details.
6769

70+
### Checks
71+
72+
Each check is either a string, referencing another profile or a base format, or a JSON object for a more complex check.
73+
74+
### Base formats
75+
76+
- `json`
77+
- `xml`
78+
- ...
79+
6880
## API
6981

7082
The webservice provides one endpoint to [list application profiles](#get-profiles) and one **Data Validation API** endpoint for each profile to validate data.

config.default.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
{
88
"id": "json",
99
"checks": ["json"]
10+
},
11+
{
12+
"id": "xml",
13+
"checks": ["xml"]
1014
}
1115
]
1216
}

lib/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from .validate import Validator, ValidationError, validateJSON, parseJSON
1+
from .validate import Validator, ValidationError, validateJSON, parseJSON, parseXML
22
from .service import ValidationService
33

4-
__all__ = [ValidationService, Validator, ValidationError, validateJSON, parseJSON]
4+
__all__ = [ValidationService, Validator, ValidationError,
5+
validateJSON, parseJSON, parseXML]

lib/validate/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
from .error import ValidationError
33
from .jsonschema import validateJSON
44
from .json import parseJSON
5+
from .xml import parseXML
56

6-
__all__ = [Validator, ValidationError, validateJSON, parseJSON]
7+
__all__ = [Validator, ValidationError, validateJSON, parseJSON, parseXML]

lib/validate/validator.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from pathlib import Path
33
from .json import parseJSON
44
from .jsonschema import validateJSON
5+
from .xml import parseXML
56

67

78
schema = json.load((Path(__file__).parent / 'profiles-schema.json').open())
@@ -35,3 +36,5 @@ def execute(self, profile, data=None, file=None):
3536
for check in checks:
3637
if check == "json":
3738
parseJSON(data)
39+
elif check == "xml":
40+
parseXML(data)

lib/validate/xml.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from .error import ValidationError
2+
from xml.dom.minidom import parse, parseString, Document
3+
from xml.parsers.expat import ExpatError
4+
from xml.parsers.expat.errors import messages
5+
from io import IOBase
6+
7+
8+
def parseXML(data) -> Document:
9+
try:
10+
if isinstance(data, IOBase):
11+
return parse(data)
12+
else:
13+
return parseString(data)
14+
except ExpatError as e:
15+
msg = messages[e.code]
16+
col = e.offset + 1
17+
pos = {
18+
"line": str(e.lineno),
19+
"linecol": f"{e.lineno}:{col}",
20+
}
21+
raise ValidationError(msg, pos)

tests/test_service.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ def test_config():
1616
"id": "json",
1717
"checks": ["json"],
1818
"url": "https://json.org/"
19+
}, {
20+
"id": "xml",
21+
"checks": ["xml"]
1922
}]
2023

2124
service = ValidationService(profiles=profiles)
22-
assert service.profiles() == [{"id": "json", "url": "https://json.org/"}]
25+
assert service.profiles() == [
26+
{"id": "json", "url": "https://json.org/"}, {"id": "xml"}]
2327

2428
with pytest.raises(Exception, match=r"This service does not support passing data via URL"):
2529
service.validate('json', url="http://example.org/")

tests/test_xml.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from lib import ValidationError, parseXML
2+
from xml.dom.minidom import Document
3+
from xml.parsers.expat.errors import codes
4+
5+
not_wellformed = [
6+
('<a x="1"\n木="1" x="2"/>', { # string
7+
"message": 'duplicate attribute',
8+
"position": {"line": "2", "linecol": "2:7"}}),
9+
('<?xml version="1.0"?>\n<木/>?'.encode("UTF-8"), { # binary
10+
"message": 'not well-formed (invalid token)',
11+
"position": {"line": "2", "linecol": "2:5"}}),
12+
]
13+
14+
15+
def test_not_wellformed():
16+
for (data, err) in not_wellformed:
17+
try:
18+
assert isinstance(parseXML(data), Document)
19+
assert 0 == "ValidationError should have been thrown!" # pragma: no cover
20+
except ValidationError as e:
21+
assert e.to_dict() == err

0 commit comments

Comments
 (0)