3 # Copyright (C) 2008 OpenedHand Ltd
4 # Copyright (C) 2008 Intel Corporation
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.
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
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
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?
26 import os.path, re, xml.etree.ElementTree as ET
27 from optparse import OptionParser
30 # upnp type: (C type, GType, type for g_value_get_*
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')
64 self.c_prefixed_name = None
75 self.related_var = None
82 self.c_prefixed_name = None
85 self.get_function = None
86 self.set_function = None
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)
98 def getActions(action_elements, prefix, variables):
100 Parse the action element list into a list of Action objects.
105 for actionElement in action_elements:
108 a.name = actionElement.find("{urn:schemas-upnp-org:service-1-0}name").text
111 raise Exception("No name found for action")
112 a.c_name = camelCaseToLowerCase (a.name)
113 a.c_prefixed_name = prefix + a.c_name
115 for argElement in actionElement.findall("{urn:schemas-upnp-org:service-1-0}argumentList/{urn:schemas-upnp-org:service-1-0}argument"):
118 arg.name = argElement.find("{urn:schemas-upnp-org:service-1-0}name").text
120 raise Exception("No name found for argument")
121 arg.c_name = camelCaseToLowerCase (arg.name)
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
128 if arg.related_var is None:
129 raise Exception("%s: related state variable %s not found" % (arg.name, var_name))
131 arg.direction = argElement.find("{urn:schemas-upnp-org:service-1-0}direction").text
133 if arg.direction == "in":
134 a.in_args.append(arg)
136 a.out_args.append(arg)
141 def getVariables(var_elements, prefix):
143 Parse the state variable element list into a list of Variable objects.
148 for varElement in var_elements:
150 variables.append(var)
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
157 var.c_name = camelCaseToLowerCase (var.name)
158 var.c_prefixed_name = prefix + var.c_name
160 if (varElement.get("sendEvents") == "no"):
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" % dataType)
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
173 def printClientSyncActionBinding(a):
174 indent = (2 + len (a.c_prefixed_name)) * " "
176 print("static inline gboolean")
177 print("%s (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
179 for arg in a.in_args:
180 print("%sconst %sin_%s," % (indent, arg.related_var.ctype, arg.c_name))
182 for arg in a.out_args:
183 print("%s%s*out_%s," % (indent, arg.related_var.ctype, arg.c_name))
185 print("%sGError **error)" % indent)
189 print(" return gupnp_service_proxy_send_action")
190 print(" (proxy, \"%s\", error," % a.name)
192 for arg in a.in_args:
193 print(" \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
196 for arg in a.out_args:
197 print(" \"%s\", %s, out_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
203 def printClientAsyncActionBinding(a):
204 # User-callback prototype
205 indent = (24 + len (a.c_prefixed_name)) * " "
206 print("typedef void (*%s_reply) (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
207 for arg in a.out_args:
208 print("%sconst %sout_%s," % (indent, arg.related_var.ctype, arg.c_name))
209 print("%sGError *error," % indent)
210 print("%sgpointer userdata);" % indent)
213 # Generated async callback handler, calls user-provided callback
214 indent = (30 + len (a.c_prefixed_name)) * " "
215 print("static void _%s_async_callback (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
216 print("%sGUPnPServiceProxyAction *action," % indent)
217 print("%sgpointer user_data)" % indent)
219 print(" GUPnPAsyncData *cbdata;")
220 print(" GError *error = NULL;")
221 for arg in a.out_args:
222 print(" %s%s;" % (arg.related_var.ctype, arg.c_name))
224 print(" cbdata = (GUPnPAsyncData *) user_data;")
225 print(" gupnp_service_proxy_end_action")
226 print(" (proxy, action, &error,")
227 for arg in a.out_args:
228 print(" \"%s\", %s, &%s," % (arg.name, arg.related_var.gtype, arg.c_name))
230 print(" ((%s_reply)cbdata->cb)" % a.c_prefixed_name)
232 for arg in a.out_args:
233 print(" %s," % arg.c_name)
234 print(" error, cbdata->userdata);")
236 print(" g_slice_free1 (sizeof (*cbdata), cbdata);")
241 indent = (8 + len (a.c_prefixed_name)) * " "
242 print("static inline GUPnPServiceProxyAction *")
243 print("%s_async (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
244 for arg in a.in_args:
245 print("%sconst %sin_%s," % (indent, arg.related_var.ctype, arg.c_name))
246 print("%s%s_reply callback," % (indent, a.c_prefixed_name))
247 print("%sgpointer userdata)" % indent)
249 print(" GUPnPServiceProxyAction* action;")
250 print(" GUPnPAsyncData *cbdata;")
252 print(" cbdata = (GUPnPAsyncData *) g_slice_alloc (sizeof (*cbdata));")
253 print(" cbdata->cb = G_CALLBACK (callback);")
254 print(" cbdata->userdata = userdata;")
255 print(" action = gupnp_service_proxy_begin_action")
256 print(" (proxy, \"%s\"," % a.name)
257 print(" _%s_async_callback, cbdata," % a.c_prefixed_name)
258 for arg in a.in_args:
259 print(" \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
262 print(" return action;")
266 def printClientVariableNotifyBinding(v):
267 ctype = re.sub ("^gchar", "const gchar", v.ctype);
270 indent = (22 + len (v.c_prefixed_name)) * " "
271 print("typedef void")
272 print("(*%s_changed_callback) (GUPnPServiceProxy *proxy," % v.c_prefixed_name)
273 print("%s%s%s," % (indent, ctype, v.c_name))
274 print("%sgpointer userdata);" % indent)
278 indent = (20 + len (v.c_prefixed_name)) * " "
280 print("_%s_changed_callback (GUPnPServiceProxy *proxy," % v.c_prefixed_name)
281 print("%sconst gchar *variable," % indent)
282 print("%sGValue *value," % indent)
283 print("%sgpointer userdata)" % indent)
285 print(" GUPnPAsyncData *cbdata;")
286 print(" %s%s;" % (ctype, v.c_name))
288 print(" cbdata = (GUPnPAsyncData *) userdata;")
289 print(" %s = %s (value);" % (v.c_name, v.get_function))
290 print(" ((%s_changed_callback)cbdata->cb)" % v.c_prefixed_name)
292 print(" %s," % v.c_name)
293 print(" cbdata->userdata);")
295 print(" g_slice_free1 (sizeof (*cbdata), cbdata);")
299 # public notify_add function
300 indent = (13 + len (v.c_prefixed_name)) * " "
301 print("static inline gboolean")
302 print("%s_add_notify (GUPnPServiceProxy *proxy," % v.c_prefixed_name)
303 print("%s%s_changed_callback callback," % (indent, v.c_prefixed_name))
304 print("%sgpointer userdata)" % indent)
306 print(" GUPnPAsyncData *cbdata;")
308 print(" cbdata = (GUPnPAsyncData *) g_slice_alloc (sizeof (*cbdata));")
309 print(" cbdata->cb = G_CALLBACK (callback);")
310 print(" cbdata->userdata = userdata;")
312 print(" return gupnp_service_proxy_add_notify")
314 print(" \"%s\"," % v.name)
315 print(" %s," % v.gtype)
316 print(" _%s_changed_callback," % v.c_prefixed_name)
321 def printServerVariableQueryBinding(v):
322 # User callback proto
323 indent = (28 + len (v.ctype)+ len (v.c_prefixed_name)) * " "
324 print("typedef %s(*%s_query_callback) (GUPnPService *service," % (v.ctype, v.c_prefixed_name))
325 print("%sgpointer userdata);" % indent)
328 indent = (12 + len (v.c_prefixed_name)) * " "
330 print("_%s_query_cb (GUPnPService *service," % v.c_prefixed_name)
331 print("%sgchar *variable," % indent)
332 print("%sGValue *value," % indent)
333 print("%sgpointer userdata)" % indent)
335 print(" GUPnPAsyncData *cbdata;")
336 print(" %s%s;" % (v.ctype, v.c_name))
339 indent = (36 + len (v.c_name) + len (v.c_prefixed_name)) * " "
340 print(" cbdata = (GUPnPAsyncData *) userdata;")
341 print(" %s = ((%s_query_callback)cbdata->cb) (service," % (v.c_name, v.c_prefixed_name))
342 print("%scbdata->userdata);" % indent)
343 print(" g_value_init (value, %s);" % v.gtype)
344 print(" %s (value, %s);" % (v.set_function, v.c_name))
348 indent = (16 + len (v.c_prefixed_name)) * " "
350 print("%s_query_connect (GUPnPService *service," % v.c_prefixed_name)
351 print("%s%s_query_callback callback," % (indent, v.c_prefixed_name))
352 print("%sgpointer userdata)" % indent)
354 print(" GUPnPAsyncData *cbdata;")
356 print(" cbdata = (GUPnPAsyncData *) g_slice_alloc (sizeof (*cbdata));")
357 print(" cbdata->cb = G_CALLBACK (callback);")
358 print(" cbdata->userdata = userdata;")
360 print(" return g_signal_connect_data (service,")
361 print(" \"query-variable::%s\"," % v.name)
362 print(" G_CALLBACK (_%s_query_cb)," % v.c_prefixed_name)
364 print(" _free_cb_data,")
365 print(" (GConnectFlags) 0);")
370 def printServerActionBinding(a):
371 # getter and setter func for GUPnPServiceAction
372 indent = (13 + len (a.c_prefixed_name)) * " "
373 if len (a.in_args) > 0:
374 print("static inline void")
375 print("%s_action_get (GUPnPServiceAction *action," % (a.c_prefixed_name))
376 for arg in a.in_args[:-1]:
377 print("%s%s*in_%s," % (indent, arg.related_var.ctype, arg.c_name))
378 print("%s%s*in_%s)" % (indent, a.in_args[-1].related_var.ctype, a.in_args[-1].c_name))
380 print(" gupnp_service_action_get (action,")
381 for arg in a.in_args:
382 print(" \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
386 if len (a.out_args) > 0:
387 print("static inline void")
388 print("%s_action_set (GUPnPServiceAction *action," % (a.c_prefixed_name))
389 for arg in a.out_args[:-1]:
390 print("%sconst %sout_%s," % (indent, arg.related_var.ctype, arg.c_name))
391 print("%sconst %sout_%s)" % (indent, a.out_args[-1].related_var.ctype, a.out_args[-1].c_name))
393 print(" gupnp_service_action_set (action,")
394 for arg in a.out_args:
395 print(" \"%s\", %s, out_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
400 indent = (17 + len (a.c_prefixed_name)) * " "
401 print("static inline gulong")
402 print("%s_action_connect (GUPnPService *service," % a.c_prefixed_name)
403 print("%sGCallback callback," % indent)
404 print("%sgpointer userdata)" % indent)
406 print(" return g_signal_connect (service,")
407 print(" \"action-invoked::%s\"," % a.name)
413 def PrintServerVariableNotifyBinding(v):
414 indent = (18 + len (v.c_prefixed_name)) * " "
416 print("%s_variable_notify (GUPnPService *service," % v.c_prefixed_name)
417 print("%sconst %s%s)" % (indent ,v.ctype, v.c_name))
419 print(" gupnp_service_notify (service,")
420 print(" \"%s\", %s, %s," % (v.name, v.gtype, v.c_name))
425 def parseSCPD(scpd, prefix):
427 Parse the scpd file into lists of Action and Variable objects.
430 prefix = prefix.lower() + "_"
432 action_elements = scpd.findall("{urn:schemas-upnp-org:service-1-0}actionList/{urn:schemas-upnp-org:service-1-0}action")
433 var_elements = scpd.findall("{urn:schemas-upnp-org:service-1-0}serviceStateTable/{urn:schemas-upnp-org:service-1-0}stateVariable")
435 variables = getVariables(var_elements, prefix)
436 actions = getActions(action_elements, prefix, variables)
438 return (actions, variables)
441 def printClientBindings(binding_name, actions, variables):
442 define = "GUPNP_GENERATED_CLIENT_BINDING_%s" % binding_name
444 print("/* Generated by gupnp-binding-tool */")
446 print("#include <libgupnp/gupnp.h>")
448 print("#ifndef %s" % define)
449 print("#define %s" % define)
451 print("G_BEGIN_DECLS")
453 print("\n#ifndef __GUPNPASYNCDATA_TYPE__")
454 print("#define __GUPNPASYNCDATA_TYPE__")
455 print("typedef struct {GCallback cb; gpointer userdata; } GUPnPAsyncData;")
459 print("\n/* action %s */\n" % a.name)
460 printClientSyncActionBinding(a)
461 printClientAsyncActionBinding(a)
463 if (not v.dummy) and v.notified:
464 print("\n/* state variable %s */\n" % v.name)
465 printClientVariableNotifyBinding(v)
470 print("#endif /* %s */" % define)
473 def printServerBindings(binding_name, actions, variables):
474 define = "GUPNP_GENERATED_CLIENT_BINDING_%s" % binding_name
476 print("/* Generated by gupnp-binding-tool */")
478 print("#include <libgupnp/gupnp.h>")
480 print("#ifndef %s" % define)
481 print("#define %s" % define)
483 print("G_BEGIN_DECLS")
485 print("\n#ifndef __GUPNPASYNCDATA_TYPE__")
486 print("#define __GUPNPASYNCDATA_TYPE__")
487 print("typedef struct {GCallback cb; gpointer userdata; } GUPnPAsyncData;")
491 print("_free_cb_data (gpointer data, GClosure *closure)")
493 print(" GUPnPAsyncData *cbdata = (GUPnPAsyncData *) data;")
494 print(" g_slice_free1 (sizeof (*cbdata), cbdata);")
499 print("\n/* action %s */\n" % a.name)
500 printServerActionBinding(a)
503 print("\n/* state variable %s */\n" % v.name)
504 printServerVariableQueryBinding(v)
506 PrintServerVariableNotifyBinding(v)
511 print("#endif /* %s */" % define)
515 parser = OptionParser("Usage: %prog [options] service-filename")
516 parser.add_option("-p", "--prefix", dest="prefix",
517 metavar="PREFIX", default="",
518 help="set prefix for generated function names")
519 parser.add_option("-m", "--mode", type="choice", dest="mode",
520 metavar="MODE", default="client",
521 choices=("client", "server"),
522 help="select generation mode, 'client' or 'server'")
524 (options, args) = parser.parse_args()
526 parser.error("Expected 1 argument, got %d" % len(args))
528 # get a name for header ifdef
529 if options.prefix == "":
530 base = re.sub("[^a-zA-Z0-9]", "_", os.path.basename(args[0]))
531 binding_name = base.upper()
533 binding_name = options.prefix.upper()
535 # parse scpd file, extract action list and state variable list
536 scpd = ET.parse(args[0])
537 (actions, variables) = parseSCPD (scpd, options.prefix)
538 if (len(actions) == 0 and len(variables) == 0):
539 raise Exception ("No actions or variables found in document")
542 if (options.mode == "client"):
543 printClientBindings(binding_name, actions, variables)
545 printServerBindings(binding_name, actions, variables)
548 if __name__ == "__main__":