svghmi/hmi_tree.py
branchsvghmi
changeset 3197 0f41c1e2c121
parent 3193 8006bb60a4dd
child 3223 061796d9855e
equal deleted inserted replaced
3196:f1049e4df866 3197:0f41c1e2c121
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 # This file is part of Beremiz
       
     5 # Copyright (C) 2021: Edouard TISSERANT
       
     6 #
       
     7 # See COPYING file for copyrights details.
       
     8 
       
     9 from __future__ import absolute_import
       
    10 from itertools import izip, imap
       
    11 from pprint import pformat
       
    12 import hashlib
       
    13 
       
    14 from lxml import etree
       
    15 
       
    16 HMI_TYPES_DESC = {
       
    17     "HMI_NODE":{},
       
    18     "HMI_STRING":{},
       
    19     "HMI_INT":{},
       
    20     "HMI_BOOL":{},
       
    21     "HMI_REAL":{}
       
    22 }
       
    23 
       
    24 HMI_TYPES = HMI_TYPES_DESC.keys()
       
    25 
       
    26 class HMITreeNode(object):
       
    27     def __init__(self, path, name, nodetype, iectype = None, vartype = None, cpath = None, hmiclass = None):
       
    28         self.path = path
       
    29         self.name = name
       
    30         self.nodetype = nodetype
       
    31         self.hmiclass = hmiclass
       
    32 
       
    33         if iectype is not None:
       
    34             self.iectype = iectype
       
    35             self.vartype = vartype
       
    36             self.cpath = cpath
       
    37 
       
    38         if nodetype in ["HMI_NODE"]:
       
    39             self.children = []
       
    40 
       
    41     def pprint(self, indent = 0):
       
    42         res = ">"*indent + pformat(self.__dict__, indent = indent, depth = 1) + "\n"
       
    43         if hasattr(self, "children"):
       
    44             res += "\n".join([child.pprint(indent = indent + 1)
       
    45                               for child in self.children])
       
    46             res += "\n"
       
    47 
       
    48         return res
       
    49 
       
    50     def place_node(self, node):
       
    51         best_child = None
       
    52         known_best_match = 0
       
    53         potential_siblings = {}
       
    54         for child in self.children:
       
    55             if child.path is not None:
       
    56                 in_common = 0
       
    57                 for child_path_item, node_path_item in izip(child.path, node.path):
       
    58                     if child_path_item == node_path_item:
       
    59                         in_common +=1
       
    60                     else:
       
    61                         break
       
    62                 # Match can only be HMI_NODE, and the whole path of node
       
    63                 # must match candidate node (except for name part)
       
    64                 # since candidate would become child of that node
       
    65                 if in_common > known_best_match and \
       
    66                    child.nodetype == "HMI_NODE" and \
       
    67                    in_common == len(child.path) - 1:
       
    68                     known_best_match = in_common
       
    69                     best_child = child
       
    70                 else:
       
    71                     potential_siblings[child.path[
       
    72                         -2 if child.nodetype == "HMI_NODE" else -1]] = child
       
    73         if best_child is not None:
       
    74             if node.nodetype == "HMI_NODE" and best_child.path[:-1] == node.path[:-1]:
       
    75                 return "Duplicate_HMI_NODE", best_child
       
    76             return best_child.place_node(node)
       
    77         else:
       
    78             candidate_name = node.path[-2 if node.nodetype == "HMI_NODE" else -1]
       
    79             if candidate_name in potential_siblings:
       
    80                 return "Non_Unique", potential_siblings[candidate_name]
       
    81 
       
    82             if node.nodetype == "HMI_NODE" and len(self.children) > 0:
       
    83                 prev = self.children[-1]
       
    84                 if prev.path[:-1] == node.path[:-1]:
       
    85                     return "Late_HMI_NODE",prev
       
    86 
       
    87             self.children.append(node)
       
    88             return None
       
    89 
       
    90     def etree(self, add_hash=False):
       
    91 
       
    92         attribs = dict(name=self.name)
       
    93         if self.path is not None:
       
    94             attribs["path"] = ".".join(self.path)
       
    95 
       
    96         if self.hmiclass is not None:
       
    97             attribs["class"] = self.hmiclass
       
    98 
       
    99         if add_hash:
       
   100             attribs["hash"] = ",".join(map(str,self.hash()))
       
   101 
       
   102         res = etree.Element(self.nodetype, **attribs)
       
   103 
       
   104         if hasattr(self, "children"):
       
   105             for child_etree in imap(lambda c:c.etree(), self.children):
       
   106                 res.append(child_etree)
       
   107 
       
   108         return res
       
   109 
       
   110     @classmethod
       
   111     def from_etree(cls, enode):
       
   112         """
       
   113         alternative constructor, restoring HMI Tree from XML backup
       
   114         note: all C-related information is gone, 
       
   115               this restore is only for tree display and widget picking
       
   116         """
       
   117         nodetype = enode.tag
       
   118         attributes = enode.attrib
       
   119         name = attributes["name"]
       
   120         path = attributes["path"].split('.') if "path" in attributes else None 
       
   121         hmiclass = attributes.get("class", None)
       
   122         # hash is computed on demand
       
   123         node = cls(path, name, nodetype, hmiclass=hmiclass)
       
   124         for child in enode.iterchildren():
       
   125             node.children.append(cls.from_etree(child))
       
   126         return node
       
   127 
       
   128     def traverse(self):
       
   129         yield self
       
   130         if hasattr(self, "children"):
       
   131             for c in self.children:
       
   132                 for yoodl in c.traverse():
       
   133                     yield yoodl
       
   134 
       
   135 
       
   136     def hash(self):
       
   137         """ Produce a hash, any change in HMI tree structure change that hash """
       
   138         s = hashlib.new('md5')
       
   139         self._hash(s)
       
   140         # limit size to HMI_HASH_SIZE as in svghmi.c
       
   141         return map(ord,s.digest())[:8]
       
   142 
       
   143     def _hash(self, s):
       
   144         s.update(str((self.name,self.nodetype)))
       
   145         if hasattr(self, "children"):
       
   146             for c in self.children:
       
   147                 c._hash(s)
       
   148 
       
   149 SPECIAL_NODES = [("HMI_ROOT", "HMI_NODE"),
       
   150                  ("heartbeat", "HMI_INT")]
       
   151                  # ("current_page", "HMI_STRING")])
       
   152