Edouard@2745: #!/usr/bin/env python Edouard@2745: # -*- coding: utf-8 -*- Edouard@2745: Edouard@2745: # This file is part of Beremiz edouard@3197: # Copyright (C) 2021: Edouard TISSERANT Edouard@2745: # Edouard@2745: # See COPYING file for copyrights details. Edouard@2745: Edouard@2745: from __future__ import absolute_import Edouard@2763: from itertools import izip, imap Edouard@2822: from pprint import pformat edouard@3223: import weakref Edouard@2789: import hashlib Edouard@2816: Edouard@2816: from lxml import etree Edouard@2756: Edouard@2749: HMI_TYPES_DESC = { Edouard@2814: "HMI_NODE":{}, Edouard@2749: "HMI_STRING":{}, Edouard@2749: "HMI_INT":{}, edouard@3068: "HMI_BOOL":{}, edouard@3068: "HMI_REAL":{} Edouard@2749: } Edouard@2749: Edouard@2749: HMI_TYPES = HMI_TYPES_DESC.keys() Edouard@2745: Edouard@2757: class HMITreeNode(object): Edouard@2822: def __init__(self, path, name, nodetype, iectype = None, vartype = None, cpath = None, hmiclass = None): Edouard@2757: self.path = path Edouard@2757: self.name = name Edouard@2757: self.nodetype = nodetype Edouard@2814: self.hmiclass = hmiclass edouard@3223: self.parent = None Edouard@2764: Edouard@2764: if iectype is not None: Edouard@2764: self.iectype = iectype Edouard@2764: self.vartype = vartype Edouard@2822: self.cpath = cpath Edouard@2822: edouard@2890: if nodetype in ["HMI_NODE"]: Edouard@2757: self.children = [] Edouard@2757: Edouard@2757: def pprint(self, indent = 0): Edouard@2757: res = ">"*indent + pformat(self.__dict__, indent = indent, depth = 1) + "\n" Edouard@2817: if hasattr(self, "children"): Edouard@2757: res += "\n".join([child.pprint(indent = indent + 1) Edouard@2757: for child in self.children]) Edouard@2757: res += "\n" Edouard@2817: Edouard@2757: return res Edouard@2757: Edouard@2757: def place_node(self, node): Edouard@2757: best_child = None Edouard@2757: known_best_match = 0 Edouard@2965: potential_siblings = {} Edouard@2817: for child in self.children: Edouard@2762: if child.path is not None: Edouard@2762: in_common = 0 Edouard@2762: for child_path_item, node_path_item in izip(child.path, node.path): Edouard@2762: if child_path_item == node_path_item: Edouard@2762: in_common +=1 Edouard@2762: else: Edouard@2762: break Edouard@2945: # Match can only be HMI_NODE, and the whole path of node Edouard@2945: # must match candidate node (except for name part) Edouard@2945: # since candidate would become child of that node Edouard@2945: if in_common > known_best_match and \ Edouard@2945: child.nodetype == "HMI_NODE" and \ Edouard@2945: in_common == len(child.path) - 1: Edouard@2762: known_best_match = in_common Edouard@2762: best_child = child Edouard@2965: else: Edouard@2965: potential_siblings[child.path[ Edouard@2965: -2 if child.nodetype == "HMI_NODE" else -1]] = child Edouard@2945: if best_child is not None: Edouard@2965: if node.nodetype == "HMI_NODE" and best_child.path[:-1] == node.path[:-1]: Edouard@2965: return "Duplicate_HMI_NODE", best_child Edouard@2965: return best_child.place_node(node) Edouard@2757: else: Edouard@2965: candidate_name = node.path[-2 if node.nodetype == "HMI_NODE" else -1] Edouard@2965: if candidate_name in potential_siblings: Edouard@2965: return "Non_Unique", potential_siblings[candidate_name] Edouard@2965: Edouard@2965: if node.nodetype == "HMI_NODE" and len(self.children) > 0: Edouard@2965: prev = self.children[-1] Edouard@2965: if prev.path[:-1] == node.path[:-1]: Edouard@2965: return "Late_HMI_NODE",prev Edouard@2965: edouard@3223: node.parent = weakref.ref(self) Edouard@2757: self.children.append(node) Edouard@2965: return None Edouard@2817: Edouard@2788: def etree(self, add_hash=False): Edouard@2763: Edouard@2763: attribs = dict(name=self.name) Edouard@2763: if self.path is not None: Edouard@2788: attribs["path"] = ".".join(self.path) Edouard@2788: Edouard@2815: if self.hmiclass is not None: Edouard@2815: attribs["class"] = self.hmiclass Edouard@2815: Edouard@2788: if add_hash: Edouard@2788: attribs["hash"] = ",".join(map(str,self.hash())) Edouard@2763: Edouard@2763: res = etree.Element(self.nodetype, **attribs) Edouard@2763: Edouard@2817: if hasattr(self, "children"): Edouard@2763: for child_etree in imap(lambda c:c.etree(), self.children): Edouard@2763: res.append(child_etree) Edouard@2763: Edouard@2763: return res Edouard@2763: edouard@3176: @classmethod edouard@3176: def from_etree(cls, enode): edouard@3176: """ edouard@3176: alternative constructor, restoring HMI Tree from XML backup edouard@3176: note: all C-related information is gone, edouard@3176: this restore is only for tree display and widget picking edouard@3176: """ edouard@3176: nodetype = enode.tag edouard@3176: attributes = enode.attrib edouard@3176: name = attributes["name"] edouard@3176: path = attributes["path"].split('.') if "path" in attributes else None edouard@3177: hmiclass = attributes.get("class", None) edouard@3176: # hash is computed on demand edouard@3176: node = cls(path, name, nodetype, hmiclass=hmiclass) edouard@3176: for child in enode.iterchildren(): Edouard@3224: newnode = cls.from_etree(child) Edouard@3224: newnode.parent = weakref.ref(node) Edouard@3224: node.children.append(newnode) edouard@3176: return node edouard@3176: Edouard@2764: def traverse(self): Edouard@2764: yield self Edouard@2817: if hasattr(self, "children"): Edouard@2764: for c in self.children: Edouard@2764: for yoodl in c.traverse(): Edouard@2764: yield yoodl Edouard@2764: edouard@3223: def hmi_path(self): edouard@3223: if self.parent is None: edouard@3223: return "/" edouard@3223: p = self.parent() edouard@3223: if p.parent is None: edouard@3223: return "/" + self.name edouard@3223: return p.hmi_path() + "/" + self.name Edouard@2788: Edouard@2788: def hash(self): Edouard@2788: """ Produce a hash, any change in HMI tree structure change that hash """ Edouard@2788: s = hashlib.new('md5') Edouard@2788: self._hash(s) Edouard@2788: # limit size to HMI_HASH_SIZE as in svghmi.c Edouard@2817: return map(ord,s.digest())[:8] Edouard@2788: Edouard@2788: def _hash(self, s): Edouard@2788: s.update(str((self.name,self.nodetype))) Edouard@2817: if hasattr(self, "children"): Edouard@2788: for c in self.children: Edouard@2788: c._hash(s) Edouard@2788: edouard@2890: SPECIAL_NODES = [("HMI_ROOT", "HMI_NODE"), edouard@2890: ("heartbeat", "HMI_INT")] Edouard@2822: