metadata_parser_xml.py revision 63c0fb27d923a32d9a398471ad318bfe84befbeb
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_properties.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 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 # Optional for non-full hardware level devices 179 # 180 d['optional'] = entry.get('optional') == 'true' 181 182 # 183 # Typedef 184 # 185 d['type_name'] = entry.get('typedef') 186 187 # 188 # Enum 189 # 190 if entry.get('enum', 'false') == 'true': 191 192 enum_values = [] 193 enum_optionals = [] 194 enum_notes = {} 195 enum_ids = {} 196 for value in entry.enum.find_all('value'): 197 198 value_body = self._strings_no_nl(value) 199 enum_values.append(value_body) 200 201 if value.attrs.get('optional', 'false') == 'true': 202 enum_optionals.append(value_body) 203 204 notes = value.find('notes') 205 if notes is not None: 206 enum_notes[value_body] = notes.string 207 208 if value.attrs.get('id') is not None: 209 enum_ids[value_body] = value['id'] 210 211 d['enum_values'] = enum_values 212 d['enum_optionals'] = enum_optionals 213 d['enum_notes'] = enum_notes 214 d['enum_ids'] = enum_ids 215 d['enum'] = True 216 217 # 218 # Container (Array/Tuple) 219 # 220 if entry.attrs.get('container') is not None: 221 container_name = entry['container'] 222 223 array = entry.find('array') 224 if array is not None: 225 array_sizes = [] 226 for size in array.find_all('size'): 227 array_sizes.append(size.string) 228 d['container_sizes'] = array_sizes 229 230 tupl = entry.find('tuple') 231 if tupl is not None: 232 tupl_values = [] 233 for val in tupl.find_all('value'): 234 tupl_values.append(val.name) 235 d['tuple_values'] = tupl_values 236 d['container_sizes'] = len(tupl_values) 237 238 d['container'] = container_name 239 240 return d 241 242 def _parse_entry_optional(self, entry): 243 d = {} 244 245 optional_elements = ['description', 'range', 'units', 'details', 'hal_details'] 246 for i in optional_elements: 247 prop = find_child_tag(entry, i) 248 249 if prop is not None: 250 d[i] = prop.string 251 252 tag_ids = [] 253 for tag in entry.find_all('tag'): 254 tag_ids.append(tag['id']) 255 256 d['tag_ids'] = tag_ids 257 258 return d 259 260 def render(self, template, output_name=None): 261 """ 262 Render the metadata model using a Mako template as the view. 263 264 The template gets the metadata as an argument, as well as all 265 public attributes from the metadata_helpers module. 266 267 The output file is encoded with UTF-8. 268 269 Args: 270 template: path to a Mako template file 271 output_name: path to the output file, or None to use stdout 272 """ 273 buf = StringIO.StringIO() 274 metadata_helpers._context_buf = buf 275 276 helpers = [(i, getattr(metadata_helpers, i)) 277 for i in dir(metadata_helpers) if not i.startswith('_')] 278 helpers = dict(helpers) 279 280 lookup = TemplateLookup(directories=[os.getcwd()]) 281 tpl = Template(filename=template, lookup=lookup) 282 283 ctx = Context(buf, metadata=self.metadata, **helpers) 284 tpl.render_context(ctx) 285 286 tpl_data = buf.getvalue() 287 metadata_helpers._context_buf = None 288 buf.close() 289 290 if output_name is None: 291 print tpl_data 292 else: 293 file(output_name, "w").write(tpl_data.encode('utf-8')) 294 295##################### 296##################### 297 298if __name__ == "__main__": 299 if len(sys.argv) <= 2: 300 print >> sys.stderr, \ 301 "Usage: %s <filename.xml> <template.mako> [<output_file>]" \ 302 % (sys.argv[0]) 303 sys.exit(0) 304 305 file_name = sys.argv[1] 306 template_name = sys.argv[2] 307 output_name = sys.argv[3] if len(sys.argv) > 3 else None 308 parser = MetadataParserXml.create_from_file(file_name) 309 parser.render(template_name, output_name) 310 311 sys.exit(0) 312