• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
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
40
41from bs4 import BeautifulSoup
42from bs4 import NavigableString
43
44from io import StringIO
45
46from mako.template import Template
47from mako.lookup import TemplateLookup
48from mako.runtime import Context
49
50from metadata_model import *
51import metadata_model
52from metadata_validate import *
53import metadata_helpers
54
55class MetadataParserXml:
56  """
57  A class to parse any XML block that passes validation with metadata-validate.
58  It builds a metadata_model.Metadata graph and then renders it over a
59  Mako template.
60
61  Attributes (Read-Only):
62    soup: an instance of BeautifulSoup corresponding to the XML contents
63    metadata: a constructed instance of metadata_model.Metadata
64  """
65  def __init__(self, xml, file_name):
66    """
67    Construct a new MetadataParserXml, immediately try to parse it into a
68    metadata model.
69
70    Args:
71      xml: The XML block to use for the metadata
72      file_name: Source of the XML block, only for debugging/errors
73
74    Raises:
75      ValueError: if the XML block failed to pass metadata_validate.py
76    """
77    self._soup = validate_xml(xml)
78
79    if self._soup is None:
80      raise ValueError("%s has an invalid XML file" % (file_name))
81
82    self._metadata = Metadata()
83    self._parse()
84    self._metadata.construct_graph()
85
86  @staticmethod
87  def create_from_file(file_name):
88    """
89    Construct a new MetadataParserXml by loading and parsing an XML file.
90
91    Args:
92      file_name: Name of the XML file to load and parse.
93
94    Raises:
95      ValueError: if the XML file failed to pass metadata_validate.py
96
97    Returns:
98      MetadataParserXml instance representing the XML file.
99    """
100    return MetadataParserXml(open(file_name).read(), file_name)
101
102  @property
103  def soup(self):
104    return self._soup
105
106  @property
107  def metadata(self):
108    return self._metadata
109
110  @staticmethod
111  def _find_direct_strings(element):
112    if element.string is not None:
113      return [element.string]
114
115    return [i for i in element.contents if isinstance(i, NavigableString)]
116
117  @staticmethod
118  def _strings_no_nl(element):
119    return "".join([i.strip() for i in MetadataParserXml._find_direct_strings(element)])
120
121  def _parse(self):
122
123    tags = self.soup.tags
124    if tags is not None:
125      for tag in tags.find_all('tag'):
126        self.metadata.insert_tag(tag['id'], tag.string)
127
128    types = self.soup.types
129    if types is not None:
130      for tp in types.find_all('typedef'):
131        languages = {}
132        for lang in tp.find_all('language'):
133          languages[lang['name']] = lang.string
134
135        self.metadata.insert_type(tp['name'], 'typedef', languages=languages)
136
137    # add all entries, preserving the ordering of the XML file
138    # this is important for future ABI compatibility when generating code
139    entry_filter = lambda x: x.name == 'entry' or x.name == 'clone'
140    for entry in self.soup.find_all(entry_filter):
141      if entry.name == 'entry':
142        d = {
143              'name': fully_qualified_name(entry),
144              'type': entry['type'],
145              'kind': find_kind(entry),
146              'type_notes': entry.attrs.get('type_notes')
147            }
148
149        d2 = self._parse_entry(entry)
150        insert = self.metadata.insert_entry
151      else:
152        d = {
153           'name': entry['entry'],
154           'kind': find_kind(entry),
155           'target_kind': entry['kind'],
156          # no type since its the same
157          # no type_notes since its the same
158        }
159        d2 = {}
160        if 'hal_version' in entry.attrs:
161          d2['hal_version'] = entry['hal_version']
162
163        insert = self.metadata.insert_clone
164
165      d3 = self._parse_entry_optional(entry)
166
167      entry_dict = {**d, **d2, **d3}
168      insert(entry_dict)
169
170    self.metadata.construct_graph()
171
172  def _parse_entry(self, entry):
173    d = {}
174
175    #
176    # Visibility
177    #
178    d['visibility'] = entry.get('visibility')
179
180    #
181    # Synthetic ?
182    #
183    d['synthetic'] = entry.get('synthetic') == 'true'
184
185    #
186    # Permission needed ?
187    #
188    d['permission_needed'] = entry.get('permission_needed')
189
190    #
191    # Hardware Level (one of limited, legacy, full)
192    #
193    d['hwlevel'] = entry.get('hwlevel')
194
195    #
196    # Deprecated ?
197    #
198    d['deprecated'] = entry.get('deprecated') == 'true'
199
200    #
201    # Optional for non-full hardware level devices
202    #
203    d['optional'] = entry.get('optional') == 'true'
204
205    #
206    # Typedef
207    #
208    d['type_name'] = entry.get('typedef')
209
210    #
211    # Initial HIDL HAL version the entry was added in
212    d['hal_version'] = entry.get('hal_version')
213
214    #
215    # Enum
216    #
217    if entry.get('enum', 'false') == 'true':
218
219      enum_values = []
220      enum_deprecateds = []
221      enum_optionals = []
222      enum_hiddens = []
223      enum_ndk_hiddens = []
224      enum_notes = {}
225      enum_sdk_notes = {}
226      enum_ndk_notes = {}
227      enum_ids = {}
228      enum_hal_versions = {}
229      for value in entry.enum.find_all('value'):
230
231        value_body = self._strings_no_nl(value)
232        enum_values.append(value_body)
233
234        if value.attrs.get('deprecated', 'false') == 'true':
235          enum_deprecateds.append(value_body)
236
237        if value.attrs.get('optional', 'false') == 'true':
238          enum_optionals.append(value_body)
239
240        if value.attrs.get('hidden', 'false') == 'true':
241          enum_hiddens.append(value_body)
242
243        if value.attrs.get('ndk_hidden', 'false') == 'true':
244          enum_ndk_hiddens.append(value_body)
245
246        notes = value.find('notes')
247        if notes is not None:
248          enum_notes[value_body] = notes.string
249
250        sdk_notes = value.find('sdk_notes')
251        if sdk_notes is not None:
252          enum_sdk_notes[value_body] = sdk_notes.string
253
254        ndk_notes = value.find('ndk_notes')
255        if ndk_notes is not None:
256          enum_ndk_notes[value_body] = ndk_notes.string
257
258        if value.attrs.get('id') is not None:
259          enum_ids[value_body] = value['id']
260
261        if value.attrs.get('hal_version') is not None:
262          enum_hal_versions[value_body] = value['hal_version']
263
264      d['enum_values'] = enum_values
265      d['enum_deprecateds'] = enum_deprecateds
266      d['enum_optionals'] = enum_optionals
267      d['enum_hiddens'] = enum_hiddens
268      d['enum_ndk_hiddens'] = enum_ndk_hiddens
269      d['enum_notes'] = enum_notes
270      d['enum_sdk_notes'] = enum_sdk_notes
271      d['enum_ndk_notes'] = enum_ndk_notes
272      d['enum_ids'] = enum_ids
273      d['enum_hal_versions'] = enum_hal_versions
274      d['enum'] = True
275
276    #
277    # Container (Array/Tuple)
278    #
279    if entry.attrs.get('container') is not None:
280      container_name = entry['container']
281
282      array = entry.find('array')
283      if array is not None:
284        array_sizes = []
285        for size in array.find_all('size'):
286          array_sizes.append(size.string)
287        d['container_sizes'] = array_sizes
288
289      tupl = entry.find('tuple')
290      if tupl is not None:
291        tupl_values = []
292        for val in tupl.find_all('value'):
293          tupl_values.append(val.name)
294        d['tuple_values'] = tupl_values
295        d['container_sizes'] = len(tupl_values)
296
297      d['container'] = container_name
298
299    return d
300
301  def _parse_entry_optional(self, entry):
302    d = {}
303
304    optional_elements = ['description', 'range', 'units', 'details', 'hal_details', 'ndk_details',\
305                         'deprecation_description']
306    for i in optional_elements:
307      prop = find_child_tag(entry, i)
308
309      if prop is not None:
310        d[i] = prop.string
311
312    tag_ids = []
313    for tag in entry.find_all('tag'):
314      tag_ids.append(tag['id'])
315
316    d['tag_ids'] = tag_ids
317
318    return d
319
320  def render(self, template, output_name=None, hal_version="3.2"):
321    """
322    Render the metadata model using a Mako template as the view.
323
324    The template gets the metadata as an argument, as well as all
325    public attributes from the metadata_helpers module.
326
327    The output file is encoded with UTF-8.
328
329    Args:
330      template: path to a Mako template file
331      output_name: path to the output file, or None to use stdout
332      hal_version: target HAL version, used when generating HIDL HAL outputs.
333                   Must be a string of form "X.Y" where X and Y are integers.
334    """
335    buf = StringIO()
336    metadata_helpers._context_buf = buf
337    metadata_helpers._hal_major_version = int(hal_version.partition('.')[0])
338    metadata_helpers._hal_minor_version = int(hal_version.partition('.')[2])
339
340    helpers = [(i, getattr(metadata_helpers, i))
341                for i in dir(metadata_helpers) if not i.startswith('_')]
342    helpers = dict(helpers)
343
344    lookup = TemplateLookup(directories=[os.getcwd()])
345    tpl = Template(filename=template, lookup=lookup)
346
347    ctx = Context(buf, metadata=self.metadata, **helpers)
348    tpl.render_context(ctx)
349
350    tpl_data = buf.getvalue()
351    metadata_helpers._context_buf = None
352    buf.close()
353
354    if output_name is None:
355      print(tpl_data)
356    else:
357      open(output_name, "w").write(tpl_data)
358
359#####################
360#####################
361
362if __name__ == "__main__":
363  if len(sys.argv) <= 2:
364    print("Usage: %s <filename.xml> <template.mako> [<output_file>] [<hal_version>]"          \
365          % (sys.argv[0]), file=sys.stderr)
366    sys.exit(0)
367
368  file_name = sys.argv[1]
369  template_name = sys.argv[2]
370  output_name = sys.argv[3] if len(sys.argv) > 3 else None
371  hal_version = sys.argv[4] if len(sys.argv) > 4 else "3.2"
372
373  parser = MetadataParserXml.create_from_file(file_name)
374  parser.render(template_name, output_name, hal_version)
375
376  sys.exit(0)
377