from pathlib import Path
from typing import Iterable, List, Optional
from xml.etree import ElementTree
from bertlv import utils
from bertlv.tree import TlvError
[docs]class MapperError(TlvError):
def __init__(
self,
message: str,
*,
element: ElementTree.Element = None,
mapping: ElementTree.Element = None,
):
kwargs = {}
if element is not None:
string = ElementTree.tostring(element, encoding="unicode")
kwargs["element"] = f"'{string.rstrip()}'"
if mapping is not None:
string = ElementTree.tostring(mapping, encoding="unicode")
kwargs["mapping"] = f"'{string.rstrip()}'"
super().__init__(f"{message}", **kwargs)
[docs]class XmlMapping:
TYPE_MAP = {
"String": "ASCII",
"Hex": "Hex",
"": "Hex",
}
def __init__(self, root: ElementTree.Element):
if root.tag != "Mapping" and root.tag != "XMLMapping":
raise MapperError(
f"expected root tag 'Mapping' or 'XMLMapping' but found '{root.tag}'"
)
self.root = root
[docs] def lookup(self, tag: str) -> Optional[ElementTree.Element]:
"""Look up the tag in the mapping and return the element."""
return self.root.find(f"./*[@TLVTag='{tag}']")
[docs] def decode(self, element: ElementTree.Element) -> bool:
"""Process the mapping for the given element."""
processed = False
map_entry = self.root.find(f"./*[@XMLTag='{element.tag}']")
if map_entry is not None:
self._check_type(map_entry, element)
element.tag = map_entry.tag
element.attrib.clear()
element.set("Tag", map_entry.get("TLVTag"))
if map_entry.tag == "Primitive":
element.set("Type", self.TYPE_MAP[map_entry.get("Type")])
processed = True
return processed
[docs] def encode(self, element: ElementTree.Element) -> bool:
"""Process the mapping for the given element."""
processed = False
map_entry = self.root.find(f"./{element.tag}[@TLVTag='{element.get('Tag')}']")
if map_entry is not None:
self._check_type(map_entry, element)
element.tag = map_entry.get("XMLTag")
element.attrib.clear()
processed = True
return processed
[docs] @classmethod
def parse(cls, filename: Path) -> "XmlMapping":
"""Parse the given mapping file."""
tree = ElementTree.parse(filename)
root = tree.getroot()
try:
obj = cls(root)
except MapperError as e:
raise MapperError(
f"error occurred while parsing the mapping file {filename}"
) from e
return obj
def _check_type(self, map_entry: ElementTree.Element, element: ElementTree.Element):
if map_entry.tag == "Primitive":
map_type = map_entry.get("Type")
actual_type = element.get("Type")
if map_type and actual_type and actual_type != self.TYPE_MAP[map_type]:
raise MapperError(
f"mapping type '{map_type}' doesn't match the actual type '{actual_type}'",
element=element,
mapping=map_entry,
)
if element.text:
if self.TYPE_MAP[map_type] == "Hex":
try:
utils.xml_text2hex(element)
except ValueError as e:
raise MapperError(
f"value is not hexadecimal as specified by mapping type '{map_entry.get('Type')}'",
element=element,
mapping=map_entry,
) from e
_mappings: List[XmlMapping] = []
[docs]def init(mappings: Iterable[XmlMapping]):
"""Init the mappings list."""
global _mappings
_mappings = mappings
[docs]def parse(filenames: Iterable[Path]):
"""Parse the given mapping files."""
global _mappings
_mappings = [XmlMapping.parse(filename) for filename in filenames]
[docs]def reset():
"""Clear the stored mappings."""
_mappings.clear()
[docs]def lookup(tag: str) -> Optional[ElementTree.Element]:
"""Look up the tag in the mappings and return the element.
Returns None if the tag is not found.
"""
for mapping in _mappings:
element = mapping.lookup(tag)
if element is not None:
return element
return None
[docs]def is_constructed(tag: str) -> Optional[bool]:
"""Look up the tag in the mappings and return True if it's constructed.
Returns None if the tag is not found.
"""
element = lookup(tag)
if element is not None:
return element.tag == "Element"
return None
[docs]def encode(element: ElementTree.Element):
"""Encode the given element using the mappings."""
for mapping in _mappings:
if mapping.encode(element):
break
[docs]def decode(element: ElementTree.Element):
"""Decode the given element using the mappings."""
for mapping in _mappings:
if mapping.decode(element):
break
[docs]def encode_tree(root: ElementTree.Element):
"""Encode all elements in the given tree using the mappings."""
for element in root:
encode(element)
encode_tree(element)
[docs]def decode_tree(root: ElementTree.Element):
"""Decode all elements in the given tree using the mappings."""
for element in root:
decode(element)
decode_tree(element)