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 # Permission needed ? 186 # 187 d['permission_needed'] = entry.get('permission_needed') 188 189 # 190 # Hardware Level (one of limited, legacy, full) 191 # 192 d['hwlevel'] = entry.get('hwlevel') 193 194 # 195 # Deprecated ? 196 # 197 d['deprecated'] = entry.get('deprecated') == 'true' 198 199 # 200 # Optional for non-full hardware level devices 201 # 202 d['optional'] = entry.get('optional') == 'true' 203 204 # 205 # Typedef 206 # 207 d['type_name'] = entry.get('typedef') 208 209 # 210 # Initial HIDL HAL version the entry was added in 211 d['hal_version'] = entry.get('hal_version') 212 213 # 214 # Enum 215 # 216 if entry.get('enum', 'false') == 'true': 217 218 enum_values = [] 219 enum_deprecateds = [] 220 enum_optionals = [] 221 enum_hiddens = [] 222 enum_ndk_hiddens = [] 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 if value.attrs.get('hidden', 'false') == 'true': 240 enum_hiddens.append(value_body) 241 242 if value.attrs.get('ndk_hidden', 'false') == 'true': 243 enum_ndk_hiddens.append(value_body) 244 245 notes = value.find('notes') 246 if notes is not None: 247 enum_notes[value_body] = notes.string 248 249 sdk_notes = value.find('sdk_notes') 250 if sdk_notes is not None: 251 enum_sdk_notes[value_body] = sdk_notes.string 252 253 ndk_notes = value.find('ndk_notes') 254 if ndk_notes is not None: 255 enum_ndk_notes[value_body] = ndk_notes.string 256 257 if value.attrs.get('id') is not None: 258 enum_ids[value_body] = value['id'] 259 260 if value.attrs.get('hal_version') is not None: 261 enum_hal_versions[value_body] = value['hal_version'] 262 263 d['enum_values'] = enum_values 264 d['enum_deprecateds'] = enum_deprecateds 265 d['enum_optionals'] = enum_optionals 266 d['enum_hiddens'] = enum_hiddens 267 d['enum_ndk_hiddens'] = enum_ndk_hiddens 268 d['enum_notes'] = enum_notes 269 d['enum_sdk_notes'] = enum_sdk_notes 270 d['enum_ndk_notes'] = enum_ndk_notes 271 d['enum_ids'] = enum_ids 272 d['enum_hal_versions'] = enum_hal_versions 273 d['enum'] = True 274 275 # 276 # Container (Array/Tuple) 277 # 278 if entry.attrs.get('container') is not None: 279 container_name = entry['container'] 280 281 array = entry.find('array') 282 if array is not None: 283 array_sizes = [] 284 for size in array.find_all('size'): 285 array_sizes.append(size.string) 286 d['container_sizes'] = array_sizes 287 288 tupl = entry.find('tuple') 289 if tupl is not None: 290 tupl_values = [] 291 for val in tupl.find_all('value'): 292 tupl_values.append(val.name) 293 d['tuple_values'] = tupl_values 294 d['container_sizes'] = len(tupl_values) 295 296 d['container'] = container_name 297 298 return d 299 300 def _parse_entry_optional(self, entry): 301 d = {} 302 303 optional_elements = ['description', 'range', 'units', 'details', 'hal_details', 'ndk_details',\ 304 'deprecation_description'] 305 for i in optional_elements: 306 prop = find_child_tag(entry, i) 307 308 if prop is not None: 309 d[i] = prop.string 310 311 tag_ids = [] 312 for tag in entry.find_all('tag'): 313 tag_ids.append(tag['id']) 314 315 d['tag_ids'] = tag_ids 316 317 return d 318 319 def render(self, template, output_name=None, hal_version="3.2"): 320 """ 321 Render the metadata model using a Mako template as the view. 322 323 The template gets the metadata as an argument, as well as all 324 public attributes from the metadata_helpers module. 325 326 The output file is encoded with UTF-8. 327 328 Args: 329 template: path to a Mako template file 330 output_name: path to the output file, or None to use stdout 331 hal_version: target HAL version, used when generating HIDL HAL outputs. 332 Must be a string of form "X.Y" where X and Y are integers. 333 """ 334 buf = StringIO.StringIO() 335 metadata_helpers._context_buf = buf 336 metadata_helpers._hal_major_version = int(hal_version.partition('.')[0]) 337 metadata_helpers._hal_minor_version = int(hal_version.partition('.')[2]) 338 339 helpers = [(i, getattr(metadata_helpers, i)) 340 for i in dir(metadata_helpers) if not i.startswith('_')] 341 helpers = dict(helpers) 342 343 lookup = TemplateLookup(directories=[os.getcwd()]) 344 tpl = Template(filename=template, lookup=lookup) 345 346 ctx = Context(buf, metadata=self.metadata, **helpers) 347 tpl.render_context(ctx) 348 349 tpl_data = buf.getvalue() 350 metadata_helpers._context_buf = None 351 buf.close() 352 353 if output_name is None: 354 print tpl_data 355 else: 356 file(output_name, "w").write(tpl_data.encode('utf-8')) 357 358##################### 359##################### 360 361if __name__ == "__main__": 362 if len(sys.argv) <= 2: 363 print >> sys.stderr, \ 364 "Usage: %s <filename.xml> <template.mako> [<output_file>] [<hal_version>]" \ 365 % (sys.argv[0]) 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