merge wxPython4
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Tue, 13 Sep 2022 16:51:54 +0200
branchwxPython4
changeset 3608 02229133df43
parent 3591 50600b946ea7 (diff)
parent 3607 cfbdccad71a5 (current diff)
child 3609 51a3d6f39944
merge
--- a/editors/CodeFileEditor.py	Tue Sep 13 16:32:39 2022 +0200
+++ b/editors/CodeFileEditor.py	Tue Sep 13 16:51:54 2022 +0200
@@ -824,12 +824,12 @@
             type_menu = wx.Menu(title='')
             base_menu = wx.Menu(title='')
             for base_type in self.Controler.GetBaseTypes():
-                new_entry = base_menu.Append(help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL, text=base_type)
+                new_entry = base_menu.Append(wx.ID_ANY, base_type)
                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), new_entry)
             type_menu.AppendMenu(wx.ID_ANY, "Base Types", base_menu)
             datatype_menu = wx.Menu(title='')
             for datatype in self.Controler.GetDataTypes():
-                new_entry = datatype_menu.Append(help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL, text=datatype)
+                new_entry = datatype_menu.Append(wx.ID_ANY, item=datatype)
                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), new_entry)
             type_menu.AppendMenu(wx.ID_ANY, "User Data Types", datatype_menu)
             rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
--- a/editors/ConfTreeNodeEditor.py	Tue Sep 13 16:32:39 2022 +0200
+++ b/editors/ConfTreeNodeEditor.py	Tue Sep 13 16:51:54 2022 +0200
@@ -435,13 +435,16 @@
                             name = element_infos["name"]
                             value = element_infos["value"]
 
-                            staticbox = wx.StaticBox(self.ParamsEditor,
-                                                     label="%s - %s" % (_(name), _(value)),
-                                                     size=wx.Size(10, 0))
-                            staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
-                            sizer.Add(staticboxsizer, border=5, flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
-                            self.GenerateSizerElements(staticboxsizer, element_infos["children"], element_path)
-                            callback = self.GetChoiceContentCallBackFunction(combobox, staticboxsizer, element_path)
+                            staticboxsizer = None
+                            if element_infos["children"]:
+                                staticbox = wx.StaticBox(self.ParamsEditor,
+                                                         label="%s - %s" % (_(name), _(value)),
+                                                         size=wx.Size(10, 0))
+                                staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
+                                sizer.Add(staticboxsizer, border=5, flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
+                                self.GenerateSizerElements(staticboxsizer, element_infos["children"], element_path)
+
+                            callback = self.GetChoiceContentCallBackFunction(combobox, element_path)
                         else:
                             for choice in element_infos["type"]:
                                 combobox.Append(choice)
@@ -527,7 +530,7 @@
                         textctrl.Bind(wx.EVT_TEXT, callback)
                         textctrl.Bind(wx.EVT_KILL_FOCUS, callback)
 
-                if not isinstance(element_infos["type"], list) and element_infos["use"] == "optional":
+                if not isinstance(element_infos["type"], list) and element_infos.get("use", None) == "optional":
                     bt = wx.BitmapButton(self.ParamsEditor, 
                         bitmap=wx.ArtProvider.GetBitmap(wx.ART_UNDO, wx.ART_TOOLBAR, (16,16)),
                         style=wx.BORDER_NONE)
@@ -579,7 +582,7 @@
             event.Skip()
         return OnChoiceChanged
 
-    def GetChoiceContentCallBackFunction(self, choicectrl, staticboxsizer, path):
+    def GetChoiceContentCallBackFunction(self, choicectrl, path):
         def OnChoiceContentChanged(event):
             self.SetConfNodeParamsAttribute(path, choicectrl.GetStringSelection())
             wx.CallAfter(self.RefreshConfNodeParamsSizer)
--- a/opc_ua/client.py	Tue Sep 13 16:32:39 2022 +0200
+++ b/opc_ua/client.py	Tue Sep 13 16:51:54 2022 +0200
@@ -6,7 +6,7 @@
 
 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
 from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT
-from .opcua_client_maker import OPCUAClientPanel, OPCUAClientModel, UA_IEC_types
+from .opcua_client_maker import OPCUAClientPanel, OPCUAClientModel, UA_IEC_types, authParams
 
 import util.paths as paths
 
@@ -29,17 +29,56 @@
     def Log(self, msg):
         self.Controler.GetCTRoot().logger.write(msg)
 
-    def UriGetter(self):
-        return self.Controler.GetServerURI() 
-
     def CreateOPCUAClient_UI(self, parent):
-        return OPCUAClientPanel(parent, self.Controler.GetModelData(), self.Log, self.UriGetter)
+        return OPCUAClientPanel(parent, self.Controler.GetModelData(), self.Log, self.Controler.GetConfig)
 
 class OPCUAClient(object):
     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       <xsd:element name="OPCUAClient">
         <xsd:complexType>
+          <xsd:sequence>
+            <xsd:element name="AuthType" minOccurs="0">
+              <xsd:complexType>
+                <xsd:choice minOccurs="0">
+                  <xsd:element name="x509">
+                    <xsd:complexType>
+                      <xsd:sequence>
+                        <xsd:element name="Policy">
+                          <xsd:annotation>
+                            <xsd:documentation>Default to Basic256Sha256 if not specified</xsd:documentation>
+                          </xsd:annotation>
+                          <xsd:complexType>
+                            <xsd:choice minOccurs="0">
+                              <xsd:element name="Basic256Sha256"/>
+                              <xsd:element name="Basic128Rsa15"/>
+                              <xsd:element name="Basic256"/>
+                            </xsd:choice>
+                          </xsd:complexType>
+                        </xsd:element>
+                        <xsd:element name="Mode">
+                          <xsd:complexType>
+                            <xsd:choice minOccurs="0">
+                              <xsd:element name="SignAndEncrypt"/>
+                              <xsd:element name="Sign"/>
+                            </xsd:choice>
+                          </xsd:complexType>
+                        </xsd:element>
+                      </xsd:sequence>
+                      <xsd:attribute name="Certificate" type="xsd:string" use="optional" default="certificate.pem"/>
+                      <xsd:attribute name="PrivateKey" type="xsd:string" use="optional" default="private_key.pem"/>
+                    </xsd:complexType>
+                  </xsd:element>
+                  <xsd:element name="UserPassword">
+                    <xsd:complexType>
+                      <xsd:attribute name="User" type="xsd:string" use="optional"/>
+                      <xsd:attribute name="Password" type="xsd:string" use="optional"/>
+                    </xsd:complexType>
+                  </xsd:element>
+                </xsd:choice>
+              </xsd:complexType>
+            </xsd:element>
+          </xsd:sequence>
           <xsd:attribute name="Server_URI" type="xsd:string" use="optional" default="opc.tcp://localhost:4840"/>
         </xsd:complexType>
       </xsd:element>
@@ -61,8 +100,23 @@
     def GetModelData(self):
         return self.modeldata
     
-    def GetServerURI(self):
-        return self.GetParamsAttributes("OPCUAClient.Server_URI")["value"]
+    def GetConfig(self):
+        cfg = lambda path: self.GetParamsAttributes("OPCUAClient."+path)["value"]
+        AuthType = cfg("AuthType")
+        res = dict(URI=cfg("Server_URI"), AuthType=AuthType)
+
+        paramList = authParams.get(AuthType, None)
+        if paramList:
+            for name,default in paramList:
+                value = cfg("AuthType."+name)
+                if value == "" or value is None:
+                    value = default
+                # cryptomaterial is expected to be in project's user provide file directory
+                if name in ["Certificate","PrivateKey"]:
+                    value = os.path.join(self.GetCTRoot()._getProjectFilesPath(), value)
+                res[name] = value
+
+        return res
 
     def GetFileName(self):
         return os.path.join(self.CTNPath(), 'selected.csv')
@@ -76,8 +130,7 @@
         locstr = "_".join(map(str, current_location))
         c_path = os.path.join(buildpath, "opcua_client__%s.c" % locstr)
 
-        c_code = self.modeldata.GenerateC(c_path, locstr, 
-            self.GetParamsAttributes("OPCUAClient.Server_URI")["value"])
+        c_code = self.modeldata.GenerateC(c_path, locstr, self.GetConfig())
 
         with open(c_path, 'wb') as c_file:
             c_file.write(c_code)
@@ -86,6 +139,8 @@
 
         CFLAGS = ' '.join(['-I"' + path + '"' for path in Open62541IncludePaths])
 
+        # Note: all user provided files are systematicaly copied, including cryptomaterial
+
         return [(c_path, CFLAGS)], LDFLAGS, True
 
     def GetVariableLocationTree(self):
--- a/opc_ua/opcua_client_maker.py	Tue Sep 13 16:32:39 2022 +0200
+++ b/opc_ua/opcua_client_maker.py	Tue Sep 13 16:51:54 2022 +0200
@@ -38,6 +38,16 @@
 
 directions = ["input", "output"]
 
+authParams = {
+    "x509":[
+        ("Certificate", "certificate.der"),
+        ("PrivateKey", "private_key.pem"),
+        ("Policy", "Basic256Sha256"),
+        ("Mode", "SignAndEncrypt")],
+    "UserPassword":[
+        ("User", None),
+        ("Password", None)]}
+
 class OPCUASubListModel(dv.DataViewIndexListModel):
     def __init__(self, data, log):
         dv.DataViewIndexListModel.__init__(self, len(data))
@@ -230,7 +240,7 @@
 
 
 class OPCUAClientPanel(wx.SplitterWindow):
-    def __init__(self, parent, modeldata, log, uri_getter):
+    def __init__(self, parent, modeldata, log, config_getter):
         self.log = log
         wx.SplitterWindow.__init__(self, parent, -1)
 
@@ -242,7 +252,7 @@
         self.inout_sizer.AddGrowableRow(1)
 
         self.client = None
-        self.uri_getter = uri_getter
+        self.config_getter = config_getter
 
         self.connect_button = wx.ToggleButton(self.inout_panel, -1, "Browse Server")
 
@@ -298,7 +308,17 @@
 
             self.tree.SetMainColumn(0)
 
-            self.client = Client(self.uri_getter())
+            config = self.config_getter()
+            self.client = Client(config["URI"])
+            
+            AuthType = config["AuthType"]
+            if AuthType=="UserPasword":
+                self.client.set_user(config["User"])
+                self.client.set_password(config["Password"])
+            elif AuthType=="x509":
+                self.client.set_security_string(
+                    "{Policy},{Mode},{Certificate},{PrivateKey}".format(**config))
+
             self.client.connect()
             self.client.load_type_definitions()  # load definition of server specific structures/extension objects
             rootnode = self.client.get_root_node()
@@ -478,85 +498,151 @@
                 for row in data:
                     writer.writerow([direction] + row)
 
-    def GenerateC(self, path, locstr, server_uri):
+    def GenerateC(self, path, locstr, config):
         template = """/* code generated by beremiz OPC-UA extension */
 
 #include <open62541/client_config_default.h>
 #include <open62541/client_highlevel.h>
 #include <open62541/plugin/log_stdout.h>
+#include <open62541/plugin/securitypolicy.h>
+
+#include <open62541/types.h>
+#include <open62541/types_generated_handling.h>
+
+static UA_INLINE UA_ByteString
+loadFile(const char *const path) {{
+    UA_ByteString fileContents = UA_STRING_NULL;
+
+    FILE *fp = fopen(path, "rb");
+    if(!fp) {{
+        errno = 0;
+        return fileContents;
+    }}
+
+    fseek(fp, 0, SEEK_END);
+    fileContents.length = (size_t)ftell(fp);
+    fileContents.data = (UA_Byte *)UA_malloc(fileContents.length * sizeof(UA_Byte));
+    if(fileContents.data) {{
+        fseek(fp, 0, SEEK_SET);
+        size_t read = fread(fileContents.data, sizeof(UA_Byte), fileContents.length, fp);
+        if(read != fileContents.length)
+            UA_ByteString_clear(&fileContents);
+    }} else {{
+        fileContents.length = 0;
+    }}
+    fclose(fp);
+
+    return fileContents;
+}}
 
 static UA_Client *client;
+static UA_ClientConfig *cc;
 
 #define DECL_VAR(ua_type, C_type, c_loc_name)                                                       \\
 static UA_Variant c_loc_name##_variant;                                                             \\
 static C_type c_loc_name##_buf = 0;                                                                 \\
 C_type *c_loc_name = &c_loc_name##_buf;
 
-%(decl)s
-
-void __cleanup_%(locstr)s(void)
-{
+{decl}
+
+void __cleanup_{locstr}(void)
+{{
     UA_Client_disconnect(client);
     UA_Client_delete(client);
-}
-
+}}
+
+
+#define INIT_NoAuth()                                                                              \\
+    UA_ClientConfig_setDefault(cc);                                                                \\
+    retval = UA_Client_connect(client, uri);
+
+/* Note : Policy is ignored here since open62541 client supports all policies by default */
+#define INIT_x509(Policy, UpperCaseMode, PrivateKey, Certificate)                                  \\
+    /* TODO try paths given in runtime CLI */                                                      \\
+    UA_ByteString certificate = loadFile(Certificate);                                             \\
+    UA_ByteString privateKey  = loadFile(PrivateKey);                                              \\
+                                                                                                   \\
+    printf("INIT_x509 %s,%s,%s,%s\\n", #Policy, #UpperCaseMode, PrivateKey, Certificate);          \\
+    cc->securityMode = UA_MESSAGESECURITYMODE_##UpperCaseMode;                                     \\
+    UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey, NULL, 0, NULL, 0);           \\
+                                                                                                   \\
+    retval = UA_Client_connect(client, uri);                                                       \\
+                                                                                                   \\
+    UA_ByteString_clear(&certificate);                                                             \\
+    UA_ByteString_clear(&privateKey);
+
+#define INIT_UserPassword(User, Password)                                                          \\
+    printf("TODO INIT_UserPassword %s,%s\\n", User, Password);                                     \\
+    retval = UA_Client_connectUsername(client, uri, User, Password);
 
 #define INIT_READ_VARIANT(ua_type, c_loc_name)                                                     \\
     UA_Variant_init(&c_loc_name##_variant);
 
-#define INIT_WRITE_VARIANT(ua_type, ua_type_enum, c_loc_name)       \\
+#define INIT_WRITE_VARIANT(ua_type, ua_type_enum, c_loc_name)                                      \\
     UA_Variant_setScalar(&c_loc_name##_variant, (ua_type*)c_loc_name, &UA_TYPES[ua_type_enum]);
 
-int __init_%(locstr)s(int argc,char **argv)
-{
+int __init_{locstr}(int argc,char **argv)
+{{
     UA_StatusCode retval;
     client = UA_Client_new();
-    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
-%(init)s
-
-    /* Connect to server */
-    retval = UA_Client_connect(client, "%(uri)s");
-    if(retval != UA_STATUSCODE_GOOD) {
+    cc = UA_Client_getConfig(client);
+    char *uri = "{uri}";
+{init}
+
+    if(retval != UA_STATUSCODE_GOOD) {{
         UA_Client_delete(client);
         return EXIT_FAILURE;
-    }
-}
+    }}
+}}
 
 #define READ_VALUE(ua_type, ua_type_enum, c_loc_name, ua_nodeid_type, ua_nsidx, ua_node_id)        \\
     retval = UA_Client_readValueAttribute(                                                         \\
         client, ua_nodeid_type(ua_nsidx, ua_node_id), &c_loc_name##_variant);                      \\
     if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(&c_loc_name##_variant) &&               \\
-       c_loc_name##_variant.type == &UA_TYPES[ua_type_enum]) {                                     \\
+       c_loc_name##_variant.type == &UA_TYPES[ua_type_enum]) {{                                    \\
             c_loc_name##_buf = *(ua_type*)c_loc_name##_variant.data;                               \\
             UA_Variant_clear(&c_loc_name##_variant);  /* Unalloc requiered on each read ! */       \\
-    }
-
-void __retrieve_%(locstr)s(void)
-{
+    }}
+
+void __retrieve_{locstr}(void)
+{{
     UA_StatusCode retval;
-%(retrieve)s
-}
-
-#define WRITE_VALUE(ua_type, c_loc_name, ua_nodeid_type, ua_nsidx, ua_node_id)       \\
+{retrieve}
+}}
+
+#define WRITE_VALUE(ua_type, c_loc_name, ua_nodeid_type, ua_nsidx, ua_node_id)                     \\
     UA_Client_writeValueAttribute(                                                                 \\
         client, ua_nodeid_type(ua_nsidx, ua_node_id), &c_loc_name##_variant);
 
-void __publish_%(locstr)s(void)
-{
-%(publish)s
-}
+void __publish_{locstr}(void)
+{{
+{publish}
+}}
 
 """
         
         formatdict = dict(
             locstr   = locstr,
-            uri      = server_uri,
+            uri      = config["URI"],
             decl     = "",
             cleanup  = "",
             init     = "",
             retrieve = "",
             publish  = "" 
         )
+
+        AuthType = config["AuthType"]
+        if AuthType == "x509":
+            config["UpperCaseMode"] = config["Mode"].upper()
+            formatdict["init"] += """
+    INIT_x509({Policy}, {UpperCaseMode}, "{PrivateKey}", "{Certificate}")""".format(**config)
+        elif AuthType == "UserPassword":
+            formatdict["init"] += """
+    INIT_UserPassword("{User}", "{Password}")""".format(**config)
+        else:
+            formatdict["init"] += """
+    INIT_NoAuth()"""
+
         for direction, data in self.iteritems():
             iec_direction_prefix = {"input": "__I", "output": "__Q"}[direction]
             for row in data:
@@ -570,18 +656,18 @@
 DECL_VAR({ua_type}, {C_type}, {c_loc_name})""".format(**locals())
 
                 if direction == "input":
-                    formatdict["init"] +="""
+                    formatdict["init"] += """
     INIT_READ_VARIANT({ua_type}, {c_loc_name})""".format(**locals())
                     formatdict["retrieve"] += """
     READ_VALUE({ua_type}, {ua_type_enum}, {c_loc_name}, {ua_nodeid_type}, {ua_nsidx}, {ua_node_id})""".format(**locals())
 
                 if direction == "output":
-                    formatdict["init"] +="""
+                    formatdict["init"] += """
     INIT_WRITE_VARIANT({ua_type}, {ua_type_enum}, {c_loc_name})""".format(**locals())
                     formatdict["publish"] += """
     WRITE_VALUE({ua_type}, {c_loc_name}, {ua_nodeid_type}, {ua_nsidx}, {ua_node_id})""".format(**locals())
 
-        Ccode = template%formatdict
+        Ccode = template.format(**formatdict)
         
         return Ccode
 
@@ -594,7 +680,20 @@
 
     frame = wx.Frame(None, -1, "OPCUA Client Test App", size=(800,600))
 
-    uri = sys.argv[1] if len(sys.argv)>1 else "opc.tcp://localhost:4840"
+    argc = len(sys.argv)
+
+    config={}
+    config["URI"] = sys.argv[1] if argc>1 else "opc.tcp://localhost:4840"
+
+    if argc > 2:
+        AuthType = sys.argv[2]
+        config["AuthType"] = AuthType
+        for (name, default), value in izip_longest(authParams[AuthType], sys.argv[3:]):
+            if value is None:
+                if default is None:
+                    raise Exception(name+" param expected")
+                value = default
+            config[name] = value
 
     test_panel = wx.Panel(frame)
     test_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
@@ -603,7 +702,7 @@
 
     modeldata = OPCUAClientModel(print)
 
-    opcuatestpanel = OPCUAClientPanel(test_panel, modeldata, print, lambda:uri)
+    opcuatestpanel = OPCUAClientPanel(test_panel, modeldata, print, lambda:config)
 
     def OnGenerate(evt):
         dlg = wx.FileDialog(
@@ -624,7 +723,7 @@
     -I ../../open62541/arch/ ../../open62541/build/bin/libopen62541.a
 */
 
-"""%(path, path[:-2]) + modeldata.GenerateC(path, "test", uri) + """
+"""%(path, path[:-2]) + modeldata.GenerateC(path, "test", config) + """
 
 int main(int argc, char *argv[]) {
 
--- a/tests/projects/opcua_client/opcua_0@opcua/confnode.xml	Tue Sep 13 16:32:39 2022 +0200
+++ b/tests/projects/opcua_client/opcua_0@opcua/confnode.xml	Tue Sep 13 16:51:54 2022 +0200
@@ -1,2 +1,9 @@
 <?xml version='1.0' encoding='utf-8'?>
-<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
+<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <AuthType>
+    <x509>
+      <Policy/>
+      <Mode/>
+    </x509>
+  </AuthType>
+</OPCUAClient>
--- a/xmlclass/xmlclass.py	Tue Sep 13 16:32:39 2022 +0200
+++ b/xmlclass/xmlclass.py	Tue Sep 13 16:51:54 2022 +0200
@@ -598,11 +598,15 @@
         else:
             return ""
 
+    def Initial():
+        p = etree.Element(infos["name"])
+        return p
+
     return {
         "type": TAG,
         "extract": ExtractTag,
         "generate": GenerateTag,
-        "initial": lambda: None,
+        "initial": Initial,
         "check": lambda x: x is None or infos["minOccurs"] == 0 and x
     }
 
@@ -1450,14 +1454,14 @@
             parts = path.split(".", 1)
             if parts[0] in attributes:
                 if len(parts) != 1:
-                    raise ValueError("Wrong path!")
+                    raise ValueError("Wrong path: "+path)
                 attr_type = gettypeinfos(attributes[parts[0]]["attr_type"]["basename"],
                                          attributes[parts[0]]["attr_type"]["facets"])
                 value = getattr(self, parts[0], "")
             elif parts[0] in elements:
                 if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
                     if len(parts) != 1:
-                        raise ValueError("Wrong path!")
+                        raise ValueError("Wrong path: "+path)
                     attr_type = gettypeinfos(elements[parts[0]]["elmt_type"]["basename"],
                                              elements[parts[0]]["elmt_type"]["facets"])
                     value = getattr(self, parts[0], "")
@@ -1466,7 +1470,7 @@
                 else:
                     attr = getattr(self, parts[0], None)
                     if attr is None:
-                        raise ValueError("Wrong path!")
+                        raise ValueError("Wrong path: "+path)
                     if len(parts) == 1:
                         return attr.getElementInfos(parts[0])
                     else:
@@ -1477,7 +1481,7 @@
             elif "base" in classinfos:
                 classinfos["base"].getElementInfos(name, path)
             else:
-                raise ValueError("Wrong path!")
+                raise ValueError("Wrong path: "+path)
         else:
             if not derived:
                 children.extend(self.getElementAttributes())
@@ -1519,7 +1523,7 @@
             parts = path.split(".", 1)
             if parts[0] in attributes:
                 if len(parts) != 1:
-                    raise ValueError("Wrong path!")
+                    raise ValueError("Wrong path: "+path)
                 if attributes[parts[0]]["attr_type"]["basename"] == "boolean":
                     setattr(self, parts[0], value)
                 elif attributes[parts[0]]["use"] == "optional" and value == None:
@@ -1534,7 +1538,7 @@
             elif parts[0] in elements:
                 if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
                     if len(parts) != 1:
-                        raise ValueError("Wrong path!")
+                        raise ValueError("Wrong path: "+path)
                     if elements[parts[0]]["elmt_type"]["basename"] == "boolean":
                         setattr(self, parts[0], value)
                     elif attributes[parts[0]]["minOccurs"] == 0 and value == "":
@@ -1738,6 +1742,8 @@
     def tostring(self):
         return NAMESPACE_PATTERN.sub("", etree.tostring(self, pretty_print=True, encoding='utf-8')).decode('utf-8')
 
+    def getElementInfos(self, name, path=None, derived=False):
+        return {"name": name, "type": TAG, "value": None, "use": None, "children": []}
 
 class XMLElementClassLookUp(etree.PythonElementClassLookup):