| 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 | |
| 20 | import xml.parsers.expat |
| 21 | |
| 22 | |
| 23 | class Annotation: |
| 24 | def __init__(self, key, value): |
| 25 | self.key = key |
| 26 | self.value = value |
| 27 | self.annotations = [] |
| 28 | self.since = "" |
| 29 | |
| 30 | |
| 31 | class 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 | |
| 40 | class 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 | |
| 53 | class 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 | |
| 63 | class 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 | |
| 88 | class 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 | |
| 101 | class 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 | |
| 371 | def parse_dbus_xml(xml_data): |
| 372 | parser = DBusXMLParser(xml_data, True) |
| 373 | return parser.parsed_interfaces |