instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / UPnP / upnpclient / xmldescriptionparser.py
1 # Written by Ingar Arntzen
2 # see LICENSE.txt for license information
3
4 """
5 This module implements parsing of UPnP Device Descriptions
6 and UPnP Service Descriptions.
7 """
8 import xml.dom.minidom as minidom
9 import urlparse
10 import uuid
11
12 ##############################################
13 #  PRIVATE UTILITY FUNCTIONS
14 ##############################################
15
16 def _get_subelement_data(element, subelement_tagname):
17     """Parse the data given an element and the tagName of an
18     assumed subelement of that element."""
19     subelement = _get_subelement(element, subelement_tagname)
20     if not subelement : 
21         return None
22     else : 
23         return _get_element_data(subelement)
24
25 def _get_subelement(element, subelement_tagname):
26     """Get an element, given a parent element and a subelement tagname."""
27     subelements = element.getElementsByTagName(subelement_tagname)
28     if not subelements: 
29         return None
30     else : 
31         return subelements[0]
32
33 def _get_element_data(element):
34     """Parse the data of the given element."""
35     text = ""
36     for node in element.childNodes:
37         if node.nodeType == node.TEXT_NODE:
38             text += node.data
39         if node.nodeType == node.CDATA_SECTION_NODE:
40             text += node.data
41     return str(text)
42
43 def _get_absolute_url(base_url_string, url_string):
44     """Construct absolute URL from an absolute base URL and a relative URL."""
45     if base_url_string == None: 
46         # Try to use only url_string
47         if _is_absolute_url(url_string):
48             return url_string
49         else: return None
50     else:
51         return urlparse.urljoin(base_url_string, url_string)
52
53 def _is_absolute_url(url_string):
54     """Determine whether given URL is absolute or not."""
55     url = urlparse.urlparse(url_string)
56     ret = True
57     if url.scheme != "http": 
58         ret = False
59     if url.port == None: 
60         ret = False
61     if len(url.netloc) == 0 : 
62         ret = False
63     return ret
64
65
66
67 ##############################################
68 #  PUBLIC PARSERS
69 ##############################################
70
71 def parse_device_description(xml_description, base_url):
72     """Parse device description. Return dictionary."""
73     ddp = _DeviceDescriptionParser()
74     return ddp.parse(xml_description, base_url)
75
76 def parse_service_description(xml_description):
77     """Parse service description. Return dictionary."""
78     sdp = _ServiceDescriptionParser()
79     return sdp.parse(xml_description)
80
81 ##############################################
82 #  DEVICE DESCRIPTION PARSER
83 ##############################################
84
85 class _DeviceDescriptionParser:
86     """
87     This class implements parsing of the xml description of a 
88     upnp device (rootdevice).
89     
90     Does not parse sub-devices.
91     """
92     def __init__(self):
93         pass
94
95     def parse(self, xmldata, base_url):
96         """
97         This method parses the xml description of a upnp device (rootdevice).
98         -> Input is device description xml-data.
99         <- Output is a dictionary with all relevant information.
100         """
101         try:
102             doc = minidom.parseString(xmldata)
103         except (TypeError, AttributeError):
104             return None
105         if doc == None: 
106             return None
107
108         root_elem = doc.documentElement
109         device = {}
110
111         # URLBase
112         device['URLBase'] = _get_subelement_data(root_elem, 'URLBase')        
113         if device['URLBase'] == None:
114             device['URLBase'] = str(base_url)
115
116         # Device Element
117         device_elem = _get_subelement(root_elem, 'device')
118         if not device_elem: 
119             return None
120
121         # deviceType
122         data = _get_subelement_data(device_elem, "deviceType")
123         if not data: 
124             return None
125         tokens = data.split(':')
126         if len(tokens) == 5:
127             device['deviceType'] = data
128             device['deviceDomain'] = tokens[1]
129             device['deviceTypeShort'] = tokens[3]
130             device['deviceVersion'] = tokens[4]
131         else : return None
132
133         # UDN & UUID
134         data = _get_subelement_data(device_elem, 'UDN') 
135         if not data: 
136             return None
137         tokens = data.split(':')  # uuid:40a69722-4160-11df-9a88-00248116b859
138         if len(tokens) == 2:
139             device['UDN'] = data
140             device['uuid'] = uuid.UUID(tokens[1])
141         else: return None
142
143         # Optional fields
144         device['name'] = _get_subelement_data(device_elem, 
145                                               'friendlyName')
146         device['manufacturer'] = _get_subelement_data(device_elem, 
147                                                       'manufacturer')
148         device['manufacturerURL'] = _get_subelement_data(device_elem, 
149                                                          'manufacturerURL')
150         device['modelName'] = _get_subelement_data(device_elem, 
151                                                    'modelName')
152         device['modelDescription'] = _get_subelement_data(device_elem, 
153                                                           'modelDescription')
154         device['modelURL'] = _get_subelement_data(device_elem, 
155                                                   'modelURL')
156         device['serialNumber'] = _get_subelement_data(device_elem, 
157                                                       'serialNumber')
158         device['UPC'] = _get_subelement_data(device_elem, 'UPC')
159         url_str = _get_subelement_data(device_elem, 'presentationURL')
160         if url_str:
161             device['presentationURL'] =  _get_absolute_url(device['URLBase'], 
162                                                            url_str) 
163         
164         # Services
165         device['services'] = []
166         service_list_elem = _get_subelement(device_elem, 'serviceList')
167         if service_list_elem:
168             service_elems = service_list_elem.getElementsByTagName('service')
169             for service_elem in service_elems:                
170                 data_str = {}
171                 data_str['serviceType'] = _get_subelement_data(service_elem, 
172                                                                'serviceType')
173                 data_str['serviceId'] =  _get_subelement_data(service_elem, 
174                                                               'serviceId')
175                 url_str =  _get_subelement_data(service_elem, 'SCPDURL')
176                 data_str['SCPDURL'] =  _get_absolute_url(device['URLBase'], 
177                                                          url_str)
178                 url_str =  _get_subelement_data(service_elem, 'controlURL')
179                 data_str['controlURL'] = _get_absolute_url(device['URLBase'], 
180                                                            url_str)
181                 url_str =  _get_subelement_data(service_elem, 'eventSubURL')
182                 data_str['eventSubURL'] = _get_absolute_url(device['URLBase'], 
183                                                             url_str)
184                 device['services'].append(data_str)
185
186         return device
187
188
189 ##############################################
190 #  SERVICE  DESCRIPTION PARSER
191 ##############################################
192
193 class _ServiceDescriptionParser:
194     """
195     This class implements parsing of the xml description of a 
196     upnp service.
197     """
198     def __init__(self):
199         pass
200
201     def parse(self, xmldata):
202         """
203         This method parses the xml description of a upnp service.
204         -> Input is device description xml-data.
205         <- Output is a dictionary with all relevant information.
206         """
207         try:
208             doc = minidom.parseString(xmldata)
209         except (TypeError, AttributeError):
210             return None
211         if doc == None: 
212             return None
213
214         root_elem = doc.documentElement
215         service = {}
216
217         # State Variables
218         service['stateVariables'] = []
219         sv_table_elem =  _get_subelement(root_elem, 'serviceStateTable')
220         sv_elems = sv_table_elem.getElementsByTagName('stateVariable')
221         for sv_elem in sv_elems:
222             stv = {}
223             stv['name'] = _get_subelement_data(sv_elem, 'name')
224             stv['dataType'] = _get_subelement_data(sv_elem, 'dataType')
225             stv['defaultValue'] = _get_subelement_data(sv_elem, 'defaultValue')
226             stv['sendEvents'] = sv_elem.attributes['sendEvents'].value
227             service['stateVariables'].append(stv)
228
229         # Actions
230         service['actions'] = []
231         action_table_elem = _get_subelement(root_elem, 'actionList')
232         if action_table_elem:
233             action_elems = action_table_elem.getElementsByTagName('action')
234             for action_elem in action_elems:
235                 action = {}
236                 action['name'] = _get_subelement_data(action_elem, 'name')
237                 action['inargs'] = []
238                 action['outargs'] = []
239                 # Arguments
240                 arg_list_elem = _get_subelement(action_elem, 'argumentList')
241                 if arg_list_elem:
242                     arg_elems = arg_list_elem.getElementsByTagName('argument')
243                     for arg_elem in arg_elems:
244                         arg = {}
245                         arg['name'] = _get_subelement_data(arg_elem, 'name')
246                         # Check that action_spec arguments refer to 
247                         # defined state variables
248                         rsv_name = _get_subelement_data(arg_elem, 
249                                                         'relatedStateVariable')
250                         for stv in service['stateVariables']:
251                             if rsv_name == stv['name']: 
252                                 arg['rsv'] = rsv_name 
253                                 break
254                         arg['direction'] = _get_subelement_data(arg_elem, 
255                                                                 'direction')
256                         if arg['direction'] == 'in':
257                             action['inargs'].append(arg)
258                         elif arg['direction'] == 'out':
259                             action['outargs'].append(arg)
260                 service['actions'].append(action)
261
262         return service
263
264
265 ##############################################
266 # MAIN
267 ##############################################
268
269 if __name__ == '__main__':
270
271     DEVICE_XML = """<?xml version="1.0"?>
272 <root xmlns="urn:schemas-upnp-org:device-1-0">
273 <specVersion>
274 <major>1</major>
275 <minor>0</minor>
276 </specVersion>
277 <URLBase>http://193.156.106.130:44444/</URLBase>
278 <device>
279 <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>
280 <friendlyName>Basic</friendlyName>
281 <manufacturer>Manufacturer</manufacturer>
282 <manufacturerURL>http://manufacturer.com</manufacturerURL>
283 <modelName>Model 1</modelName>
284 <modelDescription>Model description</modelDescription>
285 <modelURL>http://manufacturer.com/model_1</modelURL>
286 <serialNumber>123456</serialNumber>
287 <UDN>uuid:40a69722-4160-11df-9a88-00248116b859</UDN>
288 <UPC>012345678912</UPC>
289 <presentationURL>presentation.html</presentationURL>
290 <serviceList>
291 <service>
292 <serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>
293 <serviceId>urn:upnp-org:serviceId:MySwitchPower</serviceId>
294 <SCPDURL>services/MySwitchPower/description.xml</SCPDURL>
295 <controlURL>services/MySwitchPower/control</controlURL>
296 <eventSubURL>services/MySwitchPower/events</eventSubURL>
297 </service>
298 </serviceList>
299 </device>
300 </root>
301 """
302     SERVICE_XML = """<?xml version="1.0"?>
303 <scpd xmlns="urn:schemas-upnp-org:service-1-0">
304 <specVersion>
305 <major>1</major>
306 <minor>0</minor>
307 </specVersion>
308 <actionList>
309 <action>
310 <name>SetTarget</name>
311 <argumentList>
312 <argument>
313 <name>NewTargetValue</name>
314 <relatedStateVariable>Status</relatedStateVariable>
315 <direction>in</direction>
316 </argument>
317 </argumentList>
318 </action>
319 <action>
320 <name>GetStatus</name>
321 <argumentList>
322 <argument>
323 <name>ResultStatus</name>
324 <relatedStateVariable>Status</relatedStateVariable>
325 <direction>out</direction>
326 </argument>
327 </argumentList>
328 </action>
329 <action>
330 <name>GetTarget</name>
331 <argumentList>
332 <argument>
333 <name>RetTargetValue</name>
334 <relatedStateVariable>Status</relatedStateVariable>
335 <direction>out</direction>
336 </argument>
337 </argumentList>
338 </action>
339 </actionList>
340 <serviceStateTable>
341 <stateVariable sendEvents="yes">
342 <name>Status</name>
343 <dataType>boolean</dataType>
344 <defaultValue>0</defaultValue>
345 </stateVariable>
346 </serviceStateTable>
347 </scpd>
348 """
349
350     print parse_device_description(DEVICE_XML, "http://vg.no/")
351
352     print parse_service_description(SERVICE_XML)
353