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