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_visibilities = {} 223 enum_notes = {} 224 enum_sdk_notes = {} 225 enum_ndk_notes = {} 226 enum_ids = {} 227 enum_hal_versions = {} 228 for value in entry.enum.find_all('value'): 229 230 value_body = self._strings_no_nl(value) 231 enum_values.append(value_body) 232 233 if value.attrs.get('deprecated', 'false') == 'true': 234 enum_deprecateds.append(value_body) 235 236 if value.attrs.get('optional', 'false') == 'true': 237 enum_optionals.append(value_body) 238 239 visibility = value.attrs.get('visibility') 240 if visibility is not None: 241 enum_visibilities[value_body] = visibility 242 243 notes = value.find('notes') 244 if notes is not None: 245 enum_notes[value_body] = notes.string 246 247 sdk_notes = value.find('sdk_notes') 248 if sdk_notes is not None: 249 enum_sdk_notes[value_body] = sdk_notes.string 250 251 ndk_notes = value.find('ndk_notes') 252 if ndk_notes is not None: 253 enum_ndk_notes[value_body] = ndk_notes.string 254 255 if value.attrs.get('id') is not None: 256 enum_ids[value_body] = value['id'] 257 258 if value.attrs.get('hal_version') is not None: 259 enum_hal_versions[value_body] = value['hal_version'] 260 261 d['enum_values'] = enum_values 262 d['enum_deprecateds'] = enum_deprecateds 263 d['enum_optionals'] = enum_optionals 264 d['enum_visibilities'] = enum_visibilities 265 d['enum_notes'] = enum_notes 266 d['enum_sdk_notes'] = enum_sdk_notes 267 d['enum_ndk_notes'] = enum_ndk_notes 268 d['enum_ids'] = enum_ids 269 d['enum_hal_versions'] = enum_hal_versions 270 d['enum'] = True 271 272 # 273 # Container (Array/Tuple) 274 # 275 if entry.attrs.get('container') is not None: 276 container_name = entry['container'] 277 278 array = entry.find('array') 279 if array is not None: 280 array_sizes = [] 281 for size in array.find_all('size'): 282 array_sizes.append(size.string) 283 d['container_sizes'] = array_sizes 284 285 tupl = entry.find('tuple') 286 if tupl is not None: 287 tupl_values = [] 288 for val in tupl.find_all('value'): 289 tupl_values.append(val.name) 290 d['tuple_values'] = tupl_values 291 d['container_sizes'] = len(tupl_values) 292 293 d['container'] = container_name 294 295 return d 296 297 def _parse_entry_optional(self, entry): 298 d = {} 299 300 optional_elements = ['description', 'range', 'units', 'details', 'hal_details', 'ndk_details',\ 301 'deprecation_description'] 302 for i in optional_elements: 303 prop = find_child_tag(entry, i) 304 305 if prop is not None: 306 d[i] = prop.string 307 308 tag_ids = [] 309 for tag in entry.find_all('tag'): 310 tag_ids.append(tag['id']) 311 312 d['tag_ids'] = tag_ids 313 314 return d 315 316 def render(self, template, output_name=None, hal_version="3.2", copyright_year="2021"): 317 """ 318 Render the metadata model using a Mako template as the view. 319 320 The template gets the metadata as an argument, as well as all 321 public attributes from the metadata_helpers module. 322 323 The output file is encoded with UTF-8. 324 325 Args: 326 template: path to a Mako template file 327 output_name: path to the output file, or None to use stdout 328 hal_version: target HAL version, used when generating HIDL HAL outputs. 329 Must be a string of form "X.Y" where X and Y are integers. 330 copyright_year: the year in the copyright section of output file 331 """ 332 buf = StringIO() 333 metadata_helpers._context_buf = buf 334 metadata_helpers._hal_major_version = int(hal_version.partition('.')[0]) 335 metadata_helpers._hal_minor_version = int(hal_version.partition('.')[2]) 336 metadata_helpers._copyright_year = copyright_year 337 338 helpers = [(i, getattr(metadata_helpers, i)) 339 for i in dir(metadata_helpers) if not i.startswith('_')] 340 helpers = dict(helpers) 341 342 lookup = TemplateLookup(directories=[os.getcwd()]) 343 tpl = Template(filename=template, lookup=lookup) 344 345 ctx = Context(buf, metadata=self.metadata, **helpers) 346 tpl.render_context(ctx) 347 348 tpl_data = buf.getvalue() 349 metadata_helpers._context_buf = None 350 buf.close() 351 352 if output_name is None: 353 print(tpl_data) 354 else: 355 open(output_name, "w").write(tpl_data) 356 357##################### 358##################### 359 360if __name__ == "__main__": 361 if len(sys.argv) <= 2: 362 print("Usage: %s <filename.xml> <template.mako> [<output_file>]"\ 363 " [<hal_version>] [<copyright_year>]" \ 364 % (sys.argv[0]), file=sys.stderr) 365 sys.exit(0) 366 367 file_name = sys.argv[1] 368 template_name = sys.argv[2] 369 output_name = sys.argv[3] if len(sys.argv) > 3 else None 370 hal_version = sys.argv[4] if len(sys.argv) > 4 else "3.2" 371 copyright_year = sys.argv[5] if len(sys.argv) > 5 else "2021" 372 373 parser = MetadataParserXml.create_from_file(file_name) 374 parser.render(template_name, output_name, hal_version, copyright_year) 375 376 sys.exit(0) 377