blob: 0f18e6ee4098c829ca9d954dc36bf9f215b93ca9 [file] [log] [blame]
Igor Murashkin96bd0192012-11-19 16:49:37 -08001#!/usr/bin/python
2
3#
4# Copyright (C) 2012 The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19"""
Eino-Ville Talvala0d404952017-11-10 15:13:04 -080020A parser for metadata_definitions.xml can also render the resulting model
Igor Murashkin96bd0192012-11-19 16:49:37 -080021over a Mako template.
22
23Usage:
Igor Murashkin48098682012-12-05 14:51:57 -080024 metadata_parser_xml.py <filename.xml> <template.mako> [<output_file>]
25 - outputs the resulting template to output_file (stdout if none specified)
Igor Murashkin96bd0192012-11-19 16:49:37 -080026
27Module:
28 The parser is also available as a module import (MetadataParserXml) to use
29 in other modules.
30
31Dependencies:
32 BeautifulSoup - an HTML/XML parser available to download from
33 http://www.crummy.com/software/BeautifulSoup/
34 Mako - a template engine for Python, available to download from
35 http://www.makotemplates.org/
36"""
37
38import sys
Igor Murashkinda1c3142012-11-21 17:11:37 -080039import os
40import StringIO
Igor Murashkin96bd0192012-11-19 16:49:37 -080041
42from bs4 import BeautifulSoup
43from bs4 import NavigableString
44
45from mako.template import Template
Igor Murashkinda1c3142012-11-21 17:11:37 -080046from mako.lookup import TemplateLookup
47from mako.runtime import Context
Igor Murashkin96bd0192012-11-19 16:49:37 -080048
49from metadata_model import *
Igor Murashkin617da162012-11-29 13:35:15 -080050import metadata_model
Igor Murashkin96bd0192012-11-19 16:49:37 -080051from metadata_validate import *
Igor Murashkinda1c3142012-11-21 17:11:37 -080052import metadata_helpers
Igor Murashkin96bd0192012-11-19 16:49:37 -080053
54class MetadataParserXml:
55 """
Eino-Ville Talvala63c0fb22014-01-02 16:11:44 -080056 A class to parse any XML block that passes validation with metadata-validate.
Igor Murashkin96bd0192012-11-19 16:49:37 -080057 It builds a metadata_model.Metadata graph and then renders it over a
58 Mako template.
59
60 Attributes (Read-Only):
61 soup: an instance of BeautifulSoup corresponding to the XML contents
62 metadata: a constructed instance of metadata_model.Metadata
63 """
Eino-Ville Talvala63c0fb22014-01-02 16:11:44 -080064 def __init__(self, xml, file_name):
Igor Murashkin96bd0192012-11-19 16:49:37 -080065 """
66 Construct a new MetadataParserXml, immediately try to parse it into a
67 metadata model.
68
69 Args:
Eino-Ville Talvala63c0fb22014-01-02 16:11:44 -080070 xml: The XML block to use for the metadata
71 file_name: Source of the XML block, only for debugging/errors
Igor Murashkin96bd0192012-11-19 16:49:37 -080072
73 Raises:
Eino-Ville Talvala63c0fb22014-01-02 16:11:44 -080074 ValueError: if the XML block failed to pass metadata_validate.py
Igor Murashkin96bd0192012-11-19 16:49:37 -080075 """
Eino-Ville Talvala63c0fb22014-01-02 16:11:44 -080076 self._soup = validate_xml(xml)
Igor Murashkin96bd0192012-11-19 16:49:37 -080077
78 if self._soup is None:
Eino-Ville Talvala63c0fb22014-01-02 16:11:44 -080079 raise ValueError("%s has an invalid XML file" % (file_name))
Igor Murashkin96bd0192012-11-19 16:49:37 -080080
81 self._metadata = Metadata()
82 self._parse()
83 self._metadata.construct_graph()
84
Eino-Ville Talvala63c0fb22014-01-02 16:11:44 -080085 @staticmethod
86 def create_from_file(file_name):
87 """
88 Construct a new MetadataParserXml by loading and parsing an XML file.
89
90 Args:
91 file_name: Name of the XML file to load and parse.
92
93 Raises:
94 ValueError: if the XML file failed to pass metadata_validate.py
95
96 Returns:
97 MetadataParserXml instance representing the XML file.
98 """
99 return MetadataParserXml(file(file_name).read(), file_name)
100
Igor Murashkin96bd0192012-11-19 16:49:37 -0800101 @property
102 def soup(self):
103 return self._soup
104
105 @property
106 def metadata(self):
107 return self._metadata
108
109 @staticmethod
110 def _find_direct_strings(element):
111 if element.string is not None:
112 return [element.string]
113
114 return [i for i in element.contents if isinstance(i, NavigableString)]
115
116 @staticmethod
117 def _strings_no_nl(element):
118 return "".join([i.strip() for i in MetadataParserXml._find_direct_strings(element)])
119
120 def _parse(self):
121
122 tags = self.soup.tags
123 if tags is not None:
124 for tag in tags.find_all('tag'):
125 self.metadata.insert_tag(tag['id'], tag.string)
126
Igor Murashkinb8dc8812013-07-17 16:29:34 -0700127 types = self.soup.types
128 if types is not None:
129 for tp in types.find_all('typedef'):
130 languages = {}
131 for lang in tp.find_all('language'):
132 languages[lang['name']] = lang.string
133
134 self.metadata.insert_type(tp['name'], 'typedef', languages=languages)
135
Igor Murashkin586c8612012-11-29 17:08:36 -0800136 # add all entries, preserving the ordering of the XML file
137 # this is important for future ABI compatibility when generating code
138 entry_filter = lambda x: x.name == 'entry' or x.name == 'clone'
139 for entry in self.soup.find_all(entry_filter):
140 if entry.name == 'entry':
141 d = {
142 'name': fully_qualified_name(entry),
143 'type': entry['type'],
144 'kind': find_kind(entry),
145 'type_notes': entry.attrs.get('type_notes')
146 }
Igor Murashkin96bd0192012-11-19 16:49:37 -0800147
Igor Murashkin586c8612012-11-29 17:08:36 -0800148 d2 = self._parse_entry(entry)
149 insert = self.metadata.insert_entry
150 else:
151 d = {
152 'name': entry['entry'],
153 'kind': find_kind(entry),
154 'target_kind': entry['kind'],
155 # no type since its the same
156 # no type_notes since its the same
157 }
158 d2 = {}
159
160 insert = self.metadata.insert_clone
161
Igor Murashkin96bd0192012-11-19 16:49:37 -0800162 d3 = self._parse_entry_optional(entry)
163
164 entry_dict = dict(d.items() + d2.items() + d3.items())
Igor Murashkin586c8612012-11-29 17:08:36 -0800165 insert(entry_dict)
Igor Murashkin96bd0192012-11-19 16:49:37 -0800166
167 self.metadata.construct_graph()
168
169 def _parse_entry(self, entry):
170 d = {}
171
172 #
Eino-Ville Talvalaf384f0a2013-07-12 17:02:27 -0700173 # Visibility
174 #
175 d['visibility'] = entry.get('visibility')
176
177 #
Igor Murashkin6c936c12014-05-13 14:51:49 -0700178 # Synthetic ?
179 #
180 d['synthetic'] = entry.get('synthetic') == 'true'
181
182 #
Igor Murashkinca256272014-10-02 15:27:09 -0700183 # Hardware Level (one of limited, legacy, full)
184 #
185 d['hwlevel'] = entry.get('hwlevel')
186
187 #
Igor Murashkin6c936c12014-05-13 14:51:49 -0700188 # Deprecated ?
189 #
190 d['deprecated'] = entry.get('deprecated') == 'true'
191
192 #
Alex Rayef40ad62013-10-01 17:52:33 -0700193 # Optional for non-full hardware level devices
194 #
195 d['optional'] = entry.get('optional') == 'true'
196
197 #
Igor Murashkinb8dc8812013-07-17 16:29:34 -0700198 # Typedef
199 #
200 d['type_name'] = entry.get('typedef')
201
202 #
Eino-Ville Talvala0d404952017-11-10 15:13:04 -0800203 # Initial HIDL HAL version the entry was added in
204 d['hal_version'] = entry.get('hal_version')
205
206 #
Igor Murashkin96bd0192012-11-19 16:49:37 -0800207 # Enum
208 #
Igor Murashkinb556bc42012-12-04 16:07:21 -0800209 if entry.get('enum', 'false') == 'true':
Igor Murashkin96bd0192012-11-19 16:49:37 -0800210
211 enum_values = []
Zhijun He7defc682015-05-22 17:04:15 -0700212 enum_deprecateds = []
Igor Murashkin96bd0192012-11-19 16:49:37 -0800213 enum_optionals = []
Eino-Ville Talvalab4329162014-06-09 14:23:02 -0700214 enum_hiddens = []
Yin-Chia Yeh3ba998e2016-04-06 11:58:41 -0700215 enum_ndk_hiddens = []
Igor Murashkin96bd0192012-11-19 16:49:37 -0800216 enum_notes = {}
Eino-Ville Talvala0d404952017-11-10 15:13:04 -0800217 enum_sdk_notes = {}
218 enum_ndk_notes = {}
Igor Murashkin96bd0192012-11-19 16:49:37 -0800219 enum_ids = {}
220 for value in entry.enum.find_all('value'):
221
222 value_body = self._strings_no_nl(value)
223 enum_values.append(value_body)
224
Zhijun He7defc682015-05-22 17:04:15 -0700225 if value.attrs.get('deprecated', 'false') == 'true':
226 enum_deprecateds.append(value_body)
227
Igor Murashkin96bd0192012-11-19 16:49:37 -0800228 if value.attrs.get('optional', 'false') == 'true':
229 enum_optionals.append(value_body)
230
Eino-Ville Talvalab4329162014-06-09 14:23:02 -0700231 if value.attrs.get('hidden', 'false') == 'true':
232 enum_hiddens.append(value_body)
233
Yin-Chia Yeh3ba998e2016-04-06 11:58:41 -0700234 if value.attrs.get('ndk_hidden', 'false') == 'true':
235 enum_ndk_hiddens.append(value_body)
236
Igor Murashkin96bd0192012-11-19 16:49:37 -0800237 notes = value.find('notes')
238 if notes is not None:
239 enum_notes[value_body] = notes.string
240
Eino-Ville Talvala0d404952017-11-10 15:13:04 -0800241 sdk_notes = value.find('sdk_notes')
242 if sdk_notes is not None:
243 enum_sdk_notes[value_body] = sdk_notes.string
244
245 ndk_notes = value.find('ndk_notes')
246 if ndk_notes is not None:
247 enum_ndk_notes[value_body] = ndk_notes.string
248
Igor Murashkin96bd0192012-11-19 16:49:37 -0800249 if value.attrs.get('id') is not None:
250 enum_ids[value_body] = value['id']
251
252 d['enum_values'] = enum_values
Zhijun He7defc682015-05-22 17:04:15 -0700253 d['enum_deprecateds'] = enum_deprecateds
Igor Murashkin96bd0192012-11-19 16:49:37 -0800254 d['enum_optionals'] = enum_optionals
Eino-Ville Talvalab4329162014-06-09 14:23:02 -0700255 d['enum_hiddens'] = enum_hiddens
Yin-Chia Yeh3ba998e2016-04-06 11:58:41 -0700256 d['enum_ndk_hiddens'] = enum_ndk_hiddens
Igor Murashkin96bd0192012-11-19 16:49:37 -0800257 d['enum_notes'] = enum_notes
Eino-Ville Talvala0d404952017-11-10 15:13:04 -0800258 d['enum_sdk_notes'] = enum_sdk_notes
259 d['enum_ndk_notes'] = enum_ndk_notes
Igor Murashkin96bd0192012-11-19 16:49:37 -0800260 d['enum_ids'] = enum_ids
Igor Murashkinb556bc42012-12-04 16:07:21 -0800261 d['enum'] = True
Igor Murashkin96bd0192012-11-19 16:49:37 -0800262
263 #
264 # Container (Array/Tuple)
265 #
266 if entry.attrs.get('container') is not None:
267 container_name = entry['container']
268
269 array = entry.find('array')
270 if array is not None:
271 array_sizes = []
272 for size in array.find_all('size'):
273 array_sizes.append(size.string)
274 d['container_sizes'] = array_sizes
275
276 tupl = entry.find('tuple')
277 if tupl is not None:
278 tupl_values = []
279 for val in tupl.find_all('value'):
280 tupl_values.append(val.name)
281 d['tuple_values'] = tupl_values
282 d['container_sizes'] = len(tupl_values)
283
284 d['container'] = container_name
285
286 return d
287
288 def _parse_entry_optional(self, entry):
289 d = {}
290
Eino-Ville Talvala0d404952017-11-10 15:13:04 -0800291 optional_elements = ['description', 'range', 'units', 'details', 'hal_details', 'ndk_details']
Igor Murashkin96bd0192012-11-19 16:49:37 -0800292 for i in optional_elements:
293 prop = find_child_tag(entry, i)
294
295 if prop is not None:
296 d[i] = prop.string
297
298 tag_ids = []
299 for tag in entry.find_all('tag'):
300 tag_ids.append(tag['id'])
301
302 d['tag_ids'] = tag_ids
303
304 return d
305
306 def render(self, template, output_name=None):
307 """
308 Render the metadata model using a Mako template as the view.
309
Igor Murashkinda1c3142012-11-21 17:11:37 -0800310 The template gets the metadata as an argument, as well as all
311 public attributes from the metadata_helpers module.
312
Igor Murashkin1dd4ecb2013-12-11 13:31:00 -0800313 The output file is encoded with UTF-8.
314
Igor Murashkin96bd0192012-11-19 16:49:37 -0800315 Args:
316 template: path to a Mako template file
317 output_name: path to the output file, or None to use stdout
318 """
Igor Murashkinda1c3142012-11-21 17:11:37 -0800319 buf = StringIO.StringIO()
320 metadata_helpers._context_buf = buf
321
322 helpers = [(i, getattr(metadata_helpers, i))
323 for i in dir(metadata_helpers) if not i.startswith('_')]
324 helpers = dict(helpers)
325
326 lookup = TemplateLookup(directories=[os.getcwd()])
327 tpl = Template(filename=template, lookup=lookup)
328
329 ctx = Context(buf, metadata=self.metadata, **helpers)
330 tpl.render_context(ctx)
331
332 tpl_data = buf.getvalue()
333 metadata_helpers._context_buf = None
334 buf.close()
Igor Murashkin96bd0192012-11-19 16:49:37 -0800335
336 if output_name is None:
337 print tpl_data
338 else:
Igor Murashkin1dd4ecb2013-12-11 13:31:00 -0800339 file(output_name, "w").write(tpl_data.encode('utf-8'))
Igor Murashkin96bd0192012-11-19 16:49:37 -0800340
341#####################
342#####################
343
344if __name__ == "__main__":
Igor Murashkin48098682012-12-05 14:51:57 -0800345 if len(sys.argv) <= 2:
346 print >> sys.stderr, \
347 "Usage: %s <filename.xml> <template.mako> [<output_file>]" \
348 % (sys.argv[0])
Igor Murashkin96bd0192012-11-19 16:49:37 -0800349 sys.exit(0)
350
351 file_name = sys.argv[1]
Igor Murashkin617da162012-11-29 13:35:15 -0800352 template_name = sys.argv[2]
Igor Murashkin48098682012-12-05 14:51:57 -0800353 output_name = sys.argv[3] if len(sys.argv) > 3 else None
Eino-Ville Talvala63c0fb22014-01-02 16:11:44 -0800354 parser = MetadataParserXml.create_from_file(file_name)
Igor Murashkin48098682012-12-05 14:51:57 -0800355 parser.render(template_name, output_name)
Igor Murashkin96bd0192012-11-19 16:49:37 -0800356
357 sys.exit(0)