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