blob: cadbac1d8d49d1d168d875443d4348891c1948ba [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 Murashkin617da162012-11-29 13:35:15 -080024 metadata_parser_xml.py <filename.xml> <template.mako>
Igor Murashkin96bd0192012-11-19 16:49:37 -080025 - outputs the resulting template to stdout
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
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 Murashkin586c8612012-11-29 17:08:36 -0800110 # 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 }
Igor Murashkin96bd0192012-11-19 16:49:37 -0800121
Igor Murashkin586c8612012-11-29 17:08:36 -0800122 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
Igor Murashkin96bd0192012-11-19 16:49:37 -0800136 d3 = self._parse_entry_optional(entry)
137
138 entry_dict = dict(d.items() + d2.items() + d3.items())
Igor Murashkin586c8612012-11-29 17:08:36 -0800139 insert(entry_dict)
Igor Murashkin96bd0192012-11-19 16:49:37 -0800140
141 self.metadata.construct_graph()
142
143 def _parse_entry(self, entry):
144 d = {}
145
146 #
147 # Enum
148 #
149 if entry['type'] == 'enum':
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
175 #
176 # Container (Array/Tuple)
177 #
178 if entry.attrs.get('container') is not None:
179 container_name = entry['container']
180
181 array = entry.find('array')
182 if array is not None:
183 array_sizes = []
184 for size in array.find_all('size'):
185 array_sizes.append(size.string)
186 d['container_sizes'] = array_sizes
187
188 tupl = entry.find('tuple')
189 if tupl is not None:
190 tupl_values = []
191 for val in tupl.find_all('value'):
192 tupl_values.append(val.name)
193 d['tuple_values'] = tupl_values
194 d['container_sizes'] = len(tupl_values)
195
196 d['container'] = container_name
197
198 return d
199
200 def _parse_entry_optional(self, entry):
201 d = {}
202
203 optional_elements = ['description', 'range', 'units', 'notes']
204 for i in optional_elements:
205 prop = find_child_tag(entry, i)
206
207 if prop is not None:
208 d[i] = prop.string
209
210 tag_ids = []
211 for tag in entry.find_all('tag'):
212 tag_ids.append(tag['id'])
213
214 d['tag_ids'] = tag_ids
215
216 return d
217
218 def render(self, template, output_name=None):
219 """
220 Render the metadata model using a Mako template as the view.
221
Igor Murashkinda1c3142012-11-21 17:11:37 -0800222 The template gets the metadata as an argument, as well as all
223 public attributes from the metadata_helpers module.
224
Igor Murashkin96bd0192012-11-19 16:49:37 -0800225 Args:
226 template: path to a Mako template file
227 output_name: path to the output file, or None to use stdout
228 """
Igor Murashkinda1c3142012-11-21 17:11:37 -0800229 buf = StringIO.StringIO()
230 metadata_helpers._context_buf = buf
231
232 helpers = [(i, getattr(metadata_helpers, i))
233 for i in dir(metadata_helpers) if not i.startswith('_')]
234 helpers = dict(helpers)
235
236 lookup = TemplateLookup(directories=[os.getcwd()])
237 tpl = Template(filename=template, lookup=lookup)
238
239 ctx = Context(buf, metadata=self.metadata, **helpers)
240 tpl.render_context(ctx)
241
242 tpl_data = buf.getvalue()
243 metadata_helpers._context_buf = None
244 buf.close()
Igor Murashkin96bd0192012-11-19 16:49:37 -0800245
246 if output_name is None:
247 print tpl_data
248 else:
249 file(output_name, "w").write(tpl_data)
250
251#####################
252#####################
253
254if __name__ == "__main__":
255 if len(sys.argv) <= 1:
Igor Murashkin617da162012-11-29 13:35:15 -0800256 print >> sys.stderr, "Usage: %s <filename.xml> <template.mako>" \
257 % (sys.argv[0])
Igor Murashkin96bd0192012-11-19 16:49:37 -0800258 sys.exit(0)
259
260 file_name = sys.argv[1]
Igor Murashkin617da162012-11-29 13:35:15 -0800261 template_name = sys.argv[2]
Igor Murashkin96bd0192012-11-19 16:49:37 -0800262 parser = MetadataParserXml(file_name)
Igor Murashkin617da162012-11-29 13:35:15 -0800263 parser.render(template_name)
Igor Murashkin96bd0192012-11-19 16:49:37 -0800264
265 sys.exit(0)