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 file 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, file_name): 65 """ 66 Construct a new MetadataParserXml, immediately try to parse it into a 67 metadata model. 68 69 Args: 70 file_name: path to an XML file that passes metadata-validate 71 72 Raises: 73 ValueError: if the XML file failed to pass metadata_validate.py 74 """ 75 self._soup = validate_xml(file_name) 76 77 if self._soup is None: 78 raise ValueError("%s has an invalid XML file" %(file_name)) 79 80 self._metadata = Metadata() 81 self._parse() 82 self._metadata.construct_graph() 83 84 @property 85 def soup(self): 86 return self._soup 87 88 @property 89 def metadata(self): 90 return self._metadata 91 92 @staticmethod 93 def _find_direct_strings(element): 94 if element.string is not None: 95 return [element.string] 96 97 return [i for i in element.contents if isinstance(i, NavigableString)] 98 99 @staticmethod 100 def _strings_no_nl(element): 101 return "".join([i.strip() for i in MetadataParserXml._find_direct_strings(element)]) 102 103 def _parse(self): 104 105 tags = self.soup.tags 106 if tags is not None: 107 for tag in tags.find_all('tag'): 108 self.metadata.insert_tag(tag['id'], tag.string) 109 110 # add all entries, preserving the ordering of the XML file 111 # this is important for future ABI compatibility when generating code 112 entry_filter = lambda x: x.name == 'entry' or x.name == 'clone' 113 for entry in self.soup.find_all(entry_filter): 114 if entry.name == 'entry': 115 d = { 116 'name': fully_qualified_name(entry), 117 'type': entry['type'], 118 'kind': find_kind(entry), 119 'type_notes': entry.attrs.get('type_notes') 120 } 121 122 d2 = self._parse_entry(entry) 123 insert = self.metadata.insert_entry 124 else: 125 d = { 126 'name': entry['entry'], 127 'kind': find_kind(entry), 128 'target_kind': entry['kind'], 129 # no type since its the same 130 # no type_notes since its the same 131 } 132 d2 = {} 133 134 insert = self.metadata.insert_clone 135 136 d3 = self._parse_entry_optional(entry) 137 138 entry_dict = dict(d.items() + d2.items() + d3.items()) 139 insert(entry_dict) 140 141 self.metadata.construct_graph() 142 143 def _parse_entry(self, entry): 144 d = {} 145 146 # 147 # Enum 148 # 149 if entry.get('enum', 'false') == 'true': 150 151 enum_values = [] 152 enum_optionals = [] 153 enum_notes = {} 154 enum_ids = {} 155 for value in entry.enum.find_all('value'): 156 157 value_body = self._strings_no_nl(value) 158 enum_values.append(value_body) 159 160 if value.attrs.get('optional', 'false') == 'true': 161 enum_optionals.append(value_body) 162 163 notes = value.find('notes') 164 if notes is not None: 165 enum_notes[value_body] = notes.string 166 167 if value.attrs.get('id') is not None: 168 enum_ids[value_body] = value['id'] 169 170 d['enum_values'] = enum_values 171 d['enum_optionals'] = enum_optionals 172 d['enum_notes'] = enum_notes 173 d['enum_ids'] = enum_ids 174 d['enum'] = True 175 176 # 177 # Container (Array/Tuple) 178 # 179 if entry.attrs.get('container') is not None: 180 container_name = entry['container'] 181 182 array = entry.find('array') 183 if array is not None: 184 array_sizes = [] 185 for size in array.find_all('size'): 186 array_sizes.append(size.string) 187 d['container_sizes'] = array_sizes 188 189 tupl = entry.find('tuple') 190 if tupl is not None: 191 tupl_values = [] 192 for val in tupl.find_all('value'): 193 tupl_values.append(val.name) 194 d['tuple_values'] = tupl_values 195 d['container_sizes'] = len(tupl_values) 196 197 d['container'] = container_name 198 199 return d 200 201 def _parse_entry_optional(self, entry): 202 d = {} 203 204 optional_elements = ['description', 'range', 'units', 'notes'] 205 for i in optional_elements: 206 prop = find_child_tag(entry, i) 207 208 if prop is not None: 209 d[i] = prop.string 210 211 tag_ids = [] 212 for tag in entry.find_all('tag'): 213 tag_ids.append(tag['id']) 214 215 d['tag_ids'] = tag_ids 216 217 return d 218 219 def render(self, template, output_name=None): 220 """ 221 Render the metadata model using a Mako template as the view. 222 223 The template gets the metadata as an argument, as well as all 224 public attributes from the metadata_helpers module. 225 226 Args: 227 template: path to a Mako template file 228 output_name: path to the output file, or None to use stdout 229 """ 230 buf = StringIO.StringIO() 231 metadata_helpers._context_buf = buf 232 233 helpers = [(i, getattr(metadata_helpers, i)) 234 for i in dir(metadata_helpers) if not i.startswith('_')] 235 helpers = dict(helpers) 236 237 lookup = TemplateLookup(directories=[os.getcwd()]) 238 tpl = Template(filename=template, lookup=lookup) 239 240 ctx = Context(buf, metadata=self.metadata, **helpers) 241 tpl.render_context(ctx) 242 243 tpl_data = buf.getvalue() 244 metadata_helpers._context_buf = None 245 buf.close() 246 247 if output_name is None: 248 print tpl_data 249 else: 250 file(output_name, "w").write(tpl_data) 251 252##################### 253##################### 254 255if __name__ == "__main__": 256 if len(sys.argv) <= 2: 257 print >> sys.stderr, \ 258 "Usage: %s <filename.xml> <template.mako> [<output_file>]" \ 259 % (sys.argv[0]) 260 sys.exit(0) 261 262 file_name = sys.argv[1] 263 template_name = sys.argv[2] 264 output_name = sys.argv[3] if len(sys.argv) > 3 else None 265 parser = MetadataParserXml(file_name) 266 parser.render(template_name, output_name) 267 268 sys.exit(0) 269