374 lines
10 KiB
Python
Executable File
374 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
#
|
|
# Copyright (C) 2012 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
|
|
"""
|
|
A parser for metadata_definitions.xml can also render the resulting model
|
|
over a Mako template.
|
|
|
|
Usage:
|
|
metadata_parser_xml.py <filename.xml> <template.mako> [<output_file>]
|
|
- outputs the resulting template to output_file (stdout if none specified)
|
|
|
|
Module:
|
|
The parser is also available as a module import (MetadataParserXml) to use
|
|
in other modules.
|
|
|
|
Dependencies:
|
|
BeautifulSoup - an HTML/XML parser available to download from
|
|
http://www.crummy.com/software/BeautifulSoup/
|
|
Mako - a template engine for Python, available to download from
|
|
http://www.makotemplates.org/
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
|
|
from bs4 import BeautifulSoup
|
|
from bs4 import NavigableString
|
|
|
|
from io import StringIO
|
|
|
|
from mako.template import Template
|
|
from mako.lookup import TemplateLookup
|
|
from mako.runtime import Context
|
|
|
|
from metadata_model import *
|
|
import metadata_model
|
|
from metadata_validate import *
|
|
import metadata_helpers
|
|
|
|
class MetadataParserXml:
|
|
"""
|
|
A class to parse any XML block that passes validation with metadata-validate.
|
|
It builds a metadata_model.Metadata graph and then renders it over a
|
|
Mako template.
|
|
|
|
Attributes (Read-Only):
|
|
soup: an instance of BeautifulSoup corresponding to the XML contents
|
|
metadata: a constructed instance of metadata_model.Metadata
|
|
"""
|
|
def __init__(self, xml, file_name):
|
|
"""
|
|
Construct a new MetadataParserXml, immediately try to parse it into a
|
|
metadata model.
|
|
|
|
Args:
|
|
xml: The XML block to use for the metadata
|
|
file_name: Source of the XML block, only for debugging/errors
|
|
|
|
Raises:
|
|
ValueError: if the XML block failed to pass metadata_validate.py
|
|
"""
|
|
self._soup = validate_xml(xml)
|
|
|
|
if self._soup is None:
|
|
raise ValueError("%s has an invalid XML file" % (file_name))
|
|
|
|
self._metadata = Metadata()
|
|
self._parse()
|
|
self._metadata.construct_graph()
|
|
|
|
@staticmethod
|
|
def create_from_file(file_name):
|
|
"""
|
|
Construct a new MetadataParserXml by loading and parsing an XML file.
|
|
|
|
Args:
|
|
file_name: Name of the XML file to load and parse.
|
|
|
|
Raises:
|
|
ValueError: if the XML file failed to pass metadata_validate.py
|
|
|
|
Returns:
|
|
MetadataParserXml instance representing the XML file.
|
|
"""
|
|
return MetadataParserXml(open(file_name).read(), file_name)
|
|
|
|
@property
|
|
def soup(self):
|
|
return self._soup
|
|
|
|
@property
|
|
def metadata(self):
|
|
return self._metadata
|
|
|
|
@staticmethod
|
|
def _find_direct_strings(element):
|
|
if element.string is not None:
|
|
return [element.string]
|
|
|
|
return [i for i in element.contents if isinstance(i, NavigableString)]
|
|
|
|
@staticmethod
|
|
def _strings_no_nl(element):
|
|
return "".join([i.strip() for i in MetadataParserXml._find_direct_strings(element)])
|
|
|
|
def _parse(self):
|
|
|
|
tags = self.soup.tags
|
|
if tags is not None:
|
|
for tag in tags.find_all('tag'):
|
|
self.metadata.insert_tag(tag['id'], tag.string)
|
|
|
|
types = self.soup.types
|
|
if types is not None:
|
|
for tp in types.find_all('typedef'):
|
|
languages = {}
|
|
for lang in tp.find_all('language'):
|
|
languages[lang['name']] = lang.string
|
|
|
|
self.metadata.insert_type(tp['name'], 'typedef', languages=languages)
|
|
|
|
# add all entries, preserving the ordering of the XML file
|
|
# this is important for future ABI compatibility when generating code
|
|
entry_filter = lambda x: x.name == 'entry' or x.name == 'clone'
|
|
for entry in self.soup.find_all(entry_filter):
|
|
if entry.name == 'entry':
|
|
d = {
|
|
'name': fully_qualified_name(entry),
|
|
'type': entry['type'],
|
|
'kind': find_kind(entry),
|
|
'type_notes': entry.attrs.get('type_notes')
|
|
}
|
|
|
|
d2 = self._parse_entry(entry)
|
|
insert = self.metadata.insert_entry
|
|
else:
|
|
d = {
|
|
'name': entry['entry'],
|
|
'kind': find_kind(entry),
|
|
'target_kind': entry['kind'],
|
|
# no type since its the same
|
|
# no type_notes since its the same
|
|
}
|
|
d2 = {}
|
|
if 'hal_version' in entry.attrs:
|
|
d2['hal_version'] = entry['hal_version']
|
|
|
|
insert = self.metadata.insert_clone
|
|
|
|
d3 = self._parse_entry_optional(entry)
|
|
|
|
entry_dict = {**d, **d2, **d3}
|
|
insert(entry_dict)
|
|
|
|
self.metadata.construct_graph()
|
|
|
|
def _parse_entry(self, entry):
|
|
d = {}
|
|
|
|
#
|
|
# Visibility
|
|
#
|
|
d['visibility'] = entry.get('visibility')
|
|
|
|
#
|
|
# Synthetic ?
|
|
#
|
|
d['synthetic'] = entry.get('synthetic') == 'true'
|
|
|
|
#
|
|
# Permission needed ?
|
|
#
|
|
d['permission_needed'] = entry.get('permission_needed')
|
|
|
|
#
|
|
# Hardware Level (one of limited, legacy, full)
|
|
#
|
|
d['hwlevel'] = entry.get('hwlevel')
|
|
|
|
#
|
|
# Deprecated ?
|
|
#
|
|
d['deprecated'] = entry.get('deprecated') == 'true'
|
|
|
|
#
|
|
# Optional for non-full hardware level devices
|
|
#
|
|
d['optional'] = entry.get('optional') == 'true'
|
|
|
|
#
|
|
# Typedef
|
|
#
|
|
d['type_name'] = entry.get('typedef')
|
|
|
|
#
|
|
# Initial HIDL HAL version the entry was added in
|
|
d['hal_version'] = entry.get('hal_version')
|
|
|
|
#
|
|
# Enum
|
|
#
|
|
if entry.get('enum', 'false') == 'true':
|
|
|
|
enum_values = []
|
|
enum_deprecateds = []
|
|
enum_optionals = []
|
|
enum_visibilities = {}
|
|
enum_notes = {}
|
|
enum_sdk_notes = {}
|
|
enum_ndk_notes = {}
|
|
enum_ids = {}
|
|
enum_hal_versions = {}
|
|
for value in entry.enum.find_all('value'):
|
|
|
|
value_body = self._strings_no_nl(value)
|
|
enum_values.append(value_body)
|
|
|
|
if value.attrs.get('deprecated', 'false') == 'true':
|
|
enum_deprecateds.append(value_body)
|
|
|
|
if value.attrs.get('optional', 'false') == 'true':
|
|
enum_optionals.append(value_body)
|
|
|
|
visibility = value.attrs.get('visibility')
|
|
if visibility is not None:
|
|
enum_visibilities[value_body] = visibility
|
|
|
|
notes = value.find('notes')
|
|
if notes is not None:
|
|
enum_notes[value_body] = notes.string
|
|
|
|
sdk_notes = value.find('sdk_notes')
|
|
if sdk_notes is not None:
|
|
enum_sdk_notes[value_body] = sdk_notes.string
|
|
|
|
ndk_notes = value.find('ndk_notes')
|
|
if ndk_notes is not None:
|
|
enum_ndk_notes[value_body] = ndk_notes.string
|
|
|
|
if value.attrs.get('id') is not None:
|
|
enum_ids[value_body] = value['id']
|
|
|
|
if value.attrs.get('hal_version') is not None:
|
|
enum_hal_versions[value_body] = value['hal_version']
|
|
|
|
d['enum_values'] = enum_values
|
|
d['enum_deprecateds'] = enum_deprecateds
|
|
d['enum_optionals'] = enum_optionals
|
|
d['enum_visibilities'] = enum_visibilities
|
|
d['enum_notes'] = enum_notes
|
|
d['enum_sdk_notes'] = enum_sdk_notes
|
|
d['enum_ndk_notes'] = enum_ndk_notes
|
|
d['enum_ids'] = enum_ids
|
|
d['enum_hal_versions'] = enum_hal_versions
|
|
d['enum'] = True
|
|
|
|
#
|
|
# Container (Array/Tuple)
|
|
#
|
|
if entry.attrs.get('container') is not None:
|
|
container_name = entry['container']
|
|
|
|
array = entry.find('array')
|
|
if array is not None:
|
|
array_sizes = []
|
|
for size in array.find_all('size'):
|
|
array_sizes.append(size.string)
|
|
d['container_sizes'] = array_sizes
|
|
|
|
tupl = entry.find('tuple')
|
|
if tupl is not None:
|
|
tupl_values = []
|
|
for val in tupl.find_all('value'):
|
|
tupl_values.append(val.name)
|
|
d['tuple_values'] = tupl_values
|
|
d['container_sizes'] = len(tupl_values)
|
|
|
|
d['container'] = container_name
|
|
|
|
return d
|
|
|
|
def _parse_entry_optional(self, entry):
|
|
d = {}
|
|
|
|
optional_elements = ['description', 'range', 'units', 'details', 'hal_details', 'ndk_details',\
|
|
'deprecation_description']
|
|
for i in optional_elements:
|
|
prop = find_child_tag(entry, i)
|
|
|
|
if prop is not None:
|
|
d[i] = prop.string
|
|
|
|
tag_ids = []
|
|
for tag in entry.find_all('tag'):
|
|
tag_ids.append(tag['id'])
|
|
|
|
d['tag_ids'] = tag_ids
|
|
|
|
return d
|
|
|
|
def render(self, template, output_name=None, enum=None, copyright_year="2022"):
|
|
"""
|
|
Render the metadata model using a Mako template as the view.
|
|
|
|
The template gets the metadata as an argument, as well as all
|
|
public attributes from the metadata_helpers module.
|
|
|
|
The output file is encoded with UTF-8.
|
|
|
|
Args:
|
|
template: path to a Mako template file
|
|
output_name: path to the output file, or None to use stdout
|
|
enum: The name of the enum, if any
|
|
copyright_year: the year in the copyright section of output file
|
|
"""
|
|
buf = StringIO()
|
|
metadata_helpers._context_buf = buf
|
|
metadata_helpers._enum = enum
|
|
metadata_helpers._copyright_year = copyright_year
|
|
|
|
helpers = [(i, getattr(metadata_helpers, i))
|
|
for i in dir(metadata_helpers) if not i.startswith('_')]
|
|
helpers = dict(helpers)
|
|
|
|
lookup = TemplateLookup(directories=[os.getcwd()])
|
|
tpl = Template(filename=template, lookup=lookup)
|
|
|
|
ctx = Context(buf, metadata=self.metadata, **helpers)
|
|
tpl.render_context(ctx)
|
|
|
|
tpl_data = buf.getvalue()
|
|
metadata_helpers._context_buf = None
|
|
buf.close()
|
|
|
|
if output_name is None:
|
|
print(tpl_data)
|
|
else:
|
|
open(output_name, "w").write(tpl_data)
|
|
|
|
#####################
|
|
#####################
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) <= 2:
|
|
print("Usage: %s <filename.xml> <template.mako> [<output_file>]"\
|
|
" [<copyright_year>]" \
|
|
% (sys.argv[0]), file=sys.stderr)
|
|
sys.exit(0)
|
|
|
|
file_name = sys.argv[1]
|
|
template_name = sys.argv[2]
|
|
output_name = sys.argv[3] if len(sys.argv) > 3 else None
|
|
copyright_year = sys.argv[4] if len(sys.argv) > 4 else "2022"
|
|
|
|
parser = MetadataParserXml.create_from_file(file_name)
|
|
parser.render(template_name, output_name, None, copyright_year)
|
|
|
|
sys.exit(0)
|