1 # Written by Ingar Arntzen
2 # see LICENSE.txt for license information
5 This module implements parsing of UPnP Device Descriptions
6 and UPnP Service Descriptions.
8 import xml.dom.minidom as minidom
12 ##############################################
13 # PRIVATE UTILITY FUNCTIONS
14 ##############################################
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)
23 return _get_element_data(subelement)
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)
33 def _get_element_data(element):
34 """Parse the data of the given element."""
36 for node in element.childNodes:
37 if node.nodeType == node.TEXT_NODE:
39 if node.nodeType == node.CDATA_SECTION_NODE:
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):
51 return urlparse.urljoin(base_url_string, url_string)
53 def _is_absolute_url(url_string):
54 """Determine whether given URL is absolute or not."""
55 url = urlparse.urlparse(url_string)
57 if url.scheme != "http":
61 if len(url.netloc) == 0 :
67 ##############################################
69 ##############################################
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)
76 def parse_service_description(xml_description):
77 """Parse service description. Return dictionary."""
78 sdp = _ServiceDescriptionParser()
79 return sdp.parse(xml_description)
81 ##############################################
82 # DEVICE DESCRIPTION PARSER
83 ##############################################
85 class _DeviceDescriptionParser:
87 This class implements parsing of the xml description of a
88 upnp device (rootdevice).
90 Does not parse sub-devices.
95 def parse(self, xmldata, base_url):
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.
102 doc = minidom.parseString(xmldata)
103 except (TypeError, AttributeError):
108 root_elem = doc.documentElement
112 device['URLBase'] = _get_subelement_data(root_elem, 'URLBase')
113 if device['URLBase'] == None:
114 device['URLBase'] = str(base_url)
117 device_elem = _get_subelement(root_elem, 'device')
122 data = _get_subelement_data(device_elem, "deviceType")
125 tokens = data.split(':')
127 device['deviceType'] = data
128 device['deviceDomain'] = tokens[1]
129 device['deviceTypeShort'] = tokens[3]
130 device['deviceVersion'] = tokens[4]
134 data = _get_subelement_data(device_elem, 'UDN')
137 tokens = data.split(':') # uuid:40a69722-4160-11df-9a88-00248116b859
140 device['uuid'] = uuid.UUID(tokens[1])
144 device['name'] = _get_subelement_data(device_elem,
146 device['manufacturer'] = _get_subelement_data(device_elem,
148 device['manufacturerURL'] = _get_subelement_data(device_elem,
150 device['modelName'] = _get_subelement_data(device_elem,
152 device['modelDescription'] = _get_subelement_data(device_elem,
154 device['modelURL'] = _get_subelement_data(device_elem,
156 device['serialNumber'] = _get_subelement_data(device_elem,
158 device['UPC'] = _get_subelement_data(device_elem, 'UPC')
159 url_str = _get_subelement_data(device_elem, 'presentationURL')
161 device['presentationURL'] = _get_absolute_url(device['URLBase'],
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:
171 data_str['serviceType'] = _get_subelement_data(service_elem,
173 data_str['serviceId'] = _get_subelement_data(service_elem,
175 url_str = _get_subelement_data(service_elem, 'SCPDURL')
176 data_str['SCPDURL'] = _get_absolute_url(device['URLBase'],
178 url_str = _get_subelement_data(service_elem, 'controlURL')
179 data_str['controlURL'] = _get_absolute_url(device['URLBase'],
181 url_str = _get_subelement_data(service_elem, 'eventSubURL')
182 data_str['eventSubURL'] = _get_absolute_url(device['URLBase'],
184 device['services'].append(data_str)
189 ##############################################
190 # SERVICE DESCRIPTION PARSER
191 ##############################################
193 class _ServiceDescriptionParser:
195 This class implements parsing of the xml description of a
201 def parse(self, xmldata):
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.
208 doc = minidom.parseString(xmldata)
209 except (TypeError, AttributeError):
214 root_elem = doc.documentElement
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:
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)
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:
236 action['name'] = _get_subelement_data(action_elem, 'name')
237 action['inargs'] = []
238 action['outargs'] = []
240 arg_list_elem = _get_subelement(action_elem, 'argumentList')
242 arg_elems = arg_list_elem.getElementsByTagName('argument')
243 for arg_elem in arg_elems:
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
254 arg['direction'] = _get_subelement_data(arg_elem,
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)
265 ##############################################
267 ##############################################
269 if __name__ == '__main__':
271 DEVICE_XML = """<?xml version="1.0"?>
272 <root xmlns="urn:schemas-upnp-org:device-1-0">
277 <URLBase>http://193.156.106.130:44444/</URLBase>
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>
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>
302 SERVICE_XML = """<?xml version="1.0"?>
303 <scpd xmlns="urn:schemas-upnp-org:service-1-0">
310 <name>SetTarget</name>
313 <name>NewTargetValue</name>
314 <relatedStateVariable>Status</relatedStateVariable>
315 <direction>in</direction>
320 <name>GetStatus</name>
323 <name>ResultStatus</name>
324 <relatedStateVariable>Status</relatedStateVariable>
325 <direction>out</direction>
330 <name>GetTarget</name>
333 <name>RetTargetValue</name>
334 <relatedStateVariable>Status</relatedStateVariable>
335 <direction>out</direction>
341 <stateVariable sendEvents="yes">
343 <dataType>boolean</dataType>
344 <defaultValue>0</defaultValue>
350 print parse_device_description(DEVICE_XML, "http://vg.no/")
352 print parse_service_description(SERVICE_XML)