blob: f0ed12e1fb08643609c2595064ea4d068162bb9c [file] [log] [blame]
Igor Murashkin96bd0192012-11-19 16:49:37 -08001#!/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:
Igor Murashkin48098682012-12-05 14:51:57 -080024 metadata_parser_xml.py <filename.xml> <template.mako> [<output_file>]
25 - outputs the resulting template to output_file (stdout if none specified)
Igor Murashkin96bd0192012-11-19 16:49:37 -080026
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
Igor Murashkinda1c3142012-11-21 17:11:37 -080039import os
40import StringIO
Igor Murashkin96bd0192012-11-19 16:49:37 -080041
42from bs4 import BeautifulSoup
43from bs4 import NavigableString
44
45from mako.template import Template
Igor Murashkinda1c3142012-11-21 17:11:37 -080046from mako.lookup import TemplateLookup
47from mako.runtime import Context
Igor Murashkin96bd0192012-11-19 16:49:37 -080048
49from metadata_model import *
Igor Murashkin617da162012-11-29 13:35:15 -080050import metadata_model
Igor Murashkin96bd0192012-11-19 16:49:37 -080051from metadata_validate import *
Igor Murashkinda1c3142012-11-21 17:11:37 -080052import metadata_helpers
Igor Murashkin96bd0192012-11-19 16:49:37 -080053
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
Igor Murashkinb8dc8812013-07-17 16:29:34 -0700110 types = self.soup.types
111 if types is not None:
112 for tp in types.find_all('typedef'):
113 languages = {}
114 for lang in tp.find_all('language'):
115 languages[lang['name']] = lang.string
116
117 self.metadata.insert_type(tp['name'], 'typedef', languages=languages)
118
Igor Murashkin586c8612012-11-29 17:08:36 -0800119 # add all entries, preserving the ordering of the XML file
120 # this is important for future ABI compatibility when generating code
121 entry_filter = lambda x: x.name == 'entry' or x.name == 'clone'
122 for entry in self.soup.find_all(entry_filter):
123 if entry.name == 'entry':
124 d = {
125 'name': fully_qualified_name(entry),
126 'type': entry['type'],
127 'kind': find_kind(entry),
128 'type_notes': entry.attrs.get('type_notes')
129 }
Igor Murashkin96bd0192012-11-19 16:49:37 -0800130
Igor Murashkin586c8612012-11-29 17:08:36 -0800131 d2 = self._parse_entry(entry)
132 insert = self.metadata.insert_entry
133 else:
134 d = {
135 'name': entry['entry'],
136 'kind': find_kind(entry),
137 'target_kind': entry['kind'],
138 # no type since its the same
139 # no type_notes since its the same
140 }
141 d2 = {}
142
143 insert = self.metadata.insert_clone
144
Igor Murashkin96bd0192012-11-19 16:49:37 -0800145 d3 = self._parse_entry_optional(entry)
146
147 entry_dict = dict(d.items() + d2.items() + d3.items())
Igor Murashkin586c8612012-11-29 17:08:36 -0800148 insert(entry_dict)
Igor Murashkin96bd0192012-11-19 16:49:37 -0800149
150 self.metadata.construct_graph()
151
152 def _parse_entry(self, entry):
153 d = {}
154
155 #
Eino-Ville Talvalaf384f0a2013-07-12 17:02:27 -0700156 # Visibility
157 #
158 d['visibility'] = entry.get('visibility')
159
160 #
Igor Murashkinb8dc8812013-07-17 16:29:34 -0700161 # Typedef
162 #
163 d['type_name'] = entry.get('typedef')
164
165 #
Igor Murashkin96bd0192012-11-19 16:49:37 -0800166 # Enum
167 #
Igor Murashkinb556bc42012-12-04 16:07:21 -0800168 if entry.get('enum', 'false') == 'true':
Igor Murashkin96bd0192012-11-19 16:49:37 -0800169
170 enum_values = []
171 enum_optionals = []
172 enum_notes = {}
173 enum_ids = {}
174 for value in entry.enum.find_all('value'):
175
176 value_body = self._strings_no_nl(value)
177 enum_values.append(value_body)
178
179 if value.attrs.get('optional', 'false') == 'true':
180 enum_optionals.append(value_body)
181
182 notes = value.find('notes')
183 if notes is not None:
184 enum_notes[value_body] = notes.string
185
186 if value.attrs.get('id') is not None:
187 enum_ids[value_body] = value['id']
188
189 d['enum_values'] = enum_values
190 d['enum_optionals'] = enum_optionals
191 d['enum_notes'] = enum_notes
192 d['enum_ids'] = enum_ids
Igor Murashkinb556bc42012-12-04 16:07:21 -0800193 d['enum'] = True
Igor Murashkin96bd0192012-11-19 16:49:37 -0800194
195 #
196 # Container (Array/Tuple)
197 #
198 if entry.attrs.get('container') is not None:
199 container_name = entry['container']
200
201 array = entry.find('array')
202 if array is not None:
203 array_sizes = []
204 for size in array.find_all('size'):
205 array_sizes.append(size.string)
206 d['container_sizes'] = array_sizes
207
208 tupl = entry.find('tuple')
209 if tupl is not None:
210 tupl_values = []
211 for val in tupl.find_all('value'):
212 tupl_values.append(val.name)
213 d['tuple_values'] = tupl_values
214 d['container_sizes'] = len(tupl_values)
215
216 d['container'] = container_name
217
218 return d
219
220 def _parse_entry_optional(self, entry):
221 d = {}
222
223 optional_elements = ['description', 'range', 'units', 'notes']
224 for i in optional_elements:
225 prop = find_child_tag(entry, i)
226
227 if prop is not None:
228 d[i] = prop.string
229
230 tag_ids = []
231 for tag in entry.find_all('tag'):
232 tag_ids.append(tag['id'])
233
234 d['tag_ids'] = tag_ids
235
236 return d
237
238 def render(self, template, output_name=None):
239 """
240 Render the metadata model using a Mako template as the view.
241
Igor Murashkinda1c3142012-11-21 17:11:37 -0800242 The template gets the metadata as an argument, as well as all
243 public attributes from the metadata_helpers module.
244
Igor Murashkin96bd0192012-11-19 16:49:37 -0800245 Args:
246 template: path to a Mako template file
247 output_name: path to the output file, or None to use stdout
248 """
Igor Murashkinda1c3142012-11-21 17:11:37 -0800249 buf = StringIO.StringIO()
250 metadata_helpers._context_buf = buf
251
252 helpers = [(i, getattr(metadata_helpers, i))
253 for i in dir(metadata_helpers) if not i.startswith('_')]
254 helpers = dict(helpers)
255
256 lookup = TemplateLookup(directories=[os.getcwd()])
257 tpl = Template(filename=template, lookup=lookup)
258
259 ctx = Context(buf, metadata=self.metadata, **helpers)
260 tpl.render_context(ctx)
261
262 tpl_data = buf.getvalue()
263 metadata_helpers._context_buf = None
264 buf.close()
Igor Murashkin96bd0192012-11-19 16:49:37 -0800265
266 if output_name is None:
267 print tpl_data
268 else:
269 file(output_name, "w").write(tpl_data)
270
271#####################
272#####################
273
274if __name__ == "__main__":
Igor Murashkin48098682012-12-05 14:51:57 -0800275 if len(sys.argv) <= 2:
276 print >> sys.stderr, \
277 "Usage: %s <filename.xml> <template.mako> [<output_file>]" \
278 % (sys.argv[0])
Igor Murashkin96bd0192012-11-19 16:49:37 -0800279 sys.exit(0)
280
281 file_name = sys.argv[1]
Igor Murashkin617da162012-11-29 13:35:15 -0800282 template_name = sys.argv[2]
Igor Murashkin48098682012-12-05 14:51:57 -0800283 output_name = sys.argv[3] if len(sys.argv) > 3 else None
Igor Murashkin96bd0192012-11-19 16:49:37 -0800284 parser = MetadataParserXml(file_name)
Igor Murashkin48098682012-12-05 14:51:57 -0800285 parser.render(template_name, output_name)
Igor Murashkin96bd0192012-11-19 16:49:37 -0800286
287 sys.exit(0)