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