From bedec437b4dcbfd1af245e2e2b5e442df29ec714 Mon Sep 17 00:00:00 2001 From: ebroecker Date: Fri, 7 May 2021 09:14:58 +0200 Subject: [PATCH 1/2] ARXML Replace ArTree with more simple cache add option to rename Signal from Signal-Attribute --- src/canmatrix/cli/convert.py | 4 ++ src/canmatrix/convert.py | 4 ++ src/canmatrix/formats/arxml.py | 97 +++++++++---------------------- src/canmatrix/tests/test_arxml.py | 4 +- 4 files changed, 37 insertions(+), 72 deletions(-) diff --git a/src/canmatrix/cli/convert.py b/src/canmatrix/cli/convert.py index e67f29c8a..a4f2b17ee 100644 --- a/src/canmatrix/cli/convert.py +++ b/src/canmatrix/cli/convert.py @@ -75,6 +75,10 @@ def get_formats(): @click.option('--frames', help="Copy only given Frames (comma separated list) to target matrix") @click.option('--signals', help="Copy only given Signals (comma separated list) to target matrix just as 'free' signals without containing frame") @click.option('--merge', help="merge additional can databases.\nSyntax: --merge filename[:ecu=SOMEECU][:frame=FRAME1][:frame=FRAME2],filename2") +@click.option('--signalNameFromAttrib', 'signalNameFromAttrib', help="change signal_name to given signal attribute\n\ +Example --signal_name_from_attrib SysSignalName\nARXML known Attributes: SysSignalName, ISignalName, CompuMethodName", default=None) + + # arxml switches @click.option('--arxmlIgnoreClusterInfo/--no-arxmlIgnoreClusterInfo', 'arxmlIgnoreClusterInfo', default=False, help="Ignore any can cluster info from arxml; Import all frames in one matrix\ndefault False") @click.option('--arxmlUseXpath/--no-arxmlUseXpath', 'arxmlUseXpath', default=False, help="Use experimental Xpath-Implementation for resolving AR-Paths; \ndefault False") diff --git a/src/canmatrix/convert.py b/src/canmatrix/convert.py index c0838042b..c84711760 100644 --- a/src/canmatrix/convert.py +++ b/src/canmatrix/convert.py @@ -202,6 +202,10 @@ def convert(infile, out_file_name, **options): # type: (str, str, **str) -> Non if 'recalcDLC' in options and options['recalcDLC']: db.recalc_dlc(options['recalcDLC']) + if options.get('signalNameFromAttrib') is not None: + for signal in [b for a in db for b in a.signals]: + signal.name = signal.attributes.get(options.get('signalNameFromAttrib'), signal.name) + logger.info(name) logger.info("%d Frames found" % (db.frames.__len__())) diff --git a/src/canmatrix/formats/arxml.py b/src/canmatrix/formats/arxml.py index 4ea843456..4f001fadd 100644 --- a/src/canmatrix/formats/arxml.py +++ b/src/canmatrix/formats/arxml.py @@ -45,33 +45,8 @@ clusterImporter = 1 -class ArTree(object): - def __init__(self, name="", ref=None): # type: (str, lxml.etree._Element) -> None - self._name = name - self._ref = ref - self._array = [] # type: typing.List[ArTree] - self._names = {} # type: typing.Dict[str, ArTree] - - def append_child(self, name, child): # type: (str, typing.Any) -> ArTree - """Append new child and return it.""" - temp = ArTree(name, child) - self._array.append(temp) - self._names[name] = temp - return temp - - def get_child_by_name(self, name): # type: (str) -> typing.Union[ArTree, None] - if name in self._names: - return self._names[name] - return None - - @property - def ref(self): # type: () -> lxml.etree._Element - return self._ref - - # for typing only _Element = lxml.etree._Element -_DocRoot = typing.Union[_Element, ArTree] _MultiplexId = typing.Union[str, int, None] _FloatFactory = typing.Callable[[typing.Any], typing.Any] @@ -81,6 +56,25 @@ def __init__(self, use_ar_xpath): self.xml_element_cache = dict() # type: typing.Dict[str, _Element] self.use_ar_xpath = use_ar_xpath self.xml_element_cache = {} + self.path_cache = {} + + def fill_caches(self, start_element=None, ar_path=""): + if start_element is None: + start_element = self.root + self.path_cache = {} + if start_element.tag == self.ns + "SHORT-NAME": + return start_element.text + for sub_element in start_element: + text = sub_element.text + if text is not None and len(text) > 0 and text.startswith('/'): + if text not in self.path_cache: + self.path_cache[text] = [] + self.path_cache[text].append(sub_element) + new_ar_path = self.fill_caches(sub_element, ar_path) + if new_ar_path != "": + ar_path += '/' + new_ar_path + self.xml_element_cache[ar_path] = start_element + return '' def open(self, filename): self.tree = lxml.etree.parse(filename) @@ -89,6 +83,8 @@ def open(self, filename): self.ns = "{" + self.tree.xpath('namespace-uri(.)') + "}" # type: str self.nsp = self.tree.xpath('namespace-uri(.)') + if not self.use_ar_xpath: + self.fill_caches() def findall(self, xpath, start_element=None): if start_element is None: @@ -128,9 +124,7 @@ def get_short_name_path(self, shortname_path): self.xml_element_cache[shortname_path] = temp[0] return temp[0] return None - else: - return self.get_shortname_path_from_artree(shortname_path) - return elems + return None def get_short_name(self, element): # type: (_Element, str) -> str @@ -162,17 +156,6 @@ def get_all_sub_by_name(self, start_element, name): def get_sub_by_name(self, start_element, name): return self.find(name, start_element) - def fill_tree_from_xml(self, tag, tree_point): - # type: (_Element, ArTree, str) -> None - """Parse the xml tree into ArTree objects.""" - for child in tag: # type: _Element - name_elem = child.find('./' + self.ns + 'SHORT-NAME') - # long_name = child.find('./' + namespace + 'LONG-NAME') - if name_elem is not None and child is not None: - self.fill_tree_from_xml(child, tree_point.append_child(name_elem.text, child)) - if name_elem is None and child is not None: - self.fill_tree_from_xml(child, tree_point) - def find_children_by_path(self, from_element, path): # type: (_Element, str, _DocRoot, str) -> typing.Sequence[_Element] path_elements = path.split('/') @@ -190,17 +173,6 @@ def get_element_name(self, parent): return name.text return "" - def get_shortname_path_from_artree(self, path): - # type: (ArTree, str) -> typing.Optional[_Element] - """Get element from ArTree by path.""" - ptr = self.ar_tree - for name in path.split('/'): - if ptr is None: - return None - if name.strip(): - ptr = ptr.get_child_by_name(name) - return ptr.ref if ptr else None - def get_child(self, parent, tag_name): # type: (_Element, str, _DocRoot, str) -> typing.Optional[_Element] """Get first sub-child or referenced sub-child with given name.""" @@ -227,19 +199,6 @@ def get_children(self, parent, tag_name): raise "use follow_all_ref!" return ret - def build_ar_tree(self): - top_level_packages = self.find('TOP-LEVEL-PACKAGES') - - if top_level_packages is None: - # no "TOP-LEVEL-PACKAGES found, try root - top_level_packages = self.root - - if self.use_ar_xpath: - search_point = top_level_packages # type: typing.Union[_Element, ArTree] - else: - self.ar_tree = ArTree() - self.fill_tree_from_xml(top_level_packages, self.ar_tree) - def get_element_desc(self, element): # type: (_Element, _DocRoot, str) -> str """Get element description from XML.""" @@ -264,7 +223,6 @@ def get_ecu_instance(self, element): else: return None - def create_sub_element(parent, element_name, text=None, dest=None): # type: (_Element, str, typing.Optional[str]) -> _Element sn = lxml.etree.SubElement(parent, element_name) @@ -274,7 +232,6 @@ def create_sub_element(parent, element_name, text=None, dest=None): sn.set("DEST", dest) return sn - def get_base_type_of_signal(signal): # type: (canmatrix.Signal) -> typing.Tuple[str, int] """Get signal arxml-type and size based on the Signal properties.""" @@ -1274,7 +1231,7 @@ def get_signals(signal_array, frame, ea, multiplex_id, float_factory, bit_offset if isignal_name is not None and isignal_name: new_signal.add_attribute("ISignalName", isignal_name) if system_signal_name is not None and system_signal_name: - new_signal.add_attribute("ShortName", system_signal_name) + new_signal.add_attribute("SysSignalName", system_signal_name) existing_signal = frame.signal_by_name(new_signal.name) if existing_signal is None: frame.add_signal(new_signal) @@ -1298,7 +1255,7 @@ def get_frame_from_multiplexed_ipdu(pdu, target_frame, multiplex_translation, ea multiplexor.initial_value = 0 target_frame.add_signal(multiplexor) static_part = ea.get_child(pdu, "STATIC-PART") - ipdu = ea.get_child(static_part, "I-PDU") + ipdu = ea.follow_ref(static_part, "I-PDU-REF") if ipdu is not None: pdu_sig_mappings = ea.get_child(ipdu, "SIGNAL-TO-PDU-MAPPINGS") pdu_sig_mapping = ea.get_children(pdu_sig_mappings, "I-SIGNAL-TO-I-PDU-MAPPING") @@ -1938,10 +1895,8 @@ def load(file, **options): ea = Earxml(use_ar_xpath) ea.open(file) - logger.debug("Build arTree ...") - ea.build_ar_tree() - com_module = ea.get_short_name_path("ActiveEcuC/Com") + com_module = ea.get_short_name_path("/ActiveEcuC/Com") if com_module is not None and len(com_module) > 0: logger.info("seems to be a ECUC arxml. Very limited support for extracting canmatrix.") @@ -1962,4 +1917,6 @@ def load(file, **options): result.update(decode_can_helper(ea, float_factory, ignore_cluster_info)) + + return result diff --git a/src/canmatrix/tests/test_arxml.py b/src/canmatrix/tests/test_arxml.py index adca9ec3f..aeca55705 100644 --- a/src/canmatrix/tests/test_arxml.py +++ b/src/canmatrix/tests/test_arxml.py @@ -24,8 +24,8 @@ def test_get_signals_from_container_i_pdu(): assert matrix["New_CanCluster"].frames[0].signals[1].name == 'Header_DLC' assert matrix["New_CanCluster"].frames[0].signals[2].name == 'PDU_Contained_1_Signal1' assert matrix["New_CanCluster"].frames[0].signalGroups[0].signals[0].name == 'PDU_Contained_1_Signal1' - assert matrix["New_CanCluster"].frames[0].signals[2].attributes["ShortName"] == 'PDU_Contained_1_Signal1_905db81da40081cb' - assert matrix["New_CanCluster"].frames[0].signalGroups[0].signals[0].attributes["ShortName"] == 'PDU_Contained_1_Signal1_905db81da40081cb' + assert matrix["New_CanCluster"].frames[0].signals[2].attributes["SysSignalName"] == 'PDU_Contained_1_Signal1_905db81da40081cb' + assert matrix["New_CanCluster"].frames[0].signalGroups[0].signals[0].attributes["SysSignalName"] == 'PDU_Contained_1_Signal1_905db81da40081cb' def test_get_signals_from_secured_pdu(): here = Path(__file__).parent From 884116c75f6400d6d179915d20d683e80a637fe3 Mon Sep 17 00:00:00 2001 From: ebroecker Date: Fri, 7 May 2021 09:26:30 +0200 Subject: [PATCH 2/2] add/update doc --- docs/cli.rst | 18 +++++++++++++++--- src/canmatrix/cli/convert.py | 5 +++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index f5b565dce..7b2c5d0a9 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -206,6 +206,13 @@ this will remove signales ``mySignal`` and ``mySignal2`` in ``source.dbc`` and s this will load ``source.dbc`` and rename signals ``mySignal`` in ``myNewSignal`` and ``mySignal2`` in ``myNewSignal2``. The result is stored in ``target.dlc``. +:: + + $ canconvert --signalNameFromAttrib=CompuMethodName source.ARXML target.dbc + +this will load ``source.arxml`` and create ``target.dlc`` while all signals are named from CompuMethodName in ARXML. +Also known values from ARXML are SysSignalName and ISignalName + **canFD:** :: @@ -361,6 +368,11 @@ ____________________ rename Signal form databases. (comma separated list) Syntax: --renameSignal=myOldSignal:myNewSignal,mySecondSignal:mySecondNewSignal + --signalNameFromAttrib=ATTRIBUTENAME + + change signal_name to given signal attribute Syntax: --signalNameFromAttrib=SysSignalName + Example --signalNameFromAttrib SysSignalName + ARXML known Attributes: SysSignalName, ISignalName, CompuMethodName * dbc: @@ -407,10 +419,10 @@ ____________________ Excel format for startbit of motorola coded signals. Valid values: msb, lsb, msbreverse default msbreverse. [more about starbits...](https://github.com/ebroecker/canmatrix/wiki/signal-Byteorder) -* csv: +* csv/xls/xlsx: - --csvAdditionalSignalAttributes - append additional signal-collums to csv, example: + --additionalSignalAttributes//--additionalFrameAttributes + append additional signal/frame-colums to csv, example: is_signed,attributes["GenSigStartValue"] diff --git a/src/canmatrix/cli/convert.py b/src/canmatrix/cli/convert.py index a4f2b17ee..0ba23494f 100644 --- a/src/canmatrix/cli/convert.py +++ b/src/canmatrix/cli/convert.py @@ -59,6 +59,8 @@ def get_formats(): @click.option('--renameEcu', 'renameEcu', help="rename Ecu form databases. (comma separated list)\nSyntax: --renameEcu=myOldEcu:myNewEcu,mySecondEcu:mySecondNewEcu") @click.option('--deleteSignal', 'deleteSignal', help="delete Signal form databases. (comma separated list)\nSyntax: --deleteSignal=mySignal1,mySecondSignal") @click.option('--renameSignal', 'renameSignal', help="rename Signal form databases. (comma separated list)\nSyntax: --renameSignal=myOldSignal:myNewSignal,mySecondSignal:mySecondNewSignal") +@click.option('--signalNameFromAttrib', 'signalNameFromAttrib', help="change signal_name to given signal attribute\n\ +Example --signalNameFromAttrib SysSignalName\nARXML known Attributes: SysSignalName, ISignalName, CompuMethodName", default=None) @click.option('--deleteZeroSignals/--no-deleteZeroSignals', 'deleteZeroSignals', default=False, help="delete zero length signals (signals with 0 bit length) from matrix\ndefault False") @click.option('--deleteSignalAttributes', 'deleteSignalAttributes', help="delete attributes from all signals\nExample --deleteSignalAttributes GenMsgSomeVar,CycleTime") @click.option('--deleteFrame', 'deleteFrame', help="delete Frame form databases. (comma separated list)\nSyntax: --deleteFrame=myFrame1,mySecondFrame") @@ -75,8 +77,7 @@ def get_formats(): @click.option('--frames', help="Copy only given Frames (comma separated list) to target matrix") @click.option('--signals', help="Copy only given Signals (comma separated list) to target matrix just as 'free' signals without containing frame") @click.option('--merge', help="merge additional can databases.\nSyntax: --merge filename[:ecu=SOMEECU][:frame=FRAME1][:frame=FRAME2],filename2") -@click.option('--signalNameFromAttrib', 'signalNameFromAttrib', help="change signal_name to given signal attribute\n\ -Example --signal_name_from_attrib SysSignalName\nARXML known Attributes: SysSignalName, ISignalName, CompuMethodName", default=None) + # arxml switches