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_optionals = []
    209       enum_hiddens = []
    210       enum_notes = {}
    211       enum_ids = {}
    212       for value in entry.enum.find_all('value'):
    213 
    214         value_body = self._strings_no_nl(value)
    215         enum_values.append(value_body)
    216 
    217         if value.attrs.get('optional', 'false') == 'true':
    218           enum_optionals.append(value_body)
    219 
    220         if value.attrs.get('hidden', 'false') == 'true':
    221           enum_hiddens.append(value_body)
    222 
    223         notes = value.find('notes')
    224         if notes is not None:
    225           enum_notes[value_body] = notes.string
    226 
    227         if value.attrs.get('id') is not None:
    228           enum_ids[value_body] = value['id']
    229 
    230       d['enum_values'] = enum_values
    231       d['enum_optionals'] = enum_optionals
    232       d['enum_hiddens'] = enum_hiddens
    233       d['enum_notes'] = enum_notes
    234       d['enum_ids'] = enum_ids
    235       d['enum'] = True
    236 
    237     #
    238     # Container (Array/Tuple)
    239     #
    240     if entry.attrs.get('container') is not None:
    241       container_name = entry['container']
    242 
    243       array = entry.find('array')
    244       if array is not None:
    245         array_sizes = []
    246         for size in array.find_all('size'):
    247           array_sizes.append(size.string)
    248         d['container_sizes'] = array_sizes
    249 
    250       tupl = entry.find('tuple')
    251       if tupl is not None:
    252         tupl_values = []
    253         for val in tupl.find_all('value'):
    254           tupl_values.append(val.name)
    255         d['tuple_values'] = tupl_values
    256         d['container_sizes'] = len(tupl_values)
    257 
    258       d['container'] = container_name
    259 
    260     return d
    261 
    262   def _parse_entry_optional(self, entry):
    263     d = {}
    264 
    265     optional_elements = ['description', 'range', 'units', 'details', 'hal_details']
    266     for i in optional_elements:
    267       prop = find_child_tag(entry, i)
    268 
    269       if prop is not None:
    270         d[i] = prop.string
    271 
    272     tag_ids = []
    273     for tag in entry.find_all('tag'):
    274       tag_ids.append(tag['id'])
    275 
    276     d['tag_ids'] = tag_ids
    277 
    278     return d
    279 
    280   def render(self, template, output_name=None):
    281     """
    282     Render the metadata model using a Mako template as the view.
    283 
    284     The template gets the metadata as an argument, as well as all
    285     public attributes from the metadata_helpers module.
    286 
    287     The output file is encoded with UTF-8.
    288 
    289     Args:
    290       template: path to a Mako template file
    291       output_name: path to the output file, or None to use stdout
    292     """
    293     buf = StringIO.StringIO()
    294     metadata_helpers._context_buf = buf
    295 
    296     helpers = [(i, getattr(metadata_helpers, i))
    297                 for i in dir(metadata_helpers) if not i.startswith('_')]
    298     helpers = dict(helpers)
    299 
    300     lookup = TemplateLookup(directories=[os.getcwd()])
    301     tpl = Template(filename=template, lookup=lookup)
    302 
    303     ctx = Context(buf, metadata=self.metadata, **helpers)
    304     tpl.render_context(ctx)
    305 
    306     tpl_data = buf.getvalue()
    307     metadata_helpers._context_buf = None
    308     buf.close()
    309 
    310     if output_name is None:
    311       print tpl_data
    312     else:
    313       file(output_name, "w").write(tpl_data.encode('utf-8'))
    314 
    315 #####################
    316 #####################
    317 
    318 if __name__ == "__main__":
    319   if len(sys.argv) <= 2:
    320     print >> sys.stderr,                                                       \
    321            "Usage: %s <filename.xml> <template.mako> [<output_file>]"          \
    322            % (sys.argv[0])
    323     sys.exit(0)
    324 
    325   file_name = sys.argv[1]
    326   template_name = sys.argv[2]
    327   output_name = sys.argv[3] if len(sys.argv) > 3 else None
    328   parser = MetadataParserXml.create_from_file(file_name)
    329   parser.render(template_name, output_name)
    330 
    331   sys.exit(0)
    332