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