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 file 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, file_name):
     65     """
     66     Construct a new MetadataParserXml, immediately try to parse it into a
     67     metadata model.
     68 
     69     Args:
     70       file_name: path to an XML file that passes metadata-validate
     71 
     72     Raises:
     73       ValueError: if the XML file failed to pass metadata_validate.py
     74     """
     75     self._soup = validate_xml(file_name)
     76 
     77     if self._soup is None:
     78       raise ValueError("%s has an invalid XML file" %(file_name))
     79 
     80     self._metadata = Metadata()
     81     self._parse()
     82     self._metadata.construct_graph()
     83 
     84   @property
     85   def soup(self):
     86     return self._soup
     87 
     88   @property
     89   def metadata(self):
     90     return self._metadata
     91 
     92   @staticmethod
     93   def _find_direct_strings(element):
     94     if element.string is not None:
     95       return [element.string]
     96 
     97     return [i for i in element.contents if isinstance(i, NavigableString)]
     98 
     99   @staticmethod
    100   def _strings_no_nl(element):
    101     return "".join([i.strip() for i in MetadataParserXml._find_direct_strings(element)])
    102 
    103   def _parse(self):
    104 
    105     tags = self.soup.tags
    106     if tags is not None:
    107       for tag in tags.find_all('tag'):
    108         self.metadata.insert_tag(tag['id'], tag.string)
    109 
    110     types = self.soup.types
    111     if types is not None:
    112       for tp in types.find_all('typedef'):
    113         languages = {}
    114         for lang in tp.find_all('language'):
    115           languages[lang['name']] = lang.string
    116 
    117         self.metadata.insert_type(tp['name'], 'typedef', languages=languages)
    118 
    119     # add all entries, preserving the ordering of the XML file
    120     # this is important for future ABI compatibility when generating code
    121     entry_filter = lambda x: x.name == 'entry' or x.name == 'clone'
    122     for entry in self.soup.find_all(entry_filter):
    123       if entry.name == 'entry':
    124         d = {
    125               'name': fully_qualified_name(entry),
    126               'type': entry['type'],
    127               'kind': find_kind(entry),
    128               'type_notes': entry.attrs.get('type_notes')
    129             }
    130 
    131         d2 = self._parse_entry(entry)
    132         insert = self.metadata.insert_entry
    133       else:
    134         d = {
    135            'name': entry['entry'],
    136            'kind': find_kind(entry),
    137            'target_kind': entry['kind'],
    138           # no type since its the same
    139           # no type_notes since its the same
    140         }
    141         d2 = {}
    142 
    143         insert = self.metadata.insert_clone
    144 
    145       d3 = self._parse_entry_optional(entry)
    146 
    147       entry_dict = dict(d.items() + d2.items() + d3.items())
    148       insert(entry_dict)
    149 
    150     self.metadata.construct_graph()
    151 
    152   def _parse_entry(self, entry):
    153     d = {}
    154 
    155     #
    156     # Visibility
    157     #
    158     d['visibility'] = entry.get('visibility')
    159 
    160     #
    161     # Optional for non-full hardware level devices
    162     #
    163     d['optional'] = entry.get('optional') == 'true'
    164 
    165     #
    166     # Typedef
    167     #
    168     d['type_name'] = entry.get('typedef')
    169 
    170     #
    171     # Enum
    172     #
    173     if entry.get('enum', 'false') == 'true':
    174 
    175       enum_values = []
    176       enum_optionals = []
    177       enum_notes = {}
    178       enum_ids = {}
    179       for value in entry.enum.find_all('value'):
    180 
    181         value_body = self._strings_no_nl(value)
    182         enum_values.append(value_body)
    183 
    184         if value.attrs.get('optional', 'false') == 'true':
    185           enum_optionals.append(value_body)
    186 
    187         notes = value.find('notes')
    188         if notes is not None:
    189           enum_notes[value_body] = notes.string
    190 
    191         if value.attrs.get('id') is not None:
    192           enum_ids[value_body] = value['id']
    193 
    194       d['enum_values'] = enum_values
    195       d['enum_optionals'] = enum_optionals
    196       d['enum_notes'] = enum_notes
    197       d['enum_ids'] = enum_ids
    198       d['enum'] = True
    199 
    200     #
    201     # Container (Array/Tuple)
    202     #
    203     if entry.attrs.get('container') is not None:
    204       container_name = entry['container']
    205 
    206       array = entry.find('array')
    207       if array is not None:
    208         array_sizes = []
    209         for size in array.find_all('size'):
    210           array_sizes.append(size.string)
    211         d['container_sizes'] = array_sizes
    212 
    213       tupl = entry.find('tuple')
    214       if tupl is not None:
    215         tupl_values = []
    216         for val in tupl.find_all('value'):
    217           tupl_values.append(val.name)
    218         d['tuple_values'] = tupl_values
    219         d['container_sizes'] = len(tupl_values)
    220 
    221       d['container'] = container_name
    222 
    223     return d
    224 
    225   def _parse_entry_optional(self, entry):
    226     d = {}
    227 
    228     optional_elements = ['description', 'range', 'units', 'notes']
    229     for i in optional_elements:
    230       prop = find_child_tag(entry, i)
    231 
    232       if prop is not None:
    233         d[i] = prop.string
    234 
    235     tag_ids = []
    236     for tag in entry.find_all('tag'):
    237       tag_ids.append(tag['id'])
    238 
    239     d['tag_ids'] = tag_ids
    240 
    241     return d
    242 
    243   def render(self, template, output_name=None):
    244     """
    245     Render the metadata model using a Mako template as the view.
    246 
    247     The template gets the metadata as an argument, as well as all
    248     public attributes from the metadata_helpers module.
    249 
    250     Args:
    251       template: path to a Mako template file
    252       output_name: path to the output file, or None to use stdout
    253     """
    254     buf = StringIO.StringIO()
    255     metadata_helpers._context_buf = buf
    256 
    257     helpers = [(i, getattr(metadata_helpers, i))
    258                 for i in dir(metadata_helpers) if not i.startswith('_')]
    259     helpers = dict(helpers)
    260 
    261     lookup = TemplateLookup(directories=[os.getcwd()])
    262     tpl = Template(filename=template, lookup=lookup)
    263 
    264     ctx = Context(buf, metadata=self.metadata, **helpers)
    265     tpl.render_context(ctx)
    266 
    267     tpl_data = buf.getvalue()
    268     metadata_helpers._context_buf = None
    269     buf.close()
    270 
    271     if output_name is None:
    272       print tpl_data
    273     else:
    274       file(output_name, "w").write(tpl_data)
    275 
    276 #####################
    277 #####################
    278 
    279 if __name__ == "__main__":
    280   if len(sys.argv) <= 2:
    281     print >> sys.stderr,                                                       \
    282            "Usage: %s <filename.xml> <template.mako> [<output_file>]"          \
    283            % (sys.argv[0])
    284     sys.exit(0)
    285 
    286   file_name = sys.argv[1]
    287   template_name = sys.argv[2]
    288   output_name = sys.argv[3] if len(sys.argv) > 3 else None
    289   parser = MetadataParserXml(file_name)
    290   parser.render(template_name, output_name)
    291 
    292   sys.exit(0)
    293