blob: 05953d4f02c8ff7fdf2178d11301c904e6f95f92 [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
39
40from bs4 import BeautifulSoup
41from bs4 import NavigableString
42
43from mako.template import Template
44
45from metadata_model import *
Igor Murashkin617da162012-11-29 13:35:15 -080046import metadata_model
Igor Murashkin96bd0192012-11-19 16:49:37 -080047from metadata_validate import *
48
49class MetadataParserXml:
50 """
51 A class to parse any XML file that passes validation with metadata-validate.
52 It builds a metadata_model.Metadata graph and then renders it over a
53 Mako template.
54
55 Attributes (Read-Only):
56 soup: an instance of BeautifulSoup corresponding to the XML contents
57 metadata: a constructed instance of metadata_model.Metadata
58 """
59 def __init__(self, file_name):
60 """
61 Construct a new MetadataParserXml, immediately try to parse it into a
62 metadata model.
63
64 Args:
65 file_name: path to an XML file that passes metadata-validate
66
67 Raises:
68 ValueError: if the XML file failed to pass metadata_validate.py
69 """
70 self._soup = validate_xml(file_name)
71
72 if self._soup is None:
73 raise ValueError("%s has an invalid XML file" %(file_name))
74
75 self._metadata = Metadata()
76 self._parse()
77 self._metadata.construct_graph()
78
79 @property
80 def soup(self):
81 return self._soup
82
83 @property
84 def metadata(self):
85 return self._metadata
86
87 @staticmethod
88 def _find_direct_strings(element):
89 if element.string is not None:
90 return [element.string]
91
92 return [i for i in element.contents if isinstance(i, NavigableString)]
93
94 @staticmethod
95 def _strings_no_nl(element):
96 return "".join([i.strip() for i in MetadataParserXml._find_direct_strings(element)])
97
98 def _parse(self):
99
100 tags = self.soup.tags
101 if tags is not None:
102 for tag in tags.find_all('tag'):
103 self.metadata.insert_tag(tag['id'], tag.string)
104
105 for entry in self.soup.find_all("entry"):
106 d = {
107 'name': fully_qualified_name(entry),
108 'type': entry['type'],
109 'kind': find_kind(entry),
110 'type_notes': entry.attrs.get('type_notes')
111 }
112
113 d2 = self._parse_entry(entry)
114 d3 = self._parse_entry_optional(entry)
115
116 entry_dict = dict(d.items() + d2.items() + d3.items())
117 self.metadata.insert_entry(entry_dict)
118
119 entry = None
120
121 for clone in self.soup.find_all("clone"):
122 d = {
123 'name': clone['entry'],
124 'kind': find_kind(clone),
125 'target_kind': clone['kind'],
126 # no type since its the same
127 # no type_notes since its the same
128 }
129
130 d2 = self._parse_entry_optional(clone)
131 clone_dict = dict(d.items() + d2.items())
132 self.metadata.insert_clone(clone_dict)
133
134 self.metadata.construct_graph()
135
136 def _parse_entry(self, entry):
137 d = {}
138
139 #
140 # Enum
141 #
142 if entry['type'] == 'enum':
143
144 enum_values = []
145 enum_optionals = []
146 enum_notes = {}
147 enum_ids = {}
148 for value in entry.enum.find_all('value'):
149
150 value_body = self._strings_no_nl(value)
151 enum_values.append(value_body)
152
153 if value.attrs.get('optional', 'false') == 'true':
154 enum_optionals.append(value_body)
155
156 notes = value.find('notes')
157 if notes is not None:
158 enum_notes[value_body] = notes.string
159
160 if value.attrs.get('id') is not None:
161 enum_ids[value_body] = value['id']
162
163 d['enum_values'] = enum_values
164 d['enum_optionals'] = enum_optionals
165 d['enum_notes'] = enum_notes
166 d['enum_ids'] = enum_ids
167
168 #
169 # Container (Array/Tuple)
170 #
171 if entry.attrs.get('container') is not None:
172 container_name = entry['container']
173
174 array = entry.find('array')
175 if array is not None:
176 array_sizes = []
177 for size in array.find_all('size'):
178 array_sizes.append(size.string)
179 d['container_sizes'] = array_sizes
180
181 tupl = entry.find('tuple')
182 if tupl is not None:
183 tupl_values = []
184 for val in tupl.find_all('value'):
185 tupl_values.append(val.name)
186 d['tuple_values'] = tupl_values
187 d['container_sizes'] = len(tupl_values)
188
189 d['container'] = container_name
190
191 return d
192
193 def _parse_entry_optional(self, entry):
194 d = {}
195
196 optional_elements = ['description', 'range', 'units', 'notes']
197 for i in optional_elements:
198 prop = find_child_tag(entry, i)
199
200 if prop is not None:
201 d[i] = prop.string
202
203 tag_ids = []
204 for tag in entry.find_all('tag'):
205 tag_ids.append(tag['id'])
206
207 d['tag_ids'] = tag_ids
208
209 return d
210
211 def render(self, template, output_name=None):
212 """
213 Render the metadata model using a Mako template as the view.
214
215 Args:
216 template: path to a Mako template file
217 output_name: path to the output file, or None to use stdout
218 """
219 tpl = Template(filename=template)
Igor Murashkin617da162012-11-29 13:35:15 -0800220 tpl_data = tpl.render(metadata=self.metadata, metadata_model=metadata_model)
Igor Murashkin96bd0192012-11-19 16:49:37 -0800221
222 if output_name is None:
223 print tpl_data
224 else:
225 file(output_name, "w").write(tpl_data)
226
227#####################
228#####################
229
230if __name__ == "__main__":
231 if len(sys.argv) <= 1:
Igor Murashkin617da162012-11-29 13:35:15 -0800232 print >> sys.stderr, "Usage: %s <filename.xml> <template.mako>" \
233 % (sys.argv[0])
Igor Murashkin96bd0192012-11-19 16:49:37 -0800234 sys.exit(0)
235
236 file_name = sys.argv[1]
Igor Murashkin617da162012-11-29 13:35:15 -0800237 template_name = sys.argv[2]
Igor Murashkin96bd0192012-11-19 16:49:37 -0800238 parser = MetadataParserXml(file_name)
Igor Murashkin617da162012-11-29 13:35:15 -0800239 parser.render(template_name)
Igor Murashkin96bd0192012-11-19 16:49:37 -0800240
241 sys.exit(0)