1#!/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""" 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 40import StringIO 41 42from bs4 import BeautifulSoup 43from bs4 import NavigableString 44 45from mako.template import Template 46from mako.lookup import TemplateLookup 47from mako.runtime import Context 48 49from metadata_model import * 50import metadata_model 51from metadata_validate import * 52import metadata_helpers 53 54class MetadataParserXml: 55 """ 56 A class to parse any XML block that passes validation with metadata-validate. 57 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 """ 64 def __init__(self, xml, file_name): 65 """ 66 Construct a new MetadataParserXml, immediately try to parse it into a 67 metadata model. 68 69 Args: 70 xml: The XML block to use for the metadata 71 file_name: Source of the XML block, only for debugging/errors 72 73 Raises: 74 ValueError: if the XML block failed to pass metadata_validate.py 75 """ 76 self._soup = validate_xml(xml) 77 78 if self._soup is None: 79 raise ValueError("%s has an invalid XML file" % (file_name)) 80 81 self._metadata = Metadata() 82 self._parse() 83 self._metadata.construct_graph() 84 85 @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 101 @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 127 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 136 # 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 } 147 148 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 if 'hal_version' in entry.attrs: 160 d2['hal_version'] = entry['hal_version'] 161 162 insert = self.metadata.insert_clone 163 164 d3 = self._parse_entry_optional(entry) 165 166 entry_dict = dict(d.items() + d2.items() + d3.items()) 167 insert(entry_dict) 168 169 self.metadata.construct_graph() 170 171 def _parse_entry(self, entry): 172 d = {} 173 174 # 175 # Visibility 176 # 177 d['visibility'] = entry.get('visibility') 178 179 # 180 # Synthetic ? 181 # 182 d['synthetic'] = entry.get('synthetic') == 'true' 183 184 # 185 # Hardware Level (one of limited, legacy, full) 186 # 187 d['hwlevel'] = entry.get('hwlevel') 188 189 # 190 # Deprecated ? 191 # 192 d['deprecated'] = entry.get('deprecated') == 'true' 193 194 # 195 # Optional for non-full hardware level devices 196 # 197 d['optional'] = entry.get('optional') == 'true' 198 199 # 200 # Typedef 201 # 202 d['type_name'] = entry.get('typedef') 203 204 # 205 # Initial HIDL HAL version the entry was added in 206 d['hal_version'] = entry.get('hal_version') 207 208 # 209 # Enum 210 # 211 if entry.get('enum', 'false') == 'true': 212 213 enum_values = [] 214 enum_deprecateds = [] 215 enum_optionals = [] 216 enum_hiddens = [] 217 enum_ndk_hiddens = [] 218 enum_notes = {} 219 enum_sdk_notes = {} 220 enum_ndk_notes = {} 221 enum_ids = {} 222 enum_hal_versions = {} 223 for value in entry.enum.find_all('value'): 224 225 value_body = self._strings_no_nl(value) 226 enum_values.append(value_body) 227 228 if value.attrs.get('deprecated', 'false') == 'true': 229 enum_deprecateds.append(value_body) 230 231 if value.attrs.get('optional', 'false') == 'true': 232 enum_optionals.append(value_body) 233 234 if value.attrs.get('hidden', 'false') == 'true': 235 enum_hiddens.append(value_body) 236 237 if value.attrs.get('ndk_hidden', 'false') == 'true': 238 enum_ndk_hiddens.append(value_body) 239 240 notes = value.find('notes') 241 if notes is not None: 242 enum_notes[value_body] = notes.string 243 244 sdk_notes = value.find('sdk_notes') 245 if sdk_notes is not None: 246 enum_sdk_notes[value_body] = sdk_notes.string 247 248 ndk_notes = value.find('ndk_notes') 249 if ndk_notes is not None: 250 enum_ndk_notes[value_body] = ndk_notes.string 251 252 if value.attrs.get('id') is not None: 253 enum_ids[value_body] = value['id'] 254 255 if value.attrs.get('hal_version') is not None: 256 enum_hal_versions[value_body] = value['hal_version'] 257 258 d['enum_values'] = enum_values 259 d['enum_deprecateds'] = enum_deprecateds 260 d['enum_optionals'] = enum_optionals 261 d['enum_hiddens'] = enum_hiddens 262 d['enum_ndk_hiddens'] = enum_ndk_hiddens 263 d['enum_notes'] = enum_notes 264 d['enum_sdk_notes'] = enum_sdk_notes 265 d['enum_ndk_notes'] = enum_ndk_notes 266 d['enum_ids'] = enum_ids 267 d['enum_hal_versions'] = enum_hal_versions 268 d['enum'] = True 269 270 # 271 # Container (Array/Tuple) 272 # 273 if entry.attrs.get('container') is not None: 274 container_name = entry['container'] 275 276 array = entry.find('array') 277 if array is not None: 278 array_sizes = [] 279 for size in array.find_all('size'): 280 array_sizes.append(size.string) 281 d['container_sizes'] = array_sizes 282 283 tupl = entry.find('tuple') 284 if tupl is not None: 285 tupl_values = [] 286 for val in tupl.find_all('value'): 287 tupl_values.append(val.name) 288 d['tuple_values'] = tupl_values 289 d['container_sizes'] = len(tupl_values) 290 291 d['container'] = container_name 292 293 return d 294 295 def _parse_entry_optional(self, entry): 296 d = {} 297 298 optional_elements = ['description', 'range', 'units', 'details', 'hal_details', 'ndk_details',\ 299 'deprecation_description'] 300 for i in optional_elements: 301 prop = find_child_tag(entry, i) 302 303 if prop is not None: 304 d[i] = prop.string 305 306 tag_ids = [] 307 for tag in entry.find_all('tag'): 308 tag_ids.append(tag['id']) 309 310 d['tag_ids'] = tag_ids 311 312 return d 313 314 def render(self, template, output_name=None, hal_version="3.2"): 315 """ 316 Render the metadata model using a Mako template as the view. 317 318 The template gets the metadata as an argument, as well as all 319 public attributes from the metadata_helpers module. 320 321 The output file is encoded with UTF-8. 322 323 Args: 324 template: path to a Mako template file 325 output_name: path to the output file, or None to use stdout 326 hal_version: target HAL version, used when generating HIDL HAL outputs. 327 Must be a string of form "X.Y" where X and Y are integers. 328 """ 329 buf = StringIO.StringIO() 330 metadata_helpers._context_buf = buf 331 metadata_helpers._hal_major_version = int(hal_version.partition('.')[0]) 332 metadata_helpers._hal_minor_version = int(hal_version.partition('.')[2]) 333 334 helpers = [(i, getattr(metadata_helpers, i)) 335 for i in dir(metadata_helpers) if not i.startswith('_')] 336 helpers = dict(helpers) 337 338 lookup = TemplateLookup(directories=[os.getcwd()]) 339 tpl = Template(filename=template, lookup=lookup) 340 341 ctx = Context(buf, metadata=self.metadata, **helpers) 342 tpl.render_context(ctx) 343 344 tpl_data = buf.getvalue() 345 metadata_helpers._context_buf = None 346 buf.close() 347 348 if output_name is None: 349 print tpl_data 350 else: 351 file(output_name, "w").write(tpl_data.encode('utf-8')) 352 353##################### 354##################### 355 356if __name__ == "__main__": 357 if len(sys.argv) <= 2: 358 print >> sys.stderr, \ 359 "Usage: %s <filename.xml> <template.mako> [<output_file>] [<hal_version>]" \ 360 % (sys.argv[0]) 361 sys.exit(0) 362 363 file_name = sys.argv[1] 364 template_name = sys.argv[2] 365 output_name = sys.argv[3] if len(sys.argv) > 3 else None 366 hal_version = sys.argv[4] if len(sys.argv) > 4 else "3.2" 367 368 parser = MetadataParserXml.create_from_file(file_name) 369 parser.render(template_name, output_name, hal_version) 370 371 sys.exit(0) 372