Home | History | Annotate | Download | only in grit
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 '''Class for reading GRD files into memory, without processing them.
      7 '''
      8 
      9 import os.path
     10 import types
     11 import xml.sax
     12 import xml.sax.handler
     13 
     14 from grit import exception
     15 from grit import util
     16 from grit.node import base
     17 from grit.node import mapping
     18 from grit.node import misc
     19 
     20 
     21 class StopParsingException(Exception):
     22   '''An exception used to stop parsing.'''
     23   pass
     24 
     25 
     26 class GrdContentHandler(xml.sax.handler.ContentHandler):
     27   def __init__(self, stop_after, debug, dir, defines, tags_to_ignore):
     28     # Invariant of data:
     29     # 'root' is the root of the parse tree being created, or None if we haven't
     30     # parsed out any elements.
     31     # 'stack' is the a stack of elements that we push new nodes onto and
     32     # pop from when they finish parsing, or [] if we are not currently parsing.
     33     # 'stack[-1]' is the top of the stack.
     34     self.root = None
     35     self.stack = []
     36     self.stop_after = stop_after
     37     self.debug = debug
     38     self.dir = dir
     39     self.defines = defines
     40     self.tags_to_ignore = tags_to_ignore or set()
     41     self.ignore_depth = 0
     42 
     43   def startElement(self, name, attrs):
     44     if self.ignore_depth or name in self.tags_to_ignore:
     45       if self.debug and self.ignore_depth == 0:
     46         print "Ignoring element %s and its children" % name
     47       self.ignore_depth += 1
     48       return
     49 
     50     if self.debug:
     51       attr_list = ' '.join('%s="%s"' % kv for kv in attrs.items())
     52       print ("Starting parsing of element %s with attributes %r" %
     53           (name, attr_list or '(none)'))
     54 
     55     typeattr = attrs.get('type')
     56     node = mapping.ElementToClass(name, typeattr)()
     57 
     58     if self.stack:
     59       self.stack[-1].AddChild(node)
     60       node.StartParsing(name, self.stack[-1])
     61     else:
     62       assert self.root is None
     63       self.root = node
     64       node.StartParsing(name, None)
     65       if self.defines:
     66         node.SetDefines(self.defines)
     67     self.stack.append(node)
     68 
     69     for attr, attrval in attrs.items():
     70       node.HandleAttribute(attr, attrval)
     71 
     72   def endElement(self, name):
     73     if self.ignore_depth:
     74       self.ignore_depth -= 1
     75       return
     76 
     77     if name == 'part':
     78       partnode = self.stack[-1]
     79       partnode.started_inclusion = True
     80       # Add the contents of the sub-grd file as children of the <part> node.
     81       partname = partnode.GetInputPath()
     82       if os.path.dirname(partname):
     83         # TODO(benrg): Remove this limitation. (The problem is that GRIT
     84         # assumes that files referenced from the GRD file are relative to
     85         # a path stored in the root <grit> node.)
     86         raise exception.GotPathExpectedFilenameOnly()
     87       partname = os.path.join(self.dir, partname)
     88       # Exceptions propagate to the handler in grd_reader.Parse().
     89       xml.sax.parse(partname, GrdPartContentHandler(self))
     90 
     91     if self.debug:
     92       print "End parsing of element %s" % name
     93     self.stack.pop().EndParsing()
     94 
     95     if name == self.stop_after:
     96       raise StopParsingException()
     97 
     98   def characters(self, content):
     99     if self.ignore_depth == 0:
    100       if self.stack[-1]:
    101         self.stack[-1].AppendContent(content)
    102 
    103   def ignorableWhitespace(self, whitespace):
    104     # TODO(joi)  This is not supported by expat.  Should use a different XML parser?
    105     pass
    106 
    107 
    108 class GrdPartContentHandler(xml.sax.handler.ContentHandler):
    109   def __init__(self, parent):
    110     self.parent = parent
    111     self.depth = 0
    112 
    113   def startElement(self, name, attrs):
    114     if self.depth:
    115       self.parent.startElement(name, attrs)
    116     else:
    117       if name != 'grit-part':
    118         raise exception.MissingElement("root tag must be <grit-part>")
    119       if attrs:
    120         raise exception.UnexpectedAttribute(
    121             "<grit-part> tag must not have attributes")
    122     self.depth += 1
    123 
    124   def endElement(self, name):
    125     self.depth -= 1
    126     if self.depth:
    127       self.parent.endElement(name)
    128 
    129   def characters(self, content):
    130     self.parent.characters(content)
    131 
    132   def ignorableWhitespace(self, whitespace):
    133     self.parent.ignorableWhitespace(whitespace)
    134 
    135 
    136 def Parse(filename_or_stream, dir=None, stop_after=None, first_ids_file=None,
    137           debug=False, defines=None, tags_to_ignore=None, target_platform=None):
    138   '''Parses a GRD file into a tree of nodes (from grit.node).
    139 
    140   If filename_or_stream is a stream, 'dir' should point to the directory
    141   notionally containing the stream (this feature is only used in unit tests).
    142 
    143   If 'stop_after' is provided, the parsing will stop once the first node
    144   with this name has been fully parsed (including all its contents).
    145 
    146   If 'debug' is true, lots of information about the parsing events will be
    147   printed out during parsing of the file.
    148 
    149   If 'first_ids_file' is non-empty, it is used to override the setting for the
    150   first_ids_file attribute of the <grit> root node. Note that the first_ids_file
    151   parameter should be relative to the cwd, even though the first_ids_file
    152   attribute of the <grit> node is relative to the grd file.
    153 
    154   If 'target_platform' is set, this is used to determine the target
    155   platform of builds, instead of using |sys.platform|.
    156 
    157   Args:
    158     filename_or_stream: './bla.xml'
    159     dir: None (if filename_or_stream is a filename) or '.'
    160     stop_after: 'inputs'
    161     first_ids_file: 'GRIT_DIR/../gritsettings/resource_ids'
    162     debug: False
    163     defines: dictionary of defines, like {'chromeos': '1'}
    164     target_platform: None or the value that would be returned by sys.platform
    165         on your target platform.
    166 
    167   Return:
    168     Subclass of grit.node.base.Node
    169 
    170   Throws:
    171     grit.exception.Parsing
    172   '''
    173 
    174   if dir is None and isinstance(filename_or_stream, types.StringType):
    175     dir = util.dirname(filename_or_stream)
    176 
    177   handler = GrdContentHandler(stop_after=stop_after, debug=debug, dir=dir,
    178                               defines=defines, tags_to_ignore=tags_to_ignore)
    179   try:
    180     xml.sax.parse(filename_or_stream, handler)
    181   except StopParsingException:
    182     assert stop_after
    183     pass
    184   except:
    185     if not debug:
    186       print "parse exception: run GRIT with the -x flag to debug .grd problems"
    187     raise
    188 
    189   if handler.root.name != 'grit':
    190     raise exception.MissingElement("root tag must be <grit>")
    191 
    192   if hasattr(handler.root, 'SetOwnDir'):
    193     # Fix up the base_dir so it is relative to the input file.
    194     assert dir is not None
    195     handler.root.SetOwnDir(dir)
    196 
    197   if isinstance(handler.root, misc.GritNode):
    198     if target_platform:
    199       handler.root.SetTargetPlatform(target_platform)
    200     if first_ids_file:
    201       # Make the path to the first_ids_file relative to the grd file,
    202       # unless it begins with GRIT_DIR.
    203       GRIT_DIR_PREFIX = 'GRIT_DIR'
    204       if not (first_ids_file.startswith(GRIT_DIR_PREFIX)
    205           and first_ids_file[len(GRIT_DIR_PREFIX)] in ['/', '\\']):
    206         rel_dir = os.path.relpath(os.getcwd(), dir)
    207         first_ids_file = util.normpath(os.path.join(rel_dir, first_ids_file))
    208       handler.root.attrs['first_ids_file'] = first_ids_file
    209     # Assign first ids to the nodes that don't have them.
    210     handler.root.AssignFirstIds(filename_or_stream, defines)
    211 
    212   return handler.root
    213 
    214 
    215 if __name__ == '__main__':
    216   util.ChangeStdoutEncoding()
    217   print unicode(Parse(sys.argv[1]))
    218