Initialize Tizen 2.3
[external/gupnp.git] / tools / gupnp-binding-tool
1 #! /usr/bin/python
2
3 # Copyright (C) 2008 OpenedHand Ltd
4 # Copyright (C) 2008 Intel Corporation
5 #
6 # This program is free software; you can redistribute it and/or modify it under
7 # the terms of the GNU General Public License as published by the Free Software
8 # Foundation; either version 2, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13 # details.
14 #
15 # You should have received a copy of the GNU General Public License along with
16 # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
17 # St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 # TODO:
20 # - finish code cleanup
21 # - currently allowedValueList is not used: could use it to turn 
22 #   current char* value to an enum
23 # - could warn if values outside allowedValueRange are used in *_action_set() 
24 # - add option to generate skeleton source code that uses the bindings? 
25
26 import sys, os.path, re, xml.etree.ElementTree as ET
27 from optparse import OptionParser
28
29
30 # upnp type:     (C type,      GType,                     type for g_value_get_* 
31 #                                                         and g_value_set_*)
32 typemap = {
33   'ui1':         ('guint ',    'G_TYPE_UINT',             'uint'),
34   'ui2':         ('guint ',    'G_TYPE_UINT',             'uint'),
35   'ui4':         ('guint ',    'G_TYPE_UINT',             'uint'),
36   'i1':          ('gint' ,     'G_TYPE_INT',              'int'),
37   'i2':          ('gint ',     'G_TYPE_INT',              'int'),
38   'i4':          ('gint ',     'G_TYPE_INT',              'int'),
39   'int':         ('gint ',     'G_TYPE_INT',              'int'),
40   'r4':          ('gfloat ',   'G_TYPE_FLOAT',            'float'),
41   'r8':          ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
42   'number':      ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
43   'fixed.14.4':  ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
44   'float':       ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
45   'char':        ('gchar ',    'G_TYPE_CHAR',             'char'),
46   'string':      ('gchar *',   'G_TYPE_STRING',           'string'),
47   'date':        ('gchar *',   'GUPNP_TYPE_DATE',         'string'),
48   'dateTime':    ('gchar *',   'GUPNP_TYPE_DATE_TIME',    'string'),
49   'dateTime.tz': ('gchar *',   'GUPNP_TYPE_DATE_TIME_TZ', 'string'),
50   'time':        ('gchar *',   'GUPNP_TYPE_TIME',         'string'),
51   'time.tz':     ('gchar *',   'GUPNP_TYPE_TIME_TZ',      'string'),
52   'boolean':     ('gboolean ', 'G_TYPE_BOOLEAN',          'boolean'),
53   'bin.base64':  ('gchar *',   'GUPNP_TYPE_BIN_BASE64',   'string'),
54   'bin.hex':     ('gchar *',   'GUPNP_TYPE_BIN_HEX',      'string'),
55   'uri':         ('gchar *',   'GUPNP_TYPE_URI',          'string'),
56   'uuid':        ('gchar *',   'GUPNP_TYPE_UUID',         'string')
57 }
58
59
60 class Action:
61     def __init__(self):
62         self.name = None
63         self.c_name = None
64         self.c_prefixed_name = None
65         self.in_args = []
66         self.out_args = []
67         self.notify_vars = []
68
69
70 class Argument:
71     def __init__(self):
72         self.name = None
73         self.c_name = None
74         self.direction = None
75         self.related_var = None
76
77
78 class Variable:
79     def __init__(self):
80         self.name = None
81         self.c_name = None
82         self.c_prefixed_name = None
83         self.ctype = None
84         self.gtype = None
85         self.get_function = None
86         self.set_function = None
87         self.notified = True
88         self.dummy = False
89
90
91 def camelCaseToLowerCase (str):
92     # http://www.djangosnippets.org/snippets/585/
93     tmp = re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', str)
94     lower_case = tmp.lower().strip('_')
95     return re.sub('[^a-z0-9]', '_', lower_case)
96
97
98 def getActions(action_elements, prefix, variables):
99     """
100     Parse the action element list into a list of Action objects.
101     """
102     
103     actions = []
104     
105     for actionElement in action_elements:
106         a = Action()
107         actions.append(a)
108         a.name = actionElement.find("{urn:schemas-upnp-org:service-1-0}name").text
109         
110         if a.name is None:
111             raise Exception("No name found for action")
112         a.c_name = camelCaseToLowerCase (a.name)
113         a.c_prefixed_name = prefix + a.c_name
114         
115         for argElement in actionElement.findall("{urn:schemas-upnp-org:service-1-0}argumentList/{urn:schemas-upnp-org:service-1-0}argument"):
116             arg = Argument()
117
118             arg.name = argElement.find("{urn:schemas-upnp-org:service-1-0}name").text
119             if arg.name is None:
120                 raise Exception("No name found for argument")
121             arg.c_name = camelCaseToLowerCase (arg.name)
122             
123             var_name = argElement.find("{urn:schemas-upnp-org:service-1-0}relatedStateVariable").text
124             for var in variables:
125                 if var.name == var_name:
126                     arg.related_var = var
127                     break
128             if arg.related_var is None:
129                 raise Exception("%s: related state variable %s not found" % (arg.name, var_name))
130             
131             arg.direction = argElement.find("{urn:schemas-upnp-org:service-1-0}direction").text
132             
133             if arg.direction == "in":
134                     a.in_args.append(arg)
135             else:
136                     a.out_args.append(arg)
137     
138     return actions
139
140
141 def getVariables(var_elements, prefix):
142     """
143     Parse the state variable element list into a list of Variable objects.
144     """
145     
146     variables = []
147
148     for varElement in var_elements:
149         var = Variable()
150         variables.append(var)
151         
152         var.name = varElement.find("{urn:schemas-upnp-org:service-1-0}name").text
153         if var.name.startswith("A_ARG_TYPE_"):
154             # dummy state variable, only there to give type info to getter/setter
155             var.dummy = True
156         
157         var.c_name = camelCaseToLowerCase (var.name)
158         var.c_prefixed_name = prefix + var.c_name
159         
160         if (varElement.get("sendEvents") == "no"):
161             var.notified = False
162         
163         dataType = varElement.find("{urn:schemas-upnp-org:service-1-0}dataType").text
164         if not dataType in typemap:
165             raise Exception("Unknown dataType %s" % data_type)
166         (var.ctype, var.gtype, g_value_type) = typemap[dataType];
167         var.get_function = "g_value_get_" + g_value_type
168         var.set_function = "g_value_set_" + g_value_type
169     
170     return variables
171
172
173 def printClientSyncActionBinding(a):
174     indent = (2 + len (a.c_prefixed_name)) * " "
175
176     print "static inline gboolean"
177     print "%s (GUPnPServiceProxy *proxy," % a.c_prefixed_name
178     
179     for arg in a.in_args:
180         print "%sconst %sin_%s," % (indent, arg.related_var.ctype, arg.c_name)
181         
182     for arg in a.out_args:
183         print "%s%s*out_%s," % (indent, arg.related_var.ctype, arg.c_name)
184         
185     print "%sGError **error)" % indent
186     
187     print "{"
188
189     print "  return gupnp_service_proxy_send_action"
190     print "    (proxy, \"%s\", error," % a.name
191     
192     for arg in a.in_args:
193         print "     \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name)
194     print "     NULL,"
195     
196     for arg in a.out_args:
197         print "     \"%s\", %s, out_%s," % (arg.name, arg.related_var.gtype, arg.c_name)
198     print "     NULL);"
199     
200     print "}\n"
201
202
203 def printClientAsyncActionBinding(a):
204     # Magic struct to pass data around.  Defined here so that we don't have
205     # multiple copies of the struct definition.
206     asyncdata = "  struct {GCallback cb; gpointer userdata; } *cbdata;"
207
208     # User-callback prototype
209     indent = (24 + len (a.c_prefixed_name)) * " "
210     print "typedef void (*%s_reply) (GUPnPServiceProxy *proxy," % a.c_prefixed_name
211     for arg in a.out_args:
212         print "%sconst %sout_%s," % (indent, arg.related_var.ctype, arg.c_name)
213     print "%sGError *error," % indent
214     print "%sgpointer userdata);" % indent
215     print
216
217     # Generated async callback handler, calls user-provided callback
218     indent = (30 + len (a.c_prefixed_name)) * " "
219     print "static void _%s_async_callback (GUPnPServiceProxy *proxy," % a.c_prefixed_name
220     print "%sGUPnPServiceProxyAction *action," % indent
221     print "%sgpointer user_data)" % indent
222     print "{"
223     print asyncdata
224     print "  GError *error = NULL;"
225     for arg in a.out_args:
226         print "  %s%s;" % (arg.related_var.ctype, arg.c_name)
227     print
228     print "  cbdata = user_data;"
229     print "  gupnp_service_proxy_end_action"
230     print "    (proxy, action, &error,"
231     for arg in a.out_args:
232         print "     \"%s\", %s, &%s," % (arg.name, arg.related_var.gtype, arg.c_name)
233     print "     NULL);"
234     print "  ((%s_reply)cbdata->cb)" % a.c_prefixed_name
235     print "    (proxy,"
236     for arg in a.out_args:
237         print "     %s," % arg.c_name
238     print "     error, cbdata->userdata);"
239     print
240     print "  g_slice_free1 (sizeof (*cbdata), cbdata);"
241     print "}"
242     print
243
244     # Entry point
245     indent = (8 + len (a.c_prefixed_name)) * " "
246     print "static inline GUPnPServiceProxyAction *"
247     print "%s_async (GUPnPServiceProxy *proxy," % a.c_prefixed_name
248     for arg in a.in_args:
249         print "%sconst %sin_%s," % (indent, arg.related_var.ctype, arg.c_name)
250     print "%s%s_reply callback," % (indent, a.c_prefixed_name)
251     print "%sgpointer userdata)" % indent
252     print "{"
253     print "  GUPnPServiceProxyAction* action;"
254     print asyncdata
255     print
256     print "  cbdata = g_slice_alloc (sizeof (*cbdata));"
257     print "  cbdata->cb = G_CALLBACK (callback);"
258     print "  cbdata->userdata = userdata;"
259     print "  action = gupnp_service_proxy_begin_action"
260     print "    (proxy, \"%s\"," % a.name
261     print "     _%s_async_callback, cbdata," % a.c_prefixed_name
262     for arg in a.in_args:
263         print "     \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name)
264     print "     NULL);"
265     print
266     print "  return action;"
267     print "}"
268
269
270 def printClientVariableNotifyBinding(v):
271     asyncdata = "  struct {GCallback cb; gpointer userdata; } *cbdata;"
272     ctype = re.sub ("^gchar", "const gchar", v.ctype);
273     
274     # callback prototype
275     indent = (22 + len (v.c_prefixed_name)) * " "
276     print "typedef void"
277     print "(*%s_changed_callback) (GUPnPServiceProxy *proxy," % v.c_prefixed_name
278     print "%s%s%s," % (indent, ctype, v.c_name)
279     print "%sgpointer userdata);" % indent
280     print
281     
282     # private callback
283     indent = (20 + len (v.c_prefixed_name)) * " "
284     print "static void"
285     print "_%s_changed_callback (GUPnPServiceProxy *proxy," % v.c_prefixed_name
286     print "%sconst gchar *variable," % indent
287     print "%sGValue *value," % indent
288     print "%sgpointer userdata)" % indent
289     print "{"
290     print asyncdata
291     print "  %s%s;" % (ctype, v.c_name)
292     print
293     print "  cbdata = userdata;"
294     print "  %s = %s (value);" % (v.c_name, v.get_function)
295     print "  ((%s_changed_callback)cbdata->cb)" % v.c_prefixed_name
296     print "    (proxy,"
297     print "     %s," % v.c_name
298     print "     cbdata->userdata);"
299     print
300     print "  g_slice_free1 (sizeof (*cbdata), cbdata);"
301     print "}"
302     print
303     
304     # public notify_add function
305     indent = (13 + len (v.c_prefixed_name)) * " "
306     print "static inline gboolean"
307     print "%s_add_notify (GUPnPServiceProxy *proxy," % v.c_prefixed_name
308     print "%s%s_changed_callback callback," % (indent, v.c_prefixed_name)
309     print "%sgpointer userdata)" % indent
310     print "{"
311     print asyncdata
312     print
313     print "  cbdata = g_slice_alloc (sizeof (*cbdata));"
314     print "  cbdata->cb = G_CALLBACK (callback);"
315     print "  cbdata->userdata = userdata;"
316     print
317     print "  return gupnp_service_proxy_add_notify"
318     print "    (proxy,"
319     print "     \"%s\"," % v.name
320     print "     %s," % v.gtype
321     print "     _%s_changed_callback," % v.c_prefixed_name
322     print "     cbdata);"
323     print "}"
324
325
326 def printServerVariableQueryBinding(v):
327     asyncdata = "  struct {GCallback cb; gpointer userdata; } *cbdata;"
328     
329     # User callback proto
330     indent = (28 + len (v.ctype)+ len (v.c_prefixed_name)) * " "
331     print "typedef %s(*%s_query_callback) (GUPnPService *service," % (v.ctype, v.c_prefixed_name)
332     print "%sgpointer userdata);" % indent
333     print 
334     
335     indent = (12 + len (v.c_prefixed_name)) * " "
336     print "static void"
337     print "_%s_query_cb (GUPnPService *service," % v.c_prefixed_name
338     print "%sgchar *variable," % indent
339     print "%sGValue *value," % indent
340     print "%sgpointer userdata)" % indent
341     print "{"
342     print asyncdata
343     print "  %s%s;" % (v.ctype, v.c_name)
344     print 
345     
346     indent = (36 + len (v.c_name) + len (v.c_prefixed_name)) * " "
347     print "  cbdata = userdata;"
348     print "  %s = ((%s_query_callback)cbdata->cb) (service," % (v.c_name, v.c_prefixed_name)
349     print "%scbdata->userdata);" % indent
350     print "  g_value_init (value, %s);" % v.gtype
351     print "  %s (value, %s);" % (v.set_function, v.c_name)
352     print "}"
353     print
354     
355     indent = (16 + len (v.c_prefixed_name)) * " "
356     print "gulong"
357     print "%s_query_connect (GUPnPService *service," % v.c_prefixed_name
358     print "%s%s_query_callback callback," % (indent, v.c_prefixed_name)
359     print "%sgpointer userdata)" % indent
360     print "{"
361     print asyncdata
362     print
363     print "  cbdata = g_slice_alloc (sizeof (*cbdata));"
364     print "  cbdata->cb = G_CALLBACK (callback);"
365     print "  cbdata->userdata = userdata;"
366     print
367     print "  return g_signal_connect_data (service,"
368     print "                                \"query-variable::%s\"," % v.name
369     print "                                G_CALLBACK (_%s_query_cb)," % v.c_prefixed_name
370     print "                                cbdata,"
371     print "                                _free_cb_data,"
372     print "                                0);"
373     print "}"
374     print
375
376
377 def printServerActionBinding(a):
378     # getter and setter func for GUPnPServiceAction
379     indent = (13 + len (a.c_prefixed_name)) * " "
380     if len (a.in_args) > 0:
381         print "static inline void"
382         print "%s_action_get (GUPnPServiceAction *action," % (a.c_prefixed_name)
383         for arg in a.in_args[:-1]:
384             print "%s%s*in_%s," % (indent, arg.related_var.ctype, arg.c_name)
385         print "%s%s*in_%s)" % (indent, a.in_args[-1].related_var.ctype, a.in_args[-1].c_name)
386         print "{"
387         print "  gupnp_service_action_get (action,"
388         for arg in a.in_args:
389             print "                            \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name)
390         print "                            NULL);"
391         print "}"
392         print
393     if len (a.out_args) > 0:
394         print "static inline void"
395         print "%s_action_set (GUPnPServiceAction *action," % (a.c_prefixed_name)
396         for arg in a.out_args[:-1]:
397             print "%sconst %sout_%s," % (indent, arg.related_var.ctype, arg.c_name)
398         print "%sconst %sout_%s)" % (indent, a.out_args[-1].related_var.ctype, a.out_args[-1].c_name)
399         print "{"
400         print "  gupnp_service_action_set (action,"
401         for arg in a.out_args:
402             print "                            \"%s\", %s, out_%s," % (arg.name, arg.related_var.gtype, arg.c_name)
403         print "                            NULL);"
404         print "}"
405         print
406     
407     indent = (17 + len (a.c_prefixed_name)) * " "
408     print "static inline gulong"
409     print "%s_action_connect (GUPnPService *service," % a.c_prefixed_name
410     print "%sGCallback callback," % indent 
411     print "%sgpointer userdata)" % indent
412     print "{"
413     print "  return g_signal_connect (service," 
414     print "                           \"action-invoked::%s\"," % a.name
415     print "                           callback,"
416     print "                           userdata);"
417     print "}"
418     print
419
420 def PrintServerVariableNotifyBinding(v):
421     indent = (18 + len (v.c_prefixed_name)) * " "
422     print "void"
423     print "%s_variable_notify (GUPnPService *service," % v.c_prefixed_name
424     print "%sconst %s%s)" % (indent ,v.ctype, v.c_name)
425     print "{"
426     print "  gupnp_service_notify (service,"
427     print "                        \"%s\", %s, %s," % (v.name, v.gtype, v.c_name)
428     print "                        NULL);"
429     print "}"
430     print
431
432 def parseSCPD(scpd, prefix):
433     """
434     Parse the scpd file into lists of Action and Variable objects.
435     """
436     if prefix != "":
437         prefix = prefix.lower() + "_"
438     
439     action_elements = scpd.findall("{urn:schemas-upnp-org:service-1-0}actionList/{urn:schemas-upnp-org:service-1-0}action")
440     var_elements = scpd.findall("{urn:schemas-upnp-org:service-1-0}serviceStateTable/{urn:schemas-upnp-org:service-1-0}stateVariable")
441     
442     variables = getVariables(var_elements, prefix)
443     actions = getActions(action_elements, prefix, variables)
444     
445     return (actions, variables)
446
447
448 def printClientBindings(binding_name, actions, variables):
449     define = "GUPNP_GENERATED_CLIENT_BINDING_%s" % binding_name
450     
451     print "/* Generated by gupnp-binding-tool */"
452     print
453     print "#include <libgupnp/gupnp.h>"
454     print
455     print "#ifndef %s" % define
456     print "#define %s" % define
457     print
458     print "G_BEGIN_DECLS"
459     
460     for a in actions:
461         print "\n/* action %s */\n" % a.name
462         printClientSyncActionBinding(a)
463         printClientAsyncActionBinding(a)
464     for v in variables:
465         if (not v.dummy) and v.notified:
466             print "\n/* state variable %s */\n" % v.name
467             printClientVariableNotifyBinding(v)
468     
469     print
470     print "G_END_DECLS"
471     print
472     print "#endif /* %s */" % define
473
474
475 def printServerBindings(binding_name, actions, variables):
476     define = "GUPNP_GENERATED_CLIENT_BINDING_%s" % binding_name
477     
478     print "/* Generated by gupnp-binding-tool */"
479     print
480     print "#include <libgupnp/gupnp.h>"
481     print
482     print "#ifndef %s" % define
483     print "#define %s" % define
484     print
485     print "G_BEGIN_DECLS"
486     print
487     
488     print "static void"
489     print "_free_cb_data (gpointer data, GClosure *closure)"
490     print "{"
491     print "  struct {GCallback cb; gpointer userdata; } *cbdata = data;"
492     print "  g_slice_free1 (sizeof (*cbdata), cbdata);"
493     print "}"
494     print
495     
496     for a in actions:
497         print "\n/* action %s */\n" % a.name
498         printServerActionBinding(a)
499     for v in variables:
500         if not v.dummy:
501             print "\n/* state variable %s */\n" % v.name
502             printServerVariableQueryBinding(v)
503             if v.notified:
504                 PrintServerVariableNotifyBinding(v)
505     
506     print
507     print "G_END_DECLS"
508     print
509     print "#endif /* %s */" % define
510
511
512 def main ():
513     parser = OptionParser("Usage: %prog [options] service-filename")
514     parser.add_option("-p", "--prefix", dest="prefix", 
515                       metavar="PREFIX", default="",
516                       help="set prefix for generated function names")
517     parser.add_option("-m", "--mode", type="choice", dest="mode", 
518                       metavar="MODE", default="client",
519                       choices=("client", "server"),
520                       help="select generation mode, 'client' or 'server'")
521     
522     (options, args) = parser.parse_args() 
523     if len(args) != 1:
524         parser.error("Expected 1 argument, got %d" % len(args))
525     
526     # get a name for header ifdef
527     if options.prefix == "":
528         base = re.sub("[^a-zA-Z0-9]", "_", os.path.basename(args[0]))
529         binding_name = base.upper()
530     else:
531         binding_name = options.prefix.upper()
532     
533     # parse scpd file, extract action list and state variable list
534     scpd = ET.parse(args[0])
535     (actions, variables) = parseSCPD (scpd, options.prefix)
536     if (len(actions) == 0 and len(variables) == 0):
537         raise Exception ("No actions or variables found in document")
538     
539     # generate bindings
540     if (options.mode == "client"):
541         printClientBindings(binding_name, actions, variables)
542     else:
543         printServerBindings(binding_name, actions, variables)
544
545
546 if __name__ == "__main__":
547     main()