1# Based from "GDBus - GLib D-Bus Library": 2# 3# Copyright (C) 2008-2011 Red Hat, Inc. 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General 16# Public License along with this library; if not, see <http://www.gnu.org/licenses/>. 17# 18# Author: David Zeuthen <davidz@redhat.com> 19 20import xml.parsers.expat 21 22 23class Annotation: 24 def __init__(self, key, value): 25 self.key = key 26 self.value = value 27 self.annotations = [] 28 self.since = "" 29 30 31class Arg: 32 def __init__(self, name, signature): 33 self.name = name 34 self.signature = signature 35 self.annotations = [] 36 self.doc_string = "" 37 self.since = "" 38 39 40class Method: 41 def __init__(self, name, h_type_implies_unix_fd=True): 42 self.name = name 43 self.h_type_implies_unix_fd = h_type_implies_unix_fd 44 self.in_args = [] 45 self.out_args = [] 46 self.annotations = [] 47 self.doc_string = "" 48 self.since = "" 49 self.deprecated = False 50 self.unix_fd = False 51 52 53class Signal: 54 def __init__(self, name): 55 self.name = name 56 self.args = [] 57 self.annotations = [] 58 self.doc_string = "" 59 self.since = "" 60 self.deprecated = False 61 62 63class Property: 64 def __init__(self, name, signature, access): 65 self.name = name 66 self.signature = signature 67 self.access = access 68 self.annotations = [] 69 self.arg = Arg("value", self.signature) 70 self.arg.annotations = self.annotations 71 self.readable = False 72 self.writable = False 73 if self.access == "readwrite": 74 self.readable = True 75 self.writable = True 76 elif self.access == "read": 77 self.readable = True 78 elif self.access == "write": 79 self.writable = True 80 else: 81 raise ValueError('Invalid access type "{}"'.format(self.access)) 82 self.doc_string = "" 83 self.since = "" 84 self.deprecated = False 85 self.emits_changed_signal = True 86 87 88class Interface: 89 def __init__(self, name): 90 self.name = name 91 self.methods = [] 92 self.signals = [] 93 self.properties = [] 94 self.annotations = [] 95 self.doc_string = "" 96 self.doc_string_brief = "" 97 self.since = "" 98 self.deprecated = False 99 100 101class DBusXMLParser: 102 STATE_TOP = "top" 103 STATE_NODE = "node" 104 STATE_INTERFACE = "interface" 105 STATE_METHOD = "method" 106 STATE_SIGNAL = "signal" 107 STATE_PROPERTY = "property" 108 STATE_ARG = "arg" 109 STATE_ANNOTATION = "annotation" 110 STATE_IGNORED = "ignored" 111 112 def __init__(self, xml_data, h_type_implies_unix_fd=True): 113 self._parser = xml.parsers.expat.ParserCreate() 114 self._parser.CommentHandler = self.handle_comment 115 self._parser.CharacterDataHandler = self.handle_char_data 116 self._parser.StartElementHandler = self.handle_start_element 117 self._parser.EndElementHandler = self.handle_end_element 118 119 self.parsed_interfaces = [] 120 self._cur_object = None 121 122 self.state = DBusXMLParser.STATE_TOP 123 self.state_stack = [] 124 self._cur_object = None 125 self._cur_object_stack = [] 126 127 self.doc_comment_last_symbol = "" 128 129 self._h_type_implies_unix_fd = h_type_implies_unix_fd 130 131 self._parser.Parse(xml_data) 132 133 COMMENT_STATE_BEGIN = "begin" 134 COMMENT_STATE_PARAMS = "params" 135 COMMENT_STATE_BODY = "body" 136 COMMENT_STATE_SKIP = "skip" 137 138 def handle_comment(self, data): 139 comment_state = DBusXMLParser.COMMENT_STATE_BEGIN 140 lines = data.split("\n") 141 symbol = "" 142 body = "" 143 in_para = False 144 params = {} 145 for line in lines: 146 orig_line = line 147 line = line.lstrip() 148 if comment_state == DBusXMLParser.COMMENT_STATE_BEGIN: 149 if len(line) > 0: 150 colon_index = line.find(": ") 151 if colon_index == -1: 152 if line.endswith(":"): 153 symbol = line[0 : len(line) - 1] 154 comment_state = DBusXMLParser.COMMENT_STATE_PARAMS 155 else: 156 comment_state = DBusXMLParser.COMMENT_STATE_SKIP 157 else: 158 symbol = line[0:colon_index] 159 rest_of_line = line[colon_index + 2 :].strip() 160 if len(rest_of_line) > 0: 161 body += rest_of_line + "\n" 162 comment_state = DBusXMLParser.COMMENT_STATE_PARAMS 163 elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS: 164 if line.startswith("@"): 165 colon_index = line.find(": ") 166 if colon_index == -1: 167 comment_state = DBusXMLParser.COMMENT_STATE_BODY 168 if not in_para: 169 in_para = True 170 body += orig_line + "\n" 171 else: 172 param = line[1:colon_index] 173 docs = line[colon_index + 2 :] 174 params[param] = docs 175 else: 176 comment_state = DBusXMLParser.COMMENT_STATE_BODY 177 if len(line) > 0: 178 if not in_para: 179 in_para = True 180 body += orig_line + "\n" 181 elif comment_state == DBusXMLParser.COMMENT_STATE_BODY: 182 if len(line) > 0: 183 if not in_para: 184 in_para = True 185 body += orig_line + "\n" 186 else: 187 if in_para: 188 body += "\n" 189 in_para = False 190 if in_para: 191 body += "\n" 192 193 if symbol != "": 194 self.doc_comment_last_symbol = symbol 195 self.doc_comment_params = params 196 self.doc_comment_body = body 197 198 def handle_char_data(self, data): 199 # print 'char_data=%s'%data 200 pass 201 202 def handle_start_element(self, name, attrs): 203 old_state = self.state 204 old_cur_object = self._cur_object 205 if self.state == DBusXMLParser.STATE_IGNORED: 206 self.state = DBusXMLParser.STATE_IGNORED 207 elif self.state == DBusXMLParser.STATE_TOP: 208 if name == DBusXMLParser.STATE_NODE: 209 self.state = DBusXMLParser.STATE_NODE 210 else: 211 self.state = DBusXMLParser.STATE_IGNORED 212 elif self.state == DBusXMLParser.STATE_NODE: 213 if name == DBusXMLParser.STATE_INTERFACE: 214 self.state = DBusXMLParser.STATE_INTERFACE 215 iface = Interface(attrs["name"]) 216 self._cur_object = iface 217 self.parsed_interfaces.append(iface) 218 elif name == DBusXMLParser.STATE_ANNOTATION: 219 self.state = DBusXMLParser.STATE_ANNOTATION 220 anno = Annotation(attrs["name"], attrs["value"]) 221 self._cur_object.annotations.append(anno) 222 self._cur_object = anno 223 else: 224 self.state = DBusXMLParser.STATE_IGNORED 225 226 # assign docs, if any 227 if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]: 228 self._cur_object.doc_string = self.doc_comment_body 229 if "short_description" in self.doc_comment_params: 230 short_description = self.doc_comment_params["short_description"] 231 self._cur_object.doc_string_brief = short_description 232 if "since" in self.doc_comment_params: 233 self._cur_object.since = self.doc_comment_params["since"].strip() 234 235 elif self.state == DBusXMLParser.STATE_INTERFACE: 236 if name == DBusXMLParser.STATE_METHOD: 237 self.state = DBusXMLParser.STATE_METHOD 238 method = Method( 239 attrs["name"], h_type_implies_unix_fd=self._h_type_implies_unix_fd 240 ) 241 self._cur_object.methods.append(method) 242 self._cur_object = method 243 elif name == DBusXMLParser.STATE_SIGNAL: 244 self.state = DBusXMLParser.STATE_SIGNAL 245 signal = Signal(attrs["name"]) 246 self._cur_object.signals.append(signal) 247 self._cur_object = signal 248 elif name == DBusXMLParser.STATE_PROPERTY: 249 self.state = DBusXMLParser.STATE_PROPERTY 250 prop = Property(attrs["name"], attrs["type"], attrs["access"]) 251 self._cur_object.properties.append(prop) 252 self._cur_object = prop 253 elif name == DBusXMLParser.STATE_ANNOTATION: 254 self.state = DBusXMLParser.STATE_ANNOTATION 255 anno = Annotation(attrs["name"], attrs["value"]) 256 self._cur_object.annotations.append(anno) 257 self._cur_object = anno 258 else: 259 self.state = DBusXMLParser.STATE_IGNORED 260 261 # assign docs, if any 262 if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]: 263 self._cur_object.doc_string = self.doc_comment_body 264 if "since" in self.doc_comment_params: 265 self._cur_object.since = self.doc_comment_params["since"].strip() 266 267 elif self.state == DBusXMLParser.STATE_METHOD: 268 if name == DBusXMLParser.STATE_ARG: 269 self.state = DBusXMLParser.STATE_ARG 270 arg_name = None 271 if "name" in attrs: 272 arg_name = attrs["name"] 273 arg = Arg(arg_name, attrs["type"]) 274 direction = attrs.get("direction", "in") 275 if direction == "in": 276 self._cur_object.in_args.append(arg) 277 elif direction == "out": 278 self._cur_object.out_args.append(arg) 279 else: 280 raise ValueError('Invalid direction "{}"'.format(direction)) 281 self._cur_object = arg 282 elif name == DBusXMLParser.STATE_ANNOTATION: 283 self.state = DBusXMLParser.STATE_ANNOTATION 284 anno = Annotation(attrs["name"], attrs["value"]) 285 self._cur_object.annotations.append(anno) 286 self._cur_object = anno 287 else: 288 self.state = DBusXMLParser.STATE_IGNORED 289 290 # assign docs, if any 291 if self.doc_comment_last_symbol == old_cur_object.name: 292 if "name" in attrs and attrs["name"] in self.doc_comment_params: 293 doc_string = self.doc_comment_params[attrs["name"]] 294 if doc_string is not None: 295 self._cur_object.doc_string = doc_string 296 if "since" in self.doc_comment_params: 297 self._cur_object.since = self.doc_comment_params[ 298 "since" 299 ].strip() 300 301 elif self.state == DBusXMLParser.STATE_SIGNAL: 302 if name == DBusXMLParser.STATE_ARG: 303 self.state = DBusXMLParser.STATE_ARG 304 arg_name = None 305 if "name" in attrs: 306 arg_name = attrs["name"] 307 arg = Arg(arg_name, attrs["type"]) 308 self._cur_object.args.append(arg) 309 self._cur_object = arg 310 elif name == DBusXMLParser.STATE_ANNOTATION: 311 self.state = DBusXMLParser.STATE_ANNOTATION 312 anno = Annotation(attrs["name"], attrs["value"]) 313 self._cur_object.annotations.append(anno) 314 self._cur_object = anno 315 else: 316 self.state = DBusXMLParser.STATE_IGNORED 317 318 # assign docs, if any 319 if self.doc_comment_last_symbol == old_cur_object.name: 320 if "name" in attrs and attrs["name"] in self.doc_comment_params: 321 doc_string = self.doc_comment_params[attrs["name"]] 322 if doc_string is not None: 323 self._cur_object.doc_string = doc_string 324 if "since" in self.doc_comment_params: 325 self._cur_object.since = self.doc_comment_params[ 326 "since" 327 ].strip() 328 329 elif self.state == DBusXMLParser.STATE_PROPERTY: 330 if name == DBusXMLParser.STATE_ANNOTATION: 331 self.state = DBusXMLParser.STATE_ANNOTATION 332 anno = Annotation(attrs["name"], attrs["value"]) 333 self._cur_object.annotations.append(anno) 334 self._cur_object = anno 335 else: 336 self.state = DBusXMLParser.STATE_IGNORED 337 338 elif self.state == DBusXMLParser.STATE_ARG: 339 if name == DBusXMLParser.STATE_ANNOTATION: 340 self.state = DBusXMLParser.STATE_ANNOTATION 341 anno = Annotation(attrs["name"], attrs["value"]) 342 self._cur_object.annotations.append(anno) 343 self._cur_object = anno 344 else: 345 self.state = DBusXMLParser.STATE_IGNORED 346 347 elif self.state == DBusXMLParser.STATE_ANNOTATION: 348 if name == DBusXMLParser.STATE_ANNOTATION: 349 self.state = DBusXMLParser.STATE_ANNOTATION 350 anno = Annotation(attrs["name"], attrs["value"]) 351 self._cur_object.annotations.append(anno) 352 self._cur_object = anno 353 else: 354 self.state = DBusXMLParser.STATE_IGNORED 355 356 else: 357 raise ValueError( 358 'Unhandled state "{}" while entering element with name "{}"'.format( 359 self.state, name 360 ) 361 ) 362 363 self.state_stack.append(old_state) 364 self._cur_object_stack.append(old_cur_object) 365 366 def handle_end_element(self, name): 367 self.state = self.state_stack.pop() 368 self._cur_object = self._cur_object_stack.pop() 369 370 371def parse_dbus_xml(xml_data): 372 parser = DBusXMLParser(xml_data, True) 373 return parser.parsed_interfaces 374