• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_definitions.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        if 'hal_version' in entry.attrs:
160          d2['hal_version'] = entry['hal_version']
161
162        insert = self.metadata.insert_clone
163
164      d3 = self._parse_entry_optional(entry)
165
166      entry_dict = dict(d.items() + d2.items() + d3.items())
167      insert(entry_dict)
168
169    self.metadata.construct_graph()
170
171  def _parse_entry(self, entry):
172    d = {}
173
174    #
175    # Visibility
176    #
177    d['visibility'] = entry.get('visibility')
178
179    #
180    # Synthetic ?
181    #
182    d['synthetic'] = entry.get('synthetic') == 'true'
183
184    #
185    # Hardware Level (one of limited, legacy, full)
186    #
187    d['hwlevel'] = entry.get('hwlevel')
188
189    #
190    # Deprecated ?
191    #
192    d['deprecated'] = entry.get('deprecated') == 'true'
193
194    #
195    # Optional for non-full hardware level devices
196    #
197    d['optional'] = entry.get('optional') == 'true'
198
199    #
200    # Typedef
201    #
202    d['type_name'] = entry.get('typedef')
203
204    #
205    # Initial HIDL HAL version the entry was added in
206    d['hal_version'] = entry.get('hal_version')
207
208    #
209    # Enum
210    #
211    if entry.get('enum', 'false') == 'true':
212
213      enum_values = []
214      enum_deprecateds = []
215      enum_optionals = []
216      enum_hiddens = []
217      enum_ndk_hiddens = []
218      enum_notes = {}
219      enum_sdk_notes = {}
220      enum_ndk_notes = {}
221      enum_ids = {}
222      enum_hal_versions = {}
223      for value in entry.enum.find_all('value'):
224
225        value_body = self._strings_no_nl(value)
226        enum_values.append(value_body)
227
228        if value.attrs.get('deprecated', 'false') == 'true':
229          enum_deprecateds.append(value_body)
230
231        if value.attrs.get('optional', 'false') == 'true':
232          enum_optionals.append(value_body)
233
234        if value.attrs.get('hidden', 'false') == 'true':
235          enum_hiddens.append(value_body)
236
237        if value.attrs.get('ndk_hidden', 'false') == 'true':
238          enum_ndk_hiddens.append(value_body)
239
240        notes = value.find('notes')
241        if notes is not None:
242          enum_notes[value_body] = notes.string
243
244        sdk_notes = value.find('sdk_notes')
245        if sdk_notes is not None:
246          enum_sdk_notes[value_body] = sdk_notes.string
247
248        ndk_notes = value.find('ndk_notes')
249        if ndk_notes is not None:
250          enum_ndk_notes[value_body] = ndk_notes.string
251
252        if value.attrs.get('id') is not None:
253          enum_ids[value_body] = value['id']
254
255        if value.attrs.get('hal_version') is not None:
256          enum_hal_versions[value_body] = value['hal_version']
257
258      d['enum_values'] = enum_values
259      d['enum_deprecateds'] = enum_deprecateds
260      d['enum_optionals'] = enum_optionals
261      d['enum_hiddens'] = enum_hiddens
262      d['enum_ndk_hiddens'] = enum_ndk_hiddens
263      d['enum_notes'] = enum_notes
264      d['enum_sdk_notes'] = enum_sdk_notes
265      d['enum_ndk_notes'] = enum_ndk_notes
266      d['enum_ids'] = enum_ids
267      d['enum_hal_versions'] = enum_hal_versions
268      d['enum'] = True
269
270    #
271    # Container (Array/Tuple)
272    #
273    if entry.attrs.get('container') is not None:
274      container_name = entry['container']
275
276      array = entry.find('array')
277      if array is not None:
278        array_sizes = []
279        for size in array.find_all('size'):
280          array_sizes.append(size.string)
281        d['container_sizes'] = array_sizes
282
283      tupl = entry.find('tuple')
284      if tupl is not None:
285        tupl_values = []
286        for val in tupl.find_all('value'):
287          tupl_values.append(val.name)
288        d['tuple_values'] = tupl_values
289        d['container_sizes'] = len(tupl_values)
290
291      d['container'] = container_name
292
293    return d
294
295  def _parse_entry_optional(self, entry):
296    d = {}
297
298    optional_elements = ['description', 'range', 'units', 'details', 'hal_details', 'ndk_details',\
299                         'deprecation_description']
300    for i in optional_elements:
301      prop = find_child_tag(entry, i)
302
303      if prop is not None:
304        d[i] = prop.string
305
306    tag_ids = []
307    for tag in entry.find_all('tag'):
308      tag_ids.append(tag['id'])
309
310    d['tag_ids'] = tag_ids
311
312    return d
313
314  def render(self, template, output_name=None, hal_version="3.2"):
315    """
316    Render the metadata model using a Mako template as the view.
317
318    The template gets the metadata as an argument, as well as all
319    public attributes from the metadata_helpers module.
320
321    The output file is encoded with UTF-8.
322
323    Args:
324      template: path to a Mako template file
325      output_name: path to the output file, or None to use stdout
326      hal_version: target HAL version, used when generating HIDL HAL outputs.
327                   Must be a string of form "X.Y" where X and Y are integers.
328    """
329    buf = StringIO.StringIO()
330    metadata_helpers._context_buf = buf
331    metadata_helpers._hal_major_version = int(hal_version.partition('.')[0])
332    metadata_helpers._hal_minor_version = int(hal_version.partition('.')[2])
333
334    helpers = [(i, getattr(metadata_helpers, i))
335                for i in dir(metadata_helpers) if not i.startswith('_')]
336    helpers = dict(helpers)
337
338    lookup = TemplateLookup(directories=[os.getcwd()])
339    tpl = Template(filename=template, lookup=lookup)
340
341    ctx = Context(buf, metadata=self.metadata, **helpers)
342    tpl.render_context(ctx)
343
344    tpl_data = buf.getvalue()
345    metadata_helpers._context_buf = None
346    buf.close()
347
348    if output_name is None:
349      print tpl_data
350    else:
351      file(output_name, "w").write(tpl_data.encode('utf-8'))
352
353#####################
354#####################
355
356if __name__ == "__main__":
357  if len(sys.argv) <= 2:
358    print >> sys.stderr,                                                       \
359           "Usage: %s <filename.xml> <template.mako> [<output_file>] [<hal_version>]"          \
360           % (sys.argv[0])
361    sys.exit(0)
362
363  file_name = sys.argv[1]
364  template_name = sys.argv[2]
365  output_name = sys.argv[3] if len(sys.argv) > 3 else None
366  hal_version = sys.argv[4] if len(sys.argv) > 4 else "3.2"
367
368  parser = MetadataParserXml.create_from_file(file_name)
369  parser.render(template_name, output_name, hal_version)
370
371  sys.exit(0)
372