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 sys 23import xml.parsers.expat 24 25from . import dbustypes 26from .utils import print_error 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): 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._parser.Parse(xml_data) 57 58 COMMENT_STATE_BEGIN = 'begin' 59 COMMENT_STATE_PARAMS = 'params' 60 COMMENT_STATE_BODY = 'body' 61 COMMENT_STATE_SKIP = 'skip' 62 def handle_comment(self, data): 63 comment_state = DBusXMLParser.COMMENT_STATE_BEGIN; 64 lines = data.split('\n') 65 symbol = '' 66 body = '' 67 in_para = False 68 params = {} 69 for line in lines: 70 orig_line = line 71 line = line.lstrip() 72 if comment_state == DBusXMLParser.COMMENT_STATE_BEGIN: 73 if len(line) > 0: 74 colon_index = line.find(': ') 75 if colon_index == -1: 76 if line.endswith(':'): 77 symbol = line[0:len(line)-1] 78 comment_state = DBusXMLParser.COMMENT_STATE_PARAMS 79 else: 80 comment_state = DBusXMLParser.COMMENT_STATE_SKIP 81 else: 82 symbol = line[0:colon_index] 83 rest_of_line = line[colon_index+2:].strip() 84 if len(rest_of_line) > 0: 85 body += '<para>' + rest_of_line + '</para>' 86 comment_state = DBusXMLParser.COMMENT_STATE_PARAMS 87 elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS: 88 if line.startswith('@'): 89 colon_index = line.find(': ') 90 if colon_index == -1: 91 comment_state = DBusXMLParser.COMMENT_STATE_BODY 92 if not in_para: 93 body += '<para>' 94 in_para = True 95 body += orig_line + '\n' 96 else: 97 param = line[1:colon_index] 98 docs = line[colon_index + 2:] 99 params[param] = docs 100 else: 101 comment_state = DBusXMLParser.COMMENT_STATE_BODY 102 if len(line) > 0: 103 if not in_para: 104 body += '<para>' 105 in_para = True 106 body += orig_line + '\n' 107 elif comment_state == DBusXMLParser.COMMENT_STATE_BODY: 108 if len(line) > 0: 109 if not in_para: 110 body += '<para>' 111 in_para = True 112 body += orig_line + '\n' 113 else: 114 if in_para: 115 body += '</para>' 116 in_para = False 117 if in_para: 118 body += '</para>' 119 120 if symbol != '': 121 self.doc_comment_last_symbol = symbol 122 self.doc_comment_params = params 123 self.doc_comment_body = body 124 125 def handle_char_data(self, data): 126 #print 'char_data=%s'%data 127 pass 128 129 def handle_start_element(self, name, attrs): 130 old_state = self.state 131 old_cur_object = self._cur_object 132 if self.state == DBusXMLParser.STATE_IGNORED: 133 self.state = DBusXMLParser.STATE_IGNORED 134 elif self.state == DBusXMLParser.STATE_TOP: 135 if name == DBusXMLParser.STATE_NODE: 136 self.state = DBusXMLParser.STATE_NODE 137 else: 138 self.state = DBusXMLParser.STATE_IGNORED 139 elif self.state == DBusXMLParser.STATE_NODE: 140 if name == DBusXMLParser.STATE_INTERFACE: 141 self.state = DBusXMLParser.STATE_INTERFACE 142 iface = dbustypes.Interface(attrs['name']) 143 self._cur_object = iface 144 self.parsed_interfaces.append(iface) 145 elif name == DBusXMLParser.STATE_ANNOTATION: 146 self.state = DBusXMLParser.STATE_ANNOTATION 147 anno = dbustypes.Annotation(attrs['name'], attrs['value']) 148 self._cur_object.annotations.append(anno) 149 self._cur_object = anno 150 else: 151 self.state = DBusXMLParser.STATE_IGNORED 152 153 # assign docs, if any 154 if 'name' in attrs and self.doc_comment_last_symbol == attrs['name']: 155 self._cur_object.doc_string = self.doc_comment_body 156 if 'short_description' in self.doc_comment_params: 157 short_description = self.doc_comment_params['short_description'] 158 self._cur_object.doc_string_brief = short_description 159 if 'since' in self.doc_comment_params: 160 self._cur_object.since = \ 161 self.doc_comment_params['since'].strip() 162 163 elif self.state == DBusXMLParser.STATE_INTERFACE: 164 if name == DBusXMLParser.STATE_METHOD: 165 self.state = DBusXMLParser.STATE_METHOD 166 method = dbustypes.Method(attrs['name']) 167 self._cur_object.methods.append(method) 168 self._cur_object = method 169 elif name == DBusXMLParser.STATE_SIGNAL: 170 self.state = DBusXMLParser.STATE_SIGNAL 171 signal = dbustypes.Signal(attrs['name']) 172 self._cur_object.signals.append(signal) 173 self._cur_object = signal 174 elif name == DBusXMLParser.STATE_PROPERTY: 175 self.state = DBusXMLParser.STATE_PROPERTY 176 prop = dbustypes.Property(attrs['name'], attrs['type'], attrs['access']) 177 self._cur_object.properties.append(prop) 178 self._cur_object = prop 179 elif name == DBusXMLParser.STATE_ANNOTATION: 180 self.state = DBusXMLParser.STATE_ANNOTATION 181 anno = dbustypes.Annotation(attrs['name'], attrs['value']) 182 self._cur_object.annotations.append(anno) 183 self._cur_object = anno 184 else: 185 self.state = DBusXMLParser.STATE_IGNORED 186 187 # assign docs, if any 188 if 'name' in attrs and self.doc_comment_last_symbol == attrs['name']: 189 self._cur_object.doc_string = self.doc_comment_body 190 if 'since' in self.doc_comment_params: 191 self._cur_object.since = \ 192 self.doc_comment_params['since'].strip() 193 194 elif self.state == DBusXMLParser.STATE_METHOD: 195 if name == DBusXMLParser.STATE_ARG: 196 self.state = DBusXMLParser.STATE_ARG 197 arg_name = None 198 if 'name' in attrs: 199 arg_name = attrs['name'] 200 arg = dbustypes.Arg(arg_name, attrs['type']) 201 direction = attrs.get('direction', 'in') 202 if direction == 'in': 203 self._cur_object.in_args.append(arg) 204 elif direction == 'out': 205 self._cur_object.out_args.append(arg) 206 else: 207 print_error('Invalid direction "{}"'.format(direction)) 208 self._cur_object = arg 209 elif name == DBusXMLParser.STATE_ANNOTATION: 210 self.state = DBusXMLParser.STATE_ANNOTATION 211 anno = dbustypes.Annotation(attrs['name'], attrs['value']) 212 self._cur_object.annotations.append(anno) 213 self._cur_object = anno 214 else: 215 self.state = DBusXMLParser.STATE_IGNORED 216 217 # assign docs, if any 218 if self.doc_comment_last_symbol == old_cur_object.name: 219 if 'name' in attrs and attrs['name'] in self.doc_comment_params: 220 doc_string = self.doc_comment_params[attrs['name']] 221 if doc_string != None: 222 self._cur_object.doc_string = doc_string 223 if 'since' in self.doc_comment_params: 224 self._cur_object.since = \ 225 self.doc_comment_params['since'].strip() 226 227 elif self.state == DBusXMLParser.STATE_SIGNAL: 228 if name == DBusXMLParser.STATE_ARG: 229 self.state = DBusXMLParser.STATE_ARG 230 arg_name = None 231 if 'name' in attrs: 232 arg_name = attrs['name'] 233 arg = dbustypes.Arg(arg_name, attrs['type']) 234 self._cur_object.args.append(arg) 235 self._cur_object = arg 236 elif name == DBusXMLParser.STATE_ANNOTATION: 237 self.state = DBusXMLParser.STATE_ANNOTATION 238 anno = dbustypes.Annotation(attrs['name'], attrs['value']) 239 self._cur_object.annotations.append(anno) 240 self._cur_object = anno 241 else: 242 self.state = DBusXMLParser.STATE_IGNORED 243 244 # assign docs, if any 245 if self.doc_comment_last_symbol == old_cur_object.name: 246 if 'name' in attrs and attrs['name'] in self.doc_comment_params: 247 doc_string = self.doc_comment_params[attrs['name']] 248 if doc_string != None: 249 self._cur_object.doc_string = doc_string 250 if 'since' in self.doc_comment_params: 251 self._cur_object.since = \ 252 self.doc_comment_params['since'].strip() 253 254 elif self.state == DBusXMLParser.STATE_PROPERTY: 255 if name == DBusXMLParser.STATE_ANNOTATION: 256 self.state = DBusXMLParser.STATE_ANNOTATION 257 anno = dbustypes.Annotation(attrs['name'], attrs['value']) 258 self._cur_object.annotations.append(anno) 259 self._cur_object = anno 260 else: 261 self.state = DBusXMLParser.STATE_IGNORED 262 263 elif self.state == DBusXMLParser.STATE_ARG: 264 if name == DBusXMLParser.STATE_ANNOTATION: 265 self.state = DBusXMLParser.STATE_ANNOTATION 266 anno = dbustypes.Annotation(attrs['name'], attrs['value']) 267 self._cur_object.annotations.append(anno) 268 self._cur_object = anno 269 else: 270 self.state = DBusXMLParser.STATE_IGNORED 271 272 elif self.state == DBusXMLParser.STATE_ANNOTATION: 273 if name == DBusXMLParser.STATE_ANNOTATION: 274 self.state = DBusXMLParser.STATE_ANNOTATION 275 anno = dbustypes.Annotation(attrs['name'], attrs['value']) 276 self._cur_object.annotations.append(anno) 277 self._cur_object = anno 278 else: 279 self.state = DBusXMLParser.STATE_IGNORED 280 281 else: 282 print_error('Unhandled state "{}" while entering element with name "{}"'.format(self.state, name)) 283 284 self.state_stack.append(old_state) 285 self._cur_object_stack.append(old_cur_object) 286 287 def handle_end_element(self, name): 288 self.state = self.state_stack.pop() 289 self._cur_object = self._cur_object_stack.pop() 290 291def parse_dbus_xml(xml_data): 292 parser = DBusXMLParser(xml_data) 293 return parser.parsed_interfaces 294