Skip to content

Commit f82add5

Browse files
authored
Merge pull request #404 from andlaus/hex_binary
centralize parsing of `xsd:hexBinary` values
2 parents 6d17717 + c765552 commit f82add5

File tree

4 files changed

+35
-13
lines changed

4 files changed

+35
-13
lines changed

odxtools/posresponsesuppressible.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from .exceptions import odxrequire
77
from .odxlink import OdxDocFragment
8+
from .utils import read_hex_binary
89

910

1011
# note that the spec has a typo here: it calls the corresponding
@@ -29,7 +30,7 @@ class PosResponseSuppressible:
2930
def from_et(et_element: ElementTree.Element,
3031
doc_frags: List[OdxDocFragment]) -> "PosResponseSuppressible":
3132

32-
bit_mask = int(odxrequire(et_element.findtext("BIT-MASK")))
33+
bit_mask = odxrequire(read_hex_binary(et_element.find("BIT-MASK")))
3334

3435
coded_const_snref = None
3536
if (cc_snref_elem := et_element.find("CODED-CONST-SNREF")) is not None:

odxtools/standardlengthtype.py

+2-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .exceptions import odxassert, odxraise, odxrequire
1212
from .odxlink import OdxDocFragment
1313
from .odxtypes import AtomicOdxType, BytesTypes, DataType, odxstr_to_bool
14-
from .utils import dataclass_fields_asdict
14+
from .utils import dataclass_fields_asdict, read_hex_binary
1515

1616

1717
@dataclass
@@ -36,16 +36,7 @@ def from_et(et_element: ElementTree.Element,
3636
kwargs = dataclass_fields_asdict(DiagCodedType.from_et(et_element, doc_frags))
3737

3838
bit_length = int(odxrequire(et_element.findtext("BIT-LENGTH")))
39-
bit_mask = None
40-
if (bit_mask_str := et_element.findtext("BIT-MASK")) is not None:
41-
# The XSD uses the type xsd:hexBinary
42-
# xsd:hexBinary allows for leading/trailing whitespace, empty strings, and it only allows an even
43-
# number of hex digits, while some of the examples shown in the ODX specification exhibit an
44-
# odd number of hex digits.
45-
# This causes a validation paradox, so we try to be flexible
46-
bit_mask_str = bit_mask_str.strip()
47-
if len(bit_mask_str):
48-
bit_mask = int(bit_mask_str, 16)
39+
bit_mask = read_hex_binary(et_element.find("BIT-MASK"))
4940
is_condensed_raw = odxstr_to_bool(et_element.get("IS-CONDENSED"))
5041

5142
return StandardLengthType(

odxtools/templates/macros/printService.xml.jinja2

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
{%- macro printPosResponseSuppressible(prs) -%}
1010
<POS-RESPONSE-SUPPRESSABLE>
11-
<BIT-MASK>{{ hex(prs.bit_mask).lower() }}</BIT-MASK>
11+
{%- set num_nibbles = (prs.bit_mask.bit_length() + 7) // 8 * 2 %}
12+
<BIT-MASK>{{ ("%%0%dX" | format(num_nibbles | int)) | format(prs.bit_mask | int) }}</BIT-MASK>
1213
{%- if prs.coded_const_snref is not none %}
1314
<CODED-CONST-SNREF SHORT-NAME="{{ prs.coded_const_snref }}" />
1415
{%- endif %}

odxtools/utils.py

+29
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,42 @@
22
import dataclasses
33
import re
44
from typing import TYPE_CHECKING, Any, Dict, Optional
5+
from xml.etree import ElementTree
6+
7+
from .exceptions import odxraise
58

69
if TYPE_CHECKING:
710
from .database import Database
811
from .diaglayers.diaglayer import DiagLayer
912
from .snrefcontext import SnRefContext
1013

1114

15+
def read_hex_binary(et_element: Optional[ElementTree.Element]) -> Optional[int]:
16+
"""Convert the contents of an xsd:hexBinary to an integer
17+
"""
18+
if et_element is None:
19+
return None
20+
21+
if (bytes_str := et_element.text) is None:
22+
# tag exists but is immediately terminated ("<FOO />"). we
23+
# treat this like an empty string.
24+
return 0
25+
26+
# The XSD uses the type xsd:hexBinary and xsd:hexBinary allows for
27+
# leading/trailing whitespace and empty strings whilst `int(x,
28+
# 16)` raises an exception if one of these things happen.
29+
bytes_str = bytes_str.strip()
30+
if len(bytes_str) == 0:
31+
return 0
32+
33+
try:
34+
return int(bytes_str, 16)
35+
except Exception as e:
36+
odxraise(f"Caught exception while parsing hex string `{bytes_str}`"
37+
f" of {et_element.tag}: {e}")
38+
return None
39+
40+
1241
def retarget_snrefs(database: "Database",
1342
diag_layer: "DiagLayer",
1443
context: Optional["SnRefContext"] = None) -> None:

0 commit comments

Comments
 (0)