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 """ 20 A parser for metadata_properties.xml can also render the resulting model 21 over a Mako template. 22 23 Usage: 24 metadata_parser_xml.py <filename.xml> <template.mako> [<output_file>] 25 - outputs the resulting template to output_file (stdout if none specified) 26 27 Module: 28 The parser is also available as a module import (MetadataParserXml) to use 29 in other modules. 30 31 Dependencies: 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 38 import sys 39 import os 40 import StringIO 41 42 from bs4 import BeautifulSoup 43 from bs4 import NavigableString 44 45 from mako.template import Template 46 from mako.lookup import TemplateLookup 47 from mako.runtime import Context 48 49 from metadata_model import * 50 import metadata_model 51 from metadata_validate import * 52 import metadata_helpers 53 54 class 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 160 insert = self.metadata.insert_clone 161 162 d3 = self._parse_entry_optional(entry) 163 164 entry_dict = dict(d.items() + d2.items() + d3.items()) 165 insert(entry_dict) 166 167 self.metadata.construct_graph() 168 169 def _parse_entry(self, entry): 170 d = {} 171 172 # 173 # Visibility 174 # 175 d['visibility'] = entry.get('visibility') 176 177 # 178 # Synthetic ? 179 # 180 d['synthetic'] = entry.get('synthetic') == 'true' 181 182 # 183 # Hardware Level (one of limited, legacy, full) 184 # 185 d['hwlevel'] = entry.get('hwlevel') 186 187 # 188 # Deprecated ? 189 # 190 d['deprecated'] = entry.get('deprecated') == 'true' 191 192 # 193 # Optional for non-full hardware level devices 194 # 195 d['optional'] = entry.get('optional') == 'true' 196 197 # 198 # Typedef 199 # 200 d['type_name'] = entry.get('typedef') 201 202 # 203 # Enum 204 # 205 if entry.get('enum', 'false') == 'true': 206 207 enum_values = [] 208 enum_deprecateds = [] 209 enum_optionals = [] 210 enum_hiddens = [] 211 enum_ndk_hiddens = [] 212 enum_notes = {} 213 enum_ids = {} 214 for value in entry.enum.find_all('value'): 215 216 value_body = self._strings_no_nl(value) 217 enum_values.append(value_body) 218 219 if value.attrs.get('deprecated', 'false') == 'true': 220 enum_deprecateds.append(value_body) 221 222 if value.attrs.get('optional', 'false') == 'true': 223 enum_optionals.append(value_body) 224 225 if value.attrs.get('hidden', 'false') == 'true': 226 enum_hiddens.append(value_body) 227 228 if value.attrs.get('ndk_hidden', 'false') == 'true': 229 enum_ndk_hiddens.append(value_body) 230 231 notes = value.find('notes') 232 if notes is not None: 233 enum_notes[value_body] = notes.string 234 235 if value.attrs.get('id') is not None: 236 enum_ids[value_body] = value['id'] 237 238 d['enum_values'] = enum_values 239 d['enum_deprecateds'] = enum_deprecateds 240 d['enum_optionals'] = enum_optionals 241 d['enum_hiddens'] = enum_hiddens 242 d['enum_ndk_hiddens'] = enum_ndk_hiddens 243 d['enum_notes'] = enum_notes 244 d['enum_ids'] = enum_ids 245 d['enum'] = True 246 247 # 248 # Container (Array/Tuple) 249 # 250 if entry.attrs.get('container') is not None: 251 container_name = entry['container'] 252 253 array = entry.find('array') 254 if array is not None: 255 array_sizes = [] 256 for size in array.find_all('size'): 257 array_sizes.append(size.string) 258 d['container_sizes'] = array_sizes 259 260 tupl = entry.find('tuple') 261 if tupl is not None: 262 tupl_values = [] 263 for val in tupl.find_all('value'): 264 tupl_values.append(val.name) 265 d['tuple_values'] = tupl_values 266 d['container_sizes'] = len(tupl_values) 267 268 d['container'] = container_name 269 270 return d 271 272 def _parse_entry_optional(self, entry): 273 d = {} 274 275 optional_elements = ['description', 'range', 'units', 'details', 'hal_details'] 276 for i in optional_elements: 277 prop = find_child_tag(entry, i) 278 279 if prop is not None: 280 d[i] = prop.string 281 282 tag_ids = [] 283 for tag in entry.find_all('tag'): 284 tag_ids.append(tag['id']) 285 286 d['tag_ids'] = tag_ids 287 288 return d 289 290 def render(self, template, output_name=None): 291 """ 292 Render the metadata model using a Mako template as the view. 293 294 The template gets the metadata as an argument, as well as all 295 public attributes from the metadata_helpers module. 296 297 The output file is encoded with UTF-8. 298 299 Args: 300 template: path to a Mako template file 301 output_name: path to the output file, or None to use stdout 302 """ 303 buf = StringIO.StringIO() 304 metadata_helpers._context_buf = buf 305 306 helpers = [(i, getattr(metadata_helpers, i)) 307 for i in dir(metadata_helpers) if not i.startswith('_')] 308 helpers = dict(helpers) 309 310 lookup = TemplateLookup(directories=[os.getcwd()]) 311 tpl = Template(filename=template, lookup=lookup) 312 313 ctx = Context(buf, metadata=self.metadata, **helpers) 314 tpl.render_context(ctx) 315 316 tpl_data = buf.getvalue() 317 metadata_helpers._context_buf = None 318 buf.close() 319 320 if output_name is None: 321 print tpl_data 322 else: 323 file(output_name, "w").write(tpl_data.encode('utf-8')) 324 325 ##################### 326 ##################### 327 328 if __name__ == "__main__": 329 if len(sys.argv) <= 2: 330 print >> sys.stderr, \ 331 "Usage: %s <filename.xml> <template.mako> [<output_file>]" \ 332 % (sys.argv[0]) 333 sys.exit(0) 334 335 file_name = sys.argv[1] 336 template_name = sys.argv[2] 337 output_name = sys.argv[3] if len(sys.argv) > 3 else None 338 parser = MetadataParserXml.create_from_file(file_name) 339 parser.render(template_name, output_name) 340 341 sys.exit(0) 342