1#!/usr/bin/env python3 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""" 20A parser for metadata_definitions.xml can also render the resulting model 21over a Mako template. 22 23Usage: 24 metadata_parser_xml.py <filename.xml> <template.mako> [<output_file>] 25 - outputs the resulting template to output_file (stdout if none specified) 26 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 39import os 40 41from bs4 import BeautifulSoup 42from bs4 import NavigableString 43 44from io import StringIO 45 46from mako.template import Template 47from mako.lookup import TemplateLookup 48from mako.runtime import Context 49 50from metadata_model import * 51import metadata_model 52from metadata_validate import * 53import metadata_helpers 54 55class MetadataParserXml: 56 """ 57 A class to parse any XML block that passes validation with metadata-validate. 58 It builds a metadata_model.Metadata graph and then renders it over a 59 Mako template. 60 61 Attributes (Read-Only): 62 soup: an instance of BeautifulSoup corresponding to the XML contents 63 metadata: a constructed instance of metadata_model.Metadata 64 """ 65 def __init__(self, xml, file_name): 66 """ 67 Construct a new MetadataParserXml, immediately try to parse it into a 68 metadata model. 69 70 Args: 71 xml: The XML block to use for the metadata 72 file_name: Source of the XML block, only for debugging/errors 73 74 Raises: 75 ValueError: if the XML block failed to pass metadata_validate.py 76 """ 77 self._soup = validate_xml(xml) 78 79 if self._soup is None: 80 raise ValueError("%s has an invalid XML file" % (file_name)) 81 82 self._metadata = Metadata() 83 self._parse() 84 self._metadata.construct_graph() 85 86 @staticmethod 87 def create_from_file(file_name): 88 """ 89 Construct a new MetadataParserXml by loading and parsing an XML file. 90 91 Args: 92 file_name: Name of the XML file to load and parse. 93 94 Raises: 95 ValueError: if the XML file failed to pass metadata_validate.py 96 97 Returns: 98 MetadataParserXml instance representing the XML file. 99 """ 100 return MetadataParserXml(open(file_name).read(), file_name) 101 102 @property 103 def soup(self): 104 return self._soup 105 106 @property 107 def metadata(self): 108 return self._metadata 109 110 @staticmethod 111 def _find_direct_strings(element): 112 if element.string is not None: 113 return [element.string] 114 115 return [i for i in element.contents if isinstance(i, NavigableString)] 116 117 @staticmethod 118 def _strings_no_nl(element): 119 return "".join([i.strip() for i in MetadataParserXml._find_direct_strings(element)]) 120 121 def _parse(self): 122 123 tags = self.soup.tags 124 if tags is not None: 125 for tag in tags.find_all('tag'): 126 self.metadata.insert_tag(tag['id'], tag.string) 127 128 types = self.soup.types 129 if types is not None: 130 for tp in types.find_all('typedef'): 131 languages = {} 132 for lang in tp.find_all('language'): 133 languages[lang['name']] = lang.string 134 135 self.metadata.insert_type(tp['name'], 'typedef', languages=languages) 136 137 # add all entries, preserving the ordering of the XML file 138 # this is important for future ABI compatibility when generating code 139 entry_filter = lambda x: x.name == 'entry' or x.name == 'clone' 140 for entry in self.soup.find_all(entry_filter): 141 if entry.name == 'entry': 142 d = { 143 'name': fully_qualified_name(entry), 144 'type': entry['type'], 145 'kind': find_kind(entry), 146 'type_notes': entry.attrs.get('type_notes') 147 } 148 149 d2 = self._parse_entry(entry) 150 insert = self.metadata.insert_entry 151 else: 152 d = { 153 'name': entry['entry'], 154 'kind': find_kind(entry), 155 'target_kind': entry['kind'], 156 # no type since its the same 157 # no type_notes since its the same 158 } 159 d2 = {} 160 if 'hal_version' in entry.attrs: 161 d2['hal_version'] = entry['hal_version'] 162 163 insert = self.metadata.insert_clone 164 165 d3 = self._parse_entry_optional(entry) 166 167 entry_dict = {**d, **d2, **d3} 168 insert(entry_dict) 169 170 self.metadata.construct_graph() 171 172 def _parse_entry(self, entry): 173 d = {} 174 175 # 176 # Visibility 177 # 178 d['visibility'] = entry.get('visibility') 179 180 # 181 # Synthetic ? 182 # 183 d['synthetic'] = entry.get('synthetic') == 'true' 184 185 # 186 # Permission needed ? 187 # 188 d['permission_needed'] = entry.get('permission_needed') 189 190 # 191 # Hardware Level (one of limited, legacy, full) 192 # 193 d['hwlevel'] = entry.get('hwlevel') 194 195 # 196 # Deprecated ? 197 # 198 d['deprecated'] = entry.get('deprecated') == 'true' 199 200 # 201 # Optional for non-full hardware level devices 202 # 203 d['optional'] = entry.get('optional') == 'true' 204 205 # 206 # Typedef 207 # 208 d['type_name'] = entry.get('typedef') 209 210 # 211 # Initial HIDL HAL version the entry was added in 212 d['hal_version'] = entry.get('hal_version') 213 214 # 215 # Enum 216 # 217 if entry.get('enum', 'false') == 'true': 218 219 enum_values = [] 220 enum_deprecateds = [] 221 enum_optionals = [] 222 enum_hiddens = [] 223 enum_ndk_hiddens = [] 224 enum_notes = {} 225 enum_sdk_notes = {} 226 enum_ndk_notes = {} 227 enum_ids = {} 228 enum_hal_versions = {} 229 for value in entry.enum.find_all('value'): 230 231 value_body = self._strings_no_nl(value) 232 enum_values.append(value_body) 233 234 if value.attrs.get('deprecated', 'false') == 'true': 235 enum_deprecateds.append(value_body) 236 237 if value.attrs.get('optional', 'false') == 'true': 238 enum_optionals.append(value_body) 239 240 if value.attrs.get('hidden', 'false') == 'true': 241 enum_hiddens.append(value_body) 242 243 if value.attrs.get('ndk_hidden', 'false') == 'true': 244 enum_ndk_hiddens.append(value_body) 245 246 notes = value.find('notes') 247 if notes is not None: 248 enum_notes[value_body] = notes.string 249 250 sdk_notes = value.find('sdk_notes') 251 if sdk_notes is not None: 252 enum_sdk_notes[value_body] = sdk_notes.string 253 254 ndk_notes = value.find('ndk_notes') 255 if ndk_notes is not None: 256 enum_ndk_notes[value_body] = ndk_notes.string 257 258 if value.attrs.get('id') is not None: 259 enum_ids[value_body] = value['id'] 260 261 if value.attrs.get('hal_version') is not None: 262 enum_hal_versions[value_body] = value['hal_version'] 263 264 d['enum_values'] = enum_values 265 d['enum_deprecateds'] = enum_deprecateds 266 d['enum_optionals'] = enum_optionals 267 d['enum_hiddens'] = enum_hiddens 268 d['enum_ndk_hiddens'] = enum_ndk_hiddens 269 d['enum_notes'] = enum_notes 270 d['enum_sdk_notes'] = enum_sdk_notes 271 d['enum_ndk_notes'] = enum_ndk_notes 272 d['enum_ids'] = enum_ids 273 d['enum_hal_versions'] = enum_hal_versions 274 d['enum'] = True 275 276 # 277 # Container (Array/Tuple) 278 # 279 if entry.attrs.get('container') is not None: 280 container_name = entry['container'] 281 282 array = entry.find('array') 283 if array is not None: 284 array_sizes = [] 285 for size in array.find_all('size'): 286 array_sizes.append(size.string) 287 d['container_sizes'] = array_sizes 288 289 tupl = entry.find('tuple') 290 if tupl is not None: 291 tupl_values = [] 292 for val in tupl.find_all('value'): 293 tupl_values.append(val.name) 294 d['tuple_values'] = tupl_values 295 d['container_sizes'] = len(tupl_values) 296 297 d['container'] = container_name 298 299 return d 300 301 def _parse_entry_optional(self, entry): 302 d = {} 303 304 optional_elements = ['description', 'range', 'units', 'details', 'hal_details', 'ndk_details',\ 305 'deprecation_description'] 306 for i in optional_elements: 307 prop = find_child_tag(entry, i) 308 309 if prop is not None: 310 d[i] = prop.string 311 312 tag_ids = [] 313 for tag in entry.find_all('tag'): 314 tag_ids.append(tag['id']) 315 316 d['tag_ids'] = tag_ids 317 318 return d 319 320 def render(self, template, output_name=None, hal_version="3.2"): 321 """ 322 Render the metadata model using a Mako template as the view. 323 324 The template gets the metadata as an argument, as well as all 325 public attributes from the metadata_helpers module. 326 327 The output file is encoded with UTF-8. 328 329 Args: 330 template: path to a Mako template file 331 output_name: path to the output file, or None to use stdout 332 hal_version: target HAL version, used when generating HIDL HAL outputs. 333 Must be a string of form "X.Y" where X and Y are integers. 334 """ 335 buf = StringIO() 336 metadata_helpers._context_buf = buf 337 metadata_helpers._hal_major_version = int(hal_version.partition('.')[0]) 338 metadata_helpers._hal_minor_version = int(hal_version.partition('.')[2]) 339 340 helpers = [(i, getattr(metadata_helpers, i)) 341 for i in dir(metadata_helpers) if not i.startswith('_')] 342 helpers = dict(helpers) 343 344 lookup = TemplateLookup(directories=[os.getcwd()]) 345 tpl = Template(filename=template, lookup=lookup) 346 347 ctx = Context(buf, metadata=self.metadata, **helpers) 348 tpl.render_context(ctx) 349 350 tpl_data = buf.getvalue() 351 metadata_helpers._context_buf = None 352 buf.close() 353 354 if output_name is None: 355 print(tpl_data) 356 else: 357 open(output_name, "w").write(tpl_data) 358 359##################### 360##################### 361 362if __name__ == "__main__": 363 if len(sys.argv) <= 2: 364 print("Usage: %s <filename.xml> <template.mako> [<output_file>] [<hal_version>]" \ 365 % (sys.argv[0]), file=sys.stderr) 366 sys.exit(0) 367 368 file_name = sys.argv[1] 369 template_name = sys.argv[2] 370 output_name = sys.argv[3] if len(sys.argv) > 3 else None 371 hal_version = sys.argv[4] if len(sys.argv) > 4 else "3.2" 372 373 parser = MetadataParserXml.create_from_file(file_name) 374 parser.render(template_name, output_name, hal_version) 375 376 sys.exit(0) 377