Home | History | Annotate | Download | only in docs
      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 """
     20 A parser for metadata_properties.xml can also render the resulting model
     21 over a Mako template.
     22 
     23 Usage:
     24   metadata_parser_xml.py <filename.xml> <template.mako> [<output_file>]
     25   - outputs the resulting template to output_file (stdout if none specified)
     26 
     27 Module:
     28   The parser is also available as a module import (MetadataParserXml) to use
     29   in other modules.
     30 
     31 Dependencies:
     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 
     38 import sys
     39 import os
     40 import StringIO
     41 
     42 from bs4 import BeautifulSoup
     43 from bs4 import NavigableString
     44 
     45 from mako.template import Template
     46 from mako.lookup import TemplateLookup
     47 from mako.runtime import Context
     48 
     49 from metadata_model import *
     50 import metadata_model
     51 from metadata_validate import *
     52 import metadata_helpers
     53 
     54 class 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 
    160         insert = self.metadata.insert_clone
    161 
    162       d3 = self._parse_entry_optional(entry)
    163 
    164       entry_dict = dict(d.items() + d2.items() + d3.items())
    165       insert(entry_dict)
    166 
    167     self.metadata.construct_graph()
    168 
    169   def _parse_entry(self, entry):
    170     d = {}
    171 
    172     #
    173     # Visibility
    174     #
    175     d['visibility'] = entry.get('visibility')
    176 
    177     #
    178     # Synthetic ?
    179     #
    180     d['synthetic'] = entry.get('synthetic') == 'true'
    181 
    182     #
    183     # Hardware Level (one of limited, legacy, full)
    184     #
    185     d['hwlevel'] = entry.get('hwlevel')
    186 
    187     #
    188     # Deprecated ?
    189     #
    190     d['deprecated'] = entry.get('deprecated') == 'true'
    191 
    192     #
    193     # Optional for non-full hardware level devices
    194     #
    195     d['optional'] = entry.get('optional') == 'true'
    196 
    197     #
    198     # Typedef
    199     #
    200     d['type_name'] = entry.get('typedef')
    201 
    202     #
    203     # Enum
    204     #
    205     if entry.get('enum', 'false') == 'true':
    206 
    207       enum_values = []
    208       enum_deprecateds = []
    209       enum_optionals = []
    210       enum_hiddens = []
    211       enum_ndk_hiddens = []
    212       enum_notes = {}
    213       enum_ids = {}
    214       for value in entry.enum.find_all('value'):
    215 
    216         value_body = self._strings_no_nl(value)
    217         enum_values.append(value_body)
    218 
    219         if value.attrs.get('deprecated', 'false') == 'true':
    220           enum_deprecateds.append(value_body)
    221 
    222         if value.attrs.get('optional', 'false') == 'true':
    223           enum_optionals.append(value_body)
    224 
    225         if value.attrs.get('hidden', 'false') == 'true':
    226           enum_hiddens.append(value_body)
    227 
    228         if value.attrs.get('ndk_hidden', 'false') == 'true':
    229           enum_ndk_hiddens.append(value_body)
    230 
    231         notes = value.find('notes')
    232         if notes is not None:
    233           enum_notes[value_body] = notes.string
    234 
    235         if value.attrs.get('id') is not None:
    236           enum_ids[value_body] = value['id']
    237 
    238       d['enum_values'] = enum_values
    239       d['enum_deprecateds'] = enum_deprecateds
    240       d['enum_optionals'] = enum_optionals
    241       d['enum_hiddens'] = enum_hiddens
    242       d['enum_ndk_hiddens'] = enum_ndk_hiddens
    243       d['enum_notes'] = enum_notes
    244       d['enum_ids'] = enum_ids
    245       d['enum'] = True
    246 
    247     #
    248     # Container (Array/Tuple)
    249     #
    250     if entry.attrs.get('container') is not None:
    251       container_name = entry['container']
    252 
    253       array = entry.find('array')
    254       if array is not None:
    255         array_sizes = []
    256         for size in array.find_all('size'):
    257           array_sizes.append(size.string)
    258         d['container_sizes'] = array_sizes
    259 
    260       tupl = entry.find('tuple')
    261       if tupl is not None:
    262         tupl_values = []
    263         for val in tupl.find_all('value'):
    264           tupl_values.append(val.name)
    265         d['tuple_values'] = tupl_values
    266         d['container_sizes'] = len(tupl_values)
    267 
    268       d['container'] = container_name
    269 
    270     return d
    271 
    272   def _parse_entry_optional(self, entry):
    273     d = {}
    274 
    275     optional_elements = ['description', 'range', 'units', 'details', 'hal_details']
    276     for i in optional_elements:
    277       prop = find_child_tag(entry, i)
    278 
    279       if prop is not None:
    280         d[i] = prop.string
    281 
    282     tag_ids = []
    283     for tag in entry.find_all('tag'):
    284       tag_ids.append(tag['id'])
    285 
    286     d['tag_ids'] = tag_ids
    287 
    288     return d
    289 
    290   def render(self, template, output_name=None):
    291     """
    292     Render the metadata model using a Mako template as the view.
    293 
    294     The template gets the metadata as an argument, as well as all
    295     public attributes from the metadata_helpers module.
    296 
    297     The output file is encoded with UTF-8.
    298 
    299     Args:
    300       template: path to a Mako template file
    301       output_name: path to the output file, or None to use stdout
    302     """
    303     buf = StringIO.StringIO()
    304     metadata_helpers._context_buf = buf
    305 
    306     helpers = [(i, getattr(metadata_helpers, i))
    307                 for i in dir(metadata_helpers) if not i.startswith('_')]
    308     helpers = dict(helpers)
    309 
    310     lookup = TemplateLookup(directories=[os.getcwd()])
    311     tpl = Template(filename=template, lookup=lookup)
    312 
    313     ctx = Context(buf, metadata=self.metadata, **helpers)
    314     tpl.render_context(ctx)
    315 
    316     tpl_data = buf.getvalue()
    317     metadata_helpers._context_buf = None
    318     buf.close()
    319 
    320     if output_name is None:
    321       print tpl_data
    322     else:
    323       file(output_name, "w").write(tpl_data.encode('utf-8'))
    324 
    325 #####################
    326 #####################
    327 
    328 if __name__ == "__main__":
    329   if len(sys.argv) <= 2:
    330     print >> sys.stderr,                                                       \
    331            "Usage: %s <filename.xml> <template.mako> [<output_file>]"          \
    332            % (sys.argv[0])
    333     sys.exit(0)
    334 
    335   file_name = sys.argv[1]
    336   template_name = sys.argv[2]
    337   output_name = sys.argv[3] if len(sys.argv) > 3 else None
    338   parser = MetadataParserXml.create_from_file(file_name)
    339   parser.render(template_name, output_name)
    340 
    341   sys.exit(0)
    342