195640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org#!/usr/bin/env python
295640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org# Copyright (c) 2012 The Chromium Authors. All rights reserved.
301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# Use of this source code is governed by a BSD-style license that can be
401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# found in the LICENSE file.
501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''Base types for nodes in a GRIT resource tree.
701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''
801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
9977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.orgimport ast
1001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport os
1101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport types
1201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom xml.sax import saxutils
1301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
14ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.orgfrom grit import clique
1501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import exception
1601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import util
1701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
19ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.orgclass Node(object):
20ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  '''An item in the tree that has children.'''
2101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  # Valid content types that can be returned by _ContentType()
2301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  _CONTENT_TYPE_NONE = 0   # No CDATA content but may have children
2401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  _CONTENT_TYPE_CDATA = 1  # Only CDATA, no children.
2501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  _CONTENT_TYPE_MIXED = 2  # CDATA and children, possibly intermingled
2601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  # Default nodes to not whitelist skipped
2801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  _whitelist_marked_as_skip = False
2901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
30977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org  # A class-static cache to speed up EvaluateExpression().
31977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org  # Keys are expressions (e.g. 'is_ios and lang == "fr"'). Values are tuples
32977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org  # (code, variables_in_expr) where code is the compiled expression and can be
33977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org  # directly eval'd, and variables_in_expr is the list of variable and method
34977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org  # names used in the expression (e.g. ['is_ios', 'lang']).
35977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org  eval_expr_cache = {}
3651dde08ce1ace3fba4e6d64ad401de1f4f45b225joi@chromium.org
3701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def __init__(self):
3801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.children = []        # A list of child elements
3901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.mixed_content = []   # A list of u'' and/or child elements (this
4001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                              # duplicates 'children' but
4101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                              # is needed to preserve markup-type content).
4201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.name = u''           # The name of this element
4301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.attrs = {}           # The set of attributes (keys to values)
4401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.parent = None        # Our parent unless we are the root element.
4501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.uberclique = None    # Allows overriding uberclique for parts of tree
4601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
47ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # This context handler allows you to write "with node:" and get a
48ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # line identifying the offending node if an exception escapes from the body
49ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # of the with statement.
50ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  def __enter__(self):
51ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    return self
52ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
53ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  def __exit__(self, exc_type, exc_value, traceback):
54ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    if exc_type is not None:
55ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      print u'Error processing node %s' % unicode(self)
56ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
5701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def __iter__(self):
58ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    '''A preorder iteration through the tree that this node is the root of.'''
59ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    return self.Preorder()
6001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
61ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  def Preorder(self):
6201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Generator that generates first this node, then the same generator for
6301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    any child nodes.'''
6401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    yield self
6501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for child in self.children:
66ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      for iterchild in child.Preorder():
6701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        yield iterchild
6801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
69ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  def ActiveChildren(self):
70ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    '''Returns the children of this node that should be included in the current
71ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    configuration. Overridden by <if>.'''
72ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    return [node for node in self.children if not node.WhitelistMarkedAsSkip()]
73ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
74ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  def ActiveDescendants(self):
75ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    '''Yields the current node and all descendants that should be included in
76ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    the current configuration, in preorder.'''
77ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    yield self
78ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    for child in self.ActiveChildren():
79ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      for descendant in child.ActiveDescendants():
80ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        yield descendant
81ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
8201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetRoot(self):
8301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns the root Node in the tree this Node belongs to.'''
8401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    curr = self
8501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    while curr.parent:
8601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      curr = curr.parent
8701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return curr
8801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
8901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # TODO(joi) Use this (currently untested) optimization?:
9001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    #if hasattr(self, '_root'):
9101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    #  return self._root
9201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    #curr = self
9301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    #while curr.parent and not hasattr(curr, '_root'):
9401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    #  curr = curr.parent
9501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    #if curr.parent:
9601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    #  self._root = curr._root
9701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    #else:
9801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    #  self._root = curr
9901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    #return self._root
10001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
10101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def StartParsing(self, name, parent):
10201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Called at the start of parsing.
10301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
10401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Args:
10501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      name: u'elementname'
10601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      parent: grit.node.base.Node or subclass or None
10701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
10801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert isinstance(name, types.StringTypes)
10901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert not parent or isinstance(parent, Node)
11001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.name = name
11101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.parent = parent
11201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
11301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def AddChild(self, child):
11401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Adds a child to the list of children of this node, if it is a valid
11501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    child for the node.'''
11601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert isinstance(child, Node)
11701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if (not self._IsValidChild(child) or
11801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        self._ContentType() == self._CONTENT_TYPE_CDATA):
11925fe2049cd4e0348041b7ed636dfe7682ff91021joi@chromium.org      explanation = 'invalid child %s for parent %s' % (str(child), self.name)
12001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      raise exception.UnexpectedChild(explanation)
12101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.children.append(child)
12201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.mixed_content.append(child)
12301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
12401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def RemoveChild(self, child_id):
12501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Removes the first node that has a "name" attribute which
12601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    matches "child_id" in the list of immediate children of
12701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    this node.
12801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
12901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Args:
13001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      child_id: String identifying the child to be removed
13101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
13201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    index = 0
13301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Safe not to copy since we only remove the first element found
13401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for child in self.children:
13501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      name_attr = child.attrs['name']
13601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if name_attr == child_id:
13701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        self.children.pop(index)
13801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        self.mixed_content.pop(index)
13901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        break
14001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      index += 1
14101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
14201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def AppendContent(self, content):
14301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Appends a chunk of text as content of this node.
14401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
14501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Args:
14601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      content: u'hello'
14701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
14801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Return:
14901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      None
15001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
15101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert isinstance(content, types.StringTypes)
15201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if self._ContentType() != self._CONTENT_TYPE_NONE:
15301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.mixed_content.append(content)
15401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    elif content.strip() != '':
15501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      raise exception.UnexpectedContent()
15601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
15701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def HandleAttribute(self, attrib, value):
15801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Informs the node of an attribute that was parsed out of the GRD file
15901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for it.
16001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
16101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Args:
16201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      attrib: 'name'
16301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      value: 'fooblat'
16401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
16501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Return:
16601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      None
16701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
16801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert isinstance(attrib, types.StringTypes)
16901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert isinstance(value, types.StringTypes)
17001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if self._IsValidAttribute(attrib, value):
17101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.attrs[attrib] = value
17201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    else:
17301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      raise exception.UnexpectedAttribute(attrib)
17401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
17501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def EndParsing(self):
17601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Called at the end of parsing.'''
17701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
17801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # TODO(joi) Rewrite this, it's extremely ugly!
17901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if len(self.mixed_content):
18001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if isinstance(self.mixed_content[0], types.StringTypes):
18101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # Remove leading and trailing chunks of pure whitespace.
18201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        while (len(self.mixed_content) and
18301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org               isinstance(self.mixed_content[0], types.StringTypes) and
18401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org               self.mixed_content[0].strip() == ''):
18501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          self.mixed_content = self.mixed_content[1:]
18601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # Strip leading and trailing whitespace from mixed content chunks
18701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # at front and back.
18801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if (len(self.mixed_content) and
18901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            isinstance(self.mixed_content[0], types.StringTypes)):
19001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          self.mixed_content[0] = self.mixed_content[0].lstrip()
19101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # Remove leading and trailing ''' (used to demarcate whitespace)
19201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if (len(self.mixed_content) and
19301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            isinstance(self.mixed_content[0], types.StringTypes)):
19401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          if self.mixed_content[0].startswith("'''"):
19501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            self.mixed_content[0] = self.mixed_content[0][3:]
19601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if len(self.mixed_content):
19701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if isinstance(self.mixed_content[-1], types.StringTypes):
19801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # Same stuff all over again for the tail end.
19901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        while (len(self.mixed_content) and
20001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org               isinstance(self.mixed_content[-1], types.StringTypes) and
20101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org               self.mixed_content[-1].strip() == ''):
20201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          self.mixed_content = self.mixed_content[:-1]
20301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if (len(self.mixed_content) and
20401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            isinstance(self.mixed_content[-1], types.StringTypes)):
20501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          self.mixed_content[-1] = self.mixed_content[-1].rstrip()
20601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if (len(self.mixed_content) and
20701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            isinstance(self.mixed_content[-1], types.StringTypes)):
20801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          if self.mixed_content[-1].endswith("'''"):
20901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            self.mixed_content[-1] = self.mixed_content[-1][:-3]
21001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
21101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Check that all mandatory attributes are there.
21201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for node_mandatt in self.MandatoryAttributes():
21301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      mandatt_list = []
21401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if node_mandatt.find('|') >= 0:
21501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        mandatt_list = node_mandatt.split('|')
21601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
21701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        mandatt_list.append(node_mandatt)
21801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
21901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      mandatt_option_found = False
22001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      for mandatt in mandatt_list:
22101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        assert mandatt not in self.DefaultAttributes().keys()
22201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if mandatt in self.attrs:
22301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          if not mandatt_option_found:
22401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            mandatt_option_found = True
22501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          else:
22601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            raise exception.MutuallyExclusiveMandatoryAttribute(mandatt)
22701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
22801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if not mandatt_option_found:
22901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        raise exception.MissingMandatoryAttribute(mandatt)
23001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
23101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Add default attributes if not specified in input file.
23201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for defattr in self.DefaultAttributes():
23301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if not defattr in self.attrs:
23401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        self.attrs[defattr] = self.DefaultAttributes()[defattr]
23501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
23601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetCdata(self):
23701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns all CDATA of this element, concatenated into a single
23801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    string.  Note that this ignores any elements embedded in CDATA.'''
239705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org    return ''.join([c for c in self.mixed_content
240705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org                    if isinstance(c, types.StringTypes)])
24101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
24201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def __unicode__(self):
24301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns this node and all nodes below it as an XML document in a Unicode
24401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    string.'''
24501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    header = u'<?xml version="1.0" encoding="UTF-8"?>\n'
24601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return header + self.FormatXml()
24701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
24801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def FormatXml(self, indent = u'', one_line = False):
24901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns this node and all nodes below it as an XML
25001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    element in a Unicode string.  This differs from __unicode__ in that it does
25101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    not include the <?xml> stuff at the top of the string.  If one_line is true,
25201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    children and CDATA are layed out in a way that preserves internal
25301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    whitespace.
25401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
25501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert isinstance(indent, types.StringTypes)
25601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
25701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    content_one_line = (one_line or
25801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                        self._ContentType() == self._CONTENT_TYPE_MIXED)
25901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    inside_content = self.ContentsAsXml(indent, content_one_line)
26001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
26101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Then the attributes for this node.
26283717e82a9b5e0c629ff4f1078d50503ffd2ae75benrg@chromium.org    attribs = u''
26383717e82a9b5e0c629ff4f1078d50503ffd2ae75benrg@chromium.org    default_attribs = self.DefaultAttributes()
26483717e82a9b5e0c629ff4f1078d50503ffd2ae75benrg@chromium.org    for attrib, value in sorted(self.attrs.items()):
26501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # Only print an attribute if it is other than the default value.
26683717e82a9b5e0c629ff4f1078d50503ffd2ae75benrg@chromium.org      if attrib not in default_attribs or value != default_attribs[attrib]:
26783717e82a9b5e0c629ff4f1078d50503ffd2ae75benrg@chromium.org        attribs += u' %s=%s' % (attrib, saxutils.quoteattr(value))
26801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
26901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Finally build the XML for our node and return it
27001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if len(inside_content) > 0:
27101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if one_line:
27201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        return u'<%s%s>%s</%s>' % (self.name, attribs, inside_content, self.name)
27301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      elif content_one_line:
27401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        return u'%s<%s%s>\n%s  %s\n%s</%s>' % (
27501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          indent, self.name, attribs,
27601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          indent, inside_content,
27701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          indent, self.name)
27801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
27901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        return u'%s<%s%s>\n%s\n%s</%s>' % (
28001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          indent, self.name, attribs,
28101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          inside_content,
28201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          indent, self.name)
28301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    else:
28401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      return u'%s<%s%s />' % (indent, self.name, attribs)
28501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
28601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def ContentsAsXml(self, indent, one_line):
28701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns the contents of this node (CDATA and child elements) in XML
28801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    format.  If 'one_line' is true, the content will be laid out on one line.'''
28901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert isinstance(indent, types.StringTypes)
29001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
29101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Build the contents of the element.
29201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    inside_parts = []
29301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    last_item = None
29401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for mixed_item in self.mixed_content:
29501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if isinstance(mixed_item, Node):
29601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        inside_parts.append(mixed_item.FormatXml(indent + u'  ', one_line))
29701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if not one_line:
29801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          inside_parts.append(u'\n')
29901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
30001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        message = mixed_item
30101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # If this is the first item and it starts with whitespace, we add
30201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # the ''' delimiter.
30301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if not last_item and message.lstrip() != message:
30401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          message = u"'''" + message
30501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        inside_parts.append(util.EncodeCdata(message))
30601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      last_item = mixed_item
30701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
30801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # If there are only child nodes and no cdata, there will be a spurious
30901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # trailing \n
31001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if len(inside_parts) and inside_parts[-1] == '\n':
31101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      inside_parts = inside_parts[:-1]
31201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
31301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # If the last item is a string (not a node) and ends with whitespace,
31401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # we need to add the ''' delimiter.
31501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if (isinstance(last_item, types.StringTypes) and
31601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        last_item.rstrip() != last_item):
31701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      inside_parts[-1] = inside_parts[-1] + u"'''"
31801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
31901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return u''.join(inside_parts)
32001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
32177cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org  def SubstituteMessages(self, substituter):
32277cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    '''Applies substitutions to all messages in the tree.
32377cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org
32477cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    Called as a final step of RunGatherers.
32577cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org
32677cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    Args:
32777cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org      substituter: a grit.util.Substituter object.
32877cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    '''
32977cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    for child in self.children:
33077cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org      child.SubstituteMessages(substituter)
33177cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org
33201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def _IsValidChild(self, child):
33301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns true if 'child' is a valid child of this node.
33401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Overridden by subclasses.'''
33501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return False
33601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
33701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def _IsValidAttribute(self, name, value):
33801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns true if 'name' is the name of a valid attribute of this element
33901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    and 'value' is a valid value for that attribute.  Overriden by
34001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    subclasses unless they have only mandatory attributes.'''
34101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return (name in self.MandatoryAttributes() or
34201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            name in self.DefaultAttributes())
34301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
34401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def _ContentType(self):
34501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns the type of content this element can have.  Overridden by
34601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    subclasses.  The content type can be one of the _CONTENT_TYPE_XXX constants
34701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    above.'''
34801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self._CONTENT_TYPE_NONE
34901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
35001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def MandatoryAttributes(self):
35101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns a list of attribute names that are mandatory (non-optional)
35201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    on the current element. One can specify a list of
35301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    "mutually exclusive mandatory" attributes by specifying them as one
35401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    element in the list, separated by a "|" character.
35501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
35601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return []
35701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
35801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def DefaultAttributes(self):
35901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns a dictionary of attribute names that have defaults, mapped to
36001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    the default value.  Overridden by subclasses.'''
36101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return {}
36201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
36301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetCliques(self):
36401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns all MessageClique objects belonging to this node.  Overridden
36501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    by subclasses.
36601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
36701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Return:
36801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      [clique1, clique2] or []
36901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
37001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return []
37101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
37201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def ToRealPath(self, path_from_basedir):
37301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns a real path (which can be absolute or relative to the current
37401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    working directory), given a path that is relative to the base directory
37501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    set for the GRIT input file.
37601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
37701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Args:
37801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      path_from_basedir: '..'
37901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
38001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Return:
38101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      'resource'
38201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
38301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return util.normpath(os.path.join(self.GetRoot().GetBaseDir(),
384bd79a1642abbe801db78778a59cdafc10e70bcccjoi@chromium.org                                      os.path.expandvars(path_from_basedir)))
38501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
386ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org  def GetInputPath(self):
387ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    '''Returns a path, relative to the base directory set for the grd file,
388ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    that points to the file the node refers to.
389ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    '''
390ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    # This implementation works for most nodes that have an input file.
391ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    return self.attrs['file']
39201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
39301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def UberClique(self):
39401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns the uberclique that should be used for messages originating in
39501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    a given node.  If the node itself has its uberclique set, that is what we
39601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    use, otherwise we search upwards until we find one.  If we do not find one
39701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    even at the root node, we set the root node's uberclique to a new
39801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    uberclique instance.
39901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
40001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    node = self
40101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    while not node.uberclique and node.parent:
40201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      node = node.parent
40301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if not node.uberclique:
40401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      node.uberclique = clique.UberClique()
40501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return node.uberclique
40601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
40701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def IsTranslateable(self):
40801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns false if the node has contents that should not be translated,
40901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    otherwise returns false (even if the node has no contents).
41001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
41101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if not 'translateable' in self.attrs:
41201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      return True
41301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    else:
41401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      return self.attrs['translateable'] == 'true'
41501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
41601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetNodeById(self, id):
41701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns the node in the subtree parented by this node that has a 'name'
41801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    attribute matching 'id'.  Returns None if no such node is found.
41901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
42001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for node in self:
42101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if 'name' in node.attrs and node.attrs['name'] == id:
42201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        return node
42301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return None
42401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
425705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org  def GetChildrenOfType(self, type):
426705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org    '''Returns a list of all subnodes (recursing to all leaves) of this node
42783717e82a9b5e0c629ff4f1078d50503ffd2ae75benrg@chromium.org    that are of the indicated type (or tuple of types).
428705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org
429705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org    Args:
430705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org      type: A type you could use with isinstance().
431705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org
432705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org    Return:
433705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org      A list, possibly empty.
434705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org    '''
435705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org    return [child for child in self if isinstance(child, type)]
436705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org
43701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetTextualIds(self):
438ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    '''Returns a list of the textual ids of this node.
43901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
44001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if 'name' in self.attrs:
44101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      return [self.attrs['name']]
442ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    return []
44301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
44451dde08ce1ace3fba4e6d64ad401de1f4f45b225joi@chromium.org  @classmethod
445977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org  def EvaluateExpression(cls, expr, defs, target_platform, extra_variables={}):
446ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org    '''Worker for EvaluateCondition (below) and conditions in XTB files.'''
447977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org    if expr in cls.eval_expr_cache:
448977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      code, variables_in_expr = cls.eval_expr_cache[expr]
449977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org    else:
450977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      # Get a list of all variable and method names used in the expression.
451977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      syntax_tree = ast.parse(expr, mode='eval')
452977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      variables_in_expr = [node.id for node in ast.walk(syntax_tree) if
453977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org          isinstance(node, ast.Name) and node.id not in ('True', 'False')]
454977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      code = compile(syntax_tree, filename='<string>', mode='eval')
455977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      cls.eval_expr_cache[expr] = code, variables_in_expr
456977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org
457977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org    # Set values only for variables that are needed to eval the expression.
458977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org    variable_map = {}
459977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org    for name in variables_in_expr:
460977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      if name == 'os':
461977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = target_platform
462977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name == 'defs':
463977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = defs
464977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org
465977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name == 'is_linux':
466977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = target_platform.startswith('linux')
467977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name == 'is_macosx':
468977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = target_platform == 'darwin'
469977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name == 'is_win':
470977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = target_platform in ('cygwin', 'win32')
471977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name == 'is_android':
472977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = target_platform == 'android'
473977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name == 'is_ios':
474977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = target_platform == 'ios'
475a462d172a329c260c4b77186af3e9813538e6267joi@chromium.org      elif name == 'is_bsd':
476a462d172a329c260c4b77186af3e9813538e6267joi@chromium.org        value = 'bsd' in target_platform
477977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name == 'is_posix':
478977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = (target_platform in ('darwin', 'linux2', 'linux3', 'sunos5',
479977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org                                     'android', 'ios')
480977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org                 or 'bsd' in target_platform)
481977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org
482977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name == 'pp_ifdef':
483977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        def pp_ifdef(symbol):
484977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org          return symbol in defs
485977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = pp_ifdef
486977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name == 'pp_if':
487977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        def pp_if(symbol):
488977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org          return defs.get(symbol, False)
489977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = pp_if
490977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org
491977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name in defs:
492977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = defs[name]
493977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      elif name in extra_variables:
494977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = extra_variables[name]
495977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      else:
496977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        # Undefined variables default to False.
497977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        value = False
49819bf7c06d3192b2ac2fb7a7dd9775bc677636e3ejoi@chromium.org
499977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org      variable_map[name] = value
500977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org
501977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org    eval_result = eval(code, {}, variable_map)
502977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org    assert isinstance(eval_result, bool)
50351dde08ce1ace3fba4e6d64ad401de1f4f45b225joi@chromium.org    return eval_result
504ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org
50501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def EvaluateCondition(self, expr):
50601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns true if and only if the Python expression 'expr' evaluates
50701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    to true.
50801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
50901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    The expression is given a few local variables:
51001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      - 'lang' is the language currently being output
511ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org           (the 'lang' attribute of the <output> element).
512ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      - 'context' is the current output context
513ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org           (the 'context' attribute of the <output> element).
514ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      - 'defs' is a map of C preprocessor-style symbol names to their values.
51501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      - 'os' is the current platform (likely 'linux2', 'win32' or 'darwin').
516ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      - 'pp_ifdef(symbol)' is a shorthand for "symbol in defs".
517ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      - 'pp_if(symbol)' is a shorthand for "symbol in defs and defs[symbol]".
518ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      - 'is_linux', 'is_macosx', 'is_win', 'is_posix' are true if 'os'
519ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org           matches the given platform.
52001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
52101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    root = self.GetRoot()
522ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    lang = getattr(root, 'output_language', '')
523ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    context = getattr(root, 'output_context', '')
524ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    defs = getattr(root, 'defines', {})
525abf28c9e0d607e293a9d790300090e557be6b41ejoi@chromium.org    target_platform = getattr(root, 'target_platform', '')
526977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org    extra_variables = {
527977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        'lang': lang,
528977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org        'context': context,
529977eb64972b3d1f6e24e878fc0858cb33aef25e2newt@chromium.org    }
530ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org    return Node.EvaluateExpression(
531ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org        expr, defs, target_platform, extra_variables)
53201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
53301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def OnlyTheseTranslations(self, languages):
53401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Turns off loading of translations for languages not in the provided list.
53501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
53601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Attrs:
53701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      languages: ['fr', 'zh_cn']
53801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
53901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for node in self:
54001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if (hasattr(node, 'IsTranslation') and
54101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          node.IsTranslation() and
54201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          node.GetLang() not in languages):
54301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        node.DisableLoading()
54401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
545ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org  def FindBooleanAttribute(self, attr, default, skip_self):
546ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    '''Searches all ancestors of the current node for the nearest enclosing
547ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    definition of the given boolean attribute.
548ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org
549ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    Args:
550ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      attr: 'fallback_to_english'
551ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      default: What to return if no node defines the attribute.
552ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      skip_self: Don't check the current node, only its parents.
553ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    '''
554ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    p = self.parent if skip_self else self
555ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    while p:
556ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      value = p.attrs.get(attr, 'default').lower()
557ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      if value != 'default':
558ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org        return (value == 'true')
559ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org      p = p.parent
560ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    return default
561ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org
56201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def PseudoIsAllowed(self):
56301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns true if this node is allowed to use pseudo-translations.  This
56401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    is true by default, unless this node is within a <release> node that has
56501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    the allow_pseudo attribute set to false.
56601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
567ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    return self.FindBooleanAttribute('allow_pseudo',
568ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org                                     default=True, skip_self=True)
56901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
57001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def ShouldFallbackToEnglish(self):
57101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns true iff this node should fall back to English when
57201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    pseudotranslations are disabled and no translation is available for a
57301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    given message.
57401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
575ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    return self.FindBooleanAttribute('fallback_to_english',
576ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org                                     default=False, skip_self=True)
57701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
57801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def WhitelistMarkedAsSkip(self):
57901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns true if the node is marked to be skipped in the output by a
58001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    whitelist.
58101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
58201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self._whitelist_marked_as_skip
58301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
58401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def SetWhitelistMarkedAsSkip(self, mark_skipped):
58501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Sets WhitelistMarkedAsSkip.
58601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
58701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self._whitelist_marked_as_skip = mark_skipped
58801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
58977cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org  def ExpandVariables(self):
59077cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    '''Whether we need to expand variables on a given node.'''
59177cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    return False
59277cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org
59301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
59401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass ContentNode(Node):
59501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''Convenience baseclass for nodes that can have content.'''
59601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def _ContentType(self):
59701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self._CONTENT_TYPE_MIXED
59801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
599