| Index: grit/node/base.py
 | 
| ===================================================================
 | 
| --- grit/node/base.py	(revision 0)
 | 
| +++ grit/node/base.py	(revision 0)
 | 
| @@ -0,0 +1,549 @@
 | 
| +#!/usr/bin/python2.4
 | 
| +# Copyright (c) 2011 The Chromium Authors. All rights reserved.
 | 
| +# Use of this source code is governed by a BSD-style license that can be
 | 
| +# found in the LICENSE file.
 | 
| +
 | 
| +'''Base types for nodes in a GRIT resource tree.
 | 
| +'''
 | 
| +
 | 
| +import os
 | 
| +import sys
 | 
| +import types
 | 
| +from xml.sax import saxutils
 | 
| +
 | 
| +from grit import exception
 | 
| +from grit import util
 | 
| +from grit import clique
 | 
| +import grit.format.interface
 | 
| +
 | 
| +
 | 
| +class Node(grit.format.interface.ItemFormatter):
 | 
| +  '''An item in the tree that has children.  Also implements the
 | 
| +  ItemFormatter interface to allow formatting a node as a GRD document.'''
 | 
| +
 | 
| +  # Valid content types that can be returned by _ContentType()
 | 
| +  _CONTENT_TYPE_NONE = 0   # No CDATA content but may have children
 | 
| +  _CONTENT_TYPE_CDATA = 1  # Only CDATA, no children.
 | 
| +  _CONTENT_TYPE_MIXED = 2  # CDATA and children, possibly intermingled
 | 
| +
 | 
| +  # Default nodes to not whitelist skipped
 | 
| +  _whitelist_marked_as_skip = False
 | 
| +
 | 
| +  def __init__(self):
 | 
| +    self.children = []        # A list of child elements
 | 
| +    self.mixed_content = []   # A list of u'' and/or child elements (this
 | 
| +                              # duplicates 'children' but
 | 
| +                              # is needed to preserve markup-type content).
 | 
| +    self.name = u''           # The name of this element
 | 
| +    self.attrs = {}           # The set of attributes (keys to values)
 | 
| +    self.parent = None        # Our parent unless we are the root element.
 | 
| +    self.uberclique = None    # Allows overriding uberclique for parts of tree
 | 
| +
 | 
| +  def __iter__(self):
 | 
| +    '''An in-order iteration through the tree that this node is the
 | 
| +    root of.'''
 | 
| +    return self.inorder()
 | 
| +
 | 
| +  def inorder(self):
 | 
| +    '''Generator that generates first this node, then the same generator for
 | 
| +    any child nodes.'''
 | 
| +    yield self
 | 
| +    for child in self.children:
 | 
| +      for iterchild in child.inorder():
 | 
| +        yield iterchild
 | 
| +
 | 
| +  def GetRoot(self):
 | 
| +    '''Returns the root Node in the tree this Node belongs to.'''
 | 
| +    curr = self
 | 
| +    while curr.parent:
 | 
| +      curr = curr.parent
 | 
| +    return curr
 | 
| +
 | 
| +    # TODO(joi) Use this (currently untested) optimization?:
 | 
| +    #if hasattr(self, '_root'):
 | 
| +    #  return self._root
 | 
| +    #curr = self
 | 
| +    #while curr.parent and not hasattr(curr, '_root'):
 | 
| +    #  curr = curr.parent
 | 
| +    #if curr.parent:
 | 
| +    #  self._root = curr._root
 | 
| +    #else:
 | 
| +    #  self._root = curr
 | 
| +    #return self._root
 | 
| +
 | 
| +  def StartParsing(self, name, parent):
 | 
| +    '''Called at the start of parsing.
 | 
| +
 | 
| +    Args:
 | 
| +      name: u'elementname'
 | 
| +      parent: grit.node.base.Node or subclass or None
 | 
| +    '''
 | 
| +    assert isinstance(name, types.StringTypes)
 | 
| +    assert not parent or isinstance(parent, Node)
 | 
| +    self.name = name
 | 
| +    self.parent = parent
 | 
| +
 | 
| +  def AddChild(self, child):
 | 
| +    '''Adds a child to the list of children of this node, if it is a valid
 | 
| +    child for the node.'''
 | 
| +    assert isinstance(child, Node)
 | 
| +    if (not self._IsValidChild(child) or
 | 
| +        self._ContentType() == self._CONTENT_TYPE_CDATA):
 | 
| +      if child.parent:
 | 
| +        explanation = 'child %s of parent %s' % (child.name, child.parent.name)
 | 
| +      else:
 | 
| +        explanation = 'node %s with no parent' % child.name
 | 
| +      raise exception.UnexpectedChild(explanation)
 | 
| +    self.children.append(child)
 | 
| +    self.mixed_content.append(child)
 | 
| +
 | 
| +  def RemoveChild(self, child_id):
 | 
| +    '''Removes the first node that has a "name" attribute which
 | 
| +    matches "child_id" in the list of immediate children of
 | 
| +    this node.
 | 
| +
 | 
| +    Args:
 | 
| +      child_id: String identifying the child to be removed
 | 
| +    '''
 | 
| +    index = 0
 | 
| +    # Safe not to copy since we only remove the first element found
 | 
| +    for child in self.children:
 | 
| +      name_attr = child.attrs['name']
 | 
| +      if name_attr == child_id:
 | 
| +        self.children.pop(index)
 | 
| +        self.mixed_content.pop(index)
 | 
| +        break
 | 
| +      index += 1
 | 
| +
 | 
| +  def AppendContent(self, content):
 | 
| +    '''Appends a chunk of text as content of this node.
 | 
| +
 | 
| +    Args:
 | 
| +      content: u'hello'
 | 
| +
 | 
| +    Return:
 | 
| +      None
 | 
| +    '''
 | 
| +    assert isinstance(content, types.StringTypes)
 | 
| +    if self._ContentType() != self._CONTENT_TYPE_NONE:
 | 
| +      self.mixed_content.append(content)
 | 
| +    elif content.strip() != '':
 | 
| +      raise exception.UnexpectedContent()
 | 
| +
 | 
| +  def HandleAttribute(self, attrib, value):
 | 
| +    '''Informs the node of an attribute that was parsed out of the GRD file
 | 
| +    for it.
 | 
| +
 | 
| +    Args:
 | 
| +      attrib: 'name'
 | 
| +      value: 'fooblat'
 | 
| +
 | 
| +    Return:
 | 
| +      None
 | 
| +    '''
 | 
| +    assert isinstance(attrib, types.StringTypes)
 | 
| +    assert isinstance(value, types.StringTypes)
 | 
| +    if self._IsValidAttribute(attrib, value):
 | 
| +      self.attrs[attrib] = value
 | 
| +    else:
 | 
| +      raise exception.UnexpectedAttribute(attrib)
 | 
| +
 | 
| +  def EndParsing(self):
 | 
| +    '''Called at the end of parsing.'''
 | 
| +
 | 
| +    # TODO(joi) Rewrite this, it's extremely ugly!
 | 
| +    if len(self.mixed_content):
 | 
| +      if isinstance(self.mixed_content[0], types.StringTypes):
 | 
| +        # Remove leading and trailing chunks of pure whitespace.
 | 
| +        while (len(self.mixed_content) and
 | 
| +               isinstance(self.mixed_content[0], types.StringTypes) and
 | 
| +               self.mixed_content[0].strip() == ''):
 | 
| +          self.mixed_content = self.mixed_content[1:]
 | 
| +        # Strip leading and trailing whitespace from mixed content chunks
 | 
| +        # at front and back.
 | 
| +        if (len(self.mixed_content) and
 | 
| +            isinstance(self.mixed_content[0], types.StringTypes)):
 | 
| +          self.mixed_content[0] = self.mixed_content[0].lstrip()
 | 
| +        # Remove leading and trailing ''' (used to demarcate whitespace)
 | 
| +        if (len(self.mixed_content) and
 | 
| +            isinstance(self.mixed_content[0], types.StringTypes)):
 | 
| +          if self.mixed_content[0].startswith("'''"):
 | 
| +            self.mixed_content[0] = self.mixed_content[0][3:]
 | 
| +    if len(self.mixed_content):
 | 
| +      if isinstance(self.mixed_content[-1], types.StringTypes):
 | 
| +        # Same stuff all over again for the tail end.
 | 
| +        while (len(self.mixed_content) and
 | 
| +               isinstance(self.mixed_content[-1], types.StringTypes) and
 | 
| +               self.mixed_content[-1].strip() == ''):
 | 
| +          self.mixed_content = self.mixed_content[:-1]
 | 
| +        if (len(self.mixed_content) and
 | 
| +            isinstance(self.mixed_content[-1], types.StringTypes)):
 | 
| +          self.mixed_content[-1] = self.mixed_content[-1].rstrip()
 | 
| +        if (len(self.mixed_content) and
 | 
| +            isinstance(self.mixed_content[-1], types.StringTypes)):
 | 
| +          if self.mixed_content[-1].endswith("'''"):
 | 
| +            self.mixed_content[-1] = self.mixed_content[-1][:-3]
 | 
| +
 | 
| +    # Check that all mandatory attributes are there.
 | 
| +    for node_mandatt in self.MandatoryAttributes():
 | 
| +      mandatt_list = []
 | 
| +      if node_mandatt.find('|') >= 0:
 | 
| +        mandatt_list = node_mandatt.split('|')
 | 
| +      else:
 | 
| +        mandatt_list.append(node_mandatt)
 | 
| +
 | 
| +      mandatt_option_found = False
 | 
| +      for mandatt in mandatt_list:
 | 
| +        assert mandatt not in self.DefaultAttributes().keys()
 | 
| +        if mandatt in self.attrs:
 | 
| +          if not mandatt_option_found:
 | 
| +            mandatt_option_found = True
 | 
| +          else:
 | 
| +            raise exception.MutuallyExclusiveMandatoryAttribute(mandatt)
 | 
| +
 | 
| +      if not mandatt_option_found:
 | 
| +        raise exception.MissingMandatoryAttribute(mandatt)
 | 
| +
 | 
| +    # Add default attributes if not specified in input file.
 | 
| +    for defattr in self.DefaultAttributes():
 | 
| +      if not defattr in self.attrs:
 | 
| +        self.attrs[defattr] = self.DefaultAttributes()[defattr]
 | 
| +
 | 
| +  def GetCdata(self):
 | 
| +    '''Returns all CDATA of this element, concatenated into a single
 | 
| +    string.  Note that this ignores any elements embedded in CDATA.'''
 | 
| +    return ''.join(filter(lambda c: isinstance(c, types.StringTypes),
 | 
| +                          self.mixed_content))
 | 
| +
 | 
| +  def __unicode__(self):
 | 
| +    '''Returns this node and all nodes below it as an XML document in a Unicode
 | 
| +    string.'''
 | 
| +    header = u'<?xml version="1.0" encoding="UTF-8"?>\n'
 | 
| +    return header + self.FormatXml()
 | 
| +
 | 
| +  # Compliance with ItemFormatter interface.
 | 
| +  def Format(self, item, lang_re = None, begin_item=True):
 | 
| +    if not begin_item:
 | 
| +      return ''
 | 
| +    else:
 | 
| +      return item.FormatXml()
 | 
| +
 | 
| +  def FormatXml(self, indent = u'', one_line = False):
 | 
| +    '''Returns this node and all nodes below it as an XML
 | 
| +    element in a Unicode string.  This differs from __unicode__ in that it does
 | 
| +    not include the <?xml> stuff at the top of the string.  If one_line is true,
 | 
| +    children and CDATA are layed out in a way that preserves internal
 | 
| +    whitespace.
 | 
| +    '''
 | 
| +    assert isinstance(indent, types.StringTypes)
 | 
| +
 | 
| +    content_one_line = (one_line or
 | 
| +                        self._ContentType() == self._CONTENT_TYPE_MIXED)
 | 
| +    inside_content = self.ContentsAsXml(indent, content_one_line)
 | 
| +
 | 
| +    # Then the attributes for this node.
 | 
| +    attribs = u' '
 | 
| +    for (attrib, value) in self.attrs.iteritems():
 | 
| +      # Only print an attribute if it is other than the default value.
 | 
| +      if (not self.DefaultAttributes().has_key(attrib) or
 | 
| +          value != self.DefaultAttributes()[attrib]):
 | 
| +        attribs += u'%s=%s ' % (attrib, saxutils.quoteattr(value))
 | 
| +    attribs = attribs.rstrip()  # if no attribs, we end up with '', otherwise
 | 
| +                                # we end up with a space-prefixed string
 | 
| +
 | 
| +    # Finally build the XML for our node and return it
 | 
| +    if len(inside_content) > 0:
 | 
| +      if one_line:
 | 
| +        return u'<%s%s>%s</%s>' % (self.name, attribs, inside_content, self.name)
 | 
| +      elif content_one_line:
 | 
| +        return u'%s<%s%s>\n%s  %s\n%s</%s>' % (
 | 
| +          indent, self.name, attribs,
 | 
| +          indent, inside_content,
 | 
| +          indent, self.name)
 | 
| +      else:
 | 
| +        return u'%s<%s%s>\n%s\n%s</%s>' % (
 | 
| +          indent, self.name, attribs,
 | 
| +          inside_content,
 | 
| +          indent, self.name)
 | 
| +    else:
 | 
| +      return u'%s<%s%s />' % (indent, self.name, attribs)
 | 
| +
 | 
| +  def ContentsAsXml(self, indent, one_line):
 | 
| +    '''Returns the contents of this node (CDATA and child elements) in XML
 | 
| +    format.  If 'one_line' is true, the content will be laid out on one line.'''
 | 
| +    assert isinstance(indent, types.StringTypes)
 | 
| +
 | 
| +    # Build the contents of the element.
 | 
| +    inside_parts = []
 | 
| +    last_item = None
 | 
| +    for mixed_item in self.mixed_content:
 | 
| +      if isinstance(mixed_item, Node):
 | 
| +        inside_parts.append(mixed_item.FormatXml(indent + u'  ', one_line))
 | 
| +        if not one_line:
 | 
| +          inside_parts.append(u'\n')
 | 
| +      else:
 | 
| +        message = mixed_item
 | 
| +        # If this is the first item and it starts with whitespace, we add
 | 
| +        # the ''' delimiter.
 | 
| +        if not last_item and message.lstrip() != message:
 | 
| +          message = u"'''" + message
 | 
| +        inside_parts.append(util.EncodeCdata(message))
 | 
| +      last_item = mixed_item
 | 
| +
 | 
| +    # If there are only child nodes and no cdata, there will be a spurious
 | 
| +    # trailing \n
 | 
| +    if len(inside_parts) and inside_parts[-1] == '\n':
 | 
| +      inside_parts = inside_parts[:-1]
 | 
| +
 | 
| +    # If the last item is a string (not a node) and ends with whitespace,
 | 
| +    # we need to add the ''' delimiter.
 | 
| +    if (isinstance(last_item, types.StringTypes) and
 | 
| +        last_item.rstrip() != last_item):
 | 
| +      inside_parts[-1] = inside_parts[-1] + u"'''"
 | 
| +
 | 
| +    return u''.join(inside_parts)
 | 
| +
 | 
| +  def RunGatherers(self, recursive=0, debug=False):
 | 
| +    '''Runs all gatherers on this object, which may add to the data stored
 | 
| +    by the object.  If 'recursive' is true, will call RunGatherers() recursively
 | 
| +    on all child nodes first.  If 'debug' is True, will print out information
 | 
| +    as it is running each nodes' gatherers.
 | 
| +
 | 
| +    Gatherers for <translations> child nodes will always be run after all other
 | 
| +    child nodes have been gathered.
 | 
| +    '''
 | 
| +    if recursive:
 | 
| +      process_last = []
 | 
| +      for child in self.children:
 | 
| +        if child.name == 'translations':
 | 
| +          process_last.append(child)
 | 
| +        else:
 | 
| +          child.RunGatherers(recursive=recursive, debug=debug)
 | 
| +      for child in process_last:
 | 
| +        child.RunGatherers(recursive=recursive, debug=debug)
 | 
| +
 | 
| +  def ItemFormatter(self, type):
 | 
| +    '''Returns an instance of the item formatter for this object of the
 | 
| +    specified type, or None if not supported.
 | 
| +
 | 
| +    Args:
 | 
| +      type: 'rc-header'
 | 
| +
 | 
| +    Return:
 | 
| +      (object RcHeaderItemFormatter)
 | 
| +    '''
 | 
| +    if type == 'xml':
 | 
| +      return self
 | 
| +    else:
 | 
| +      return None
 | 
| +
 | 
| +  def SatisfiesOutputCondition(self):
 | 
| +    '''Returns true if this node is either not a child of an <if> element
 | 
| +    or if it is a child of an <if> element and the conditions for it being
 | 
| +    output are satisfied.
 | 
| +
 | 
| +    Used to determine whether to return item formatters for formats that
 | 
| +    obey conditional output of resources (e.g. the RC formatters).
 | 
| +    '''
 | 
| +    from grit.node import misc
 | 
| +    if not self.parent or not isinstance(self.parent, misc.IfNode):
 | 
| +      return True
 | 
| +    else:
 | 
| +      return self.parent.IsConditionSatisfied()
 | 
| +
 | 
| +  def _IsValidChild(self, child):
 | 
| +    '''Returns true if 'child' is a valid child of this node.
 | 
| +    Overridden by subclasses.'''
 | 
| +    return False
 | 
| +
 | 
| +  def _IsValidAttribute(self, name, value):
 | 
| +    '''Returns true if 'name' is the name of a valid attribute of this element
 | 
| +    and 'value' is a valid value for that attribute.  Overriden by
 | 
| +    subclasses unless they have only mandatory attributes.'''
 | 
| +    return (name in self.MandatoryAttributes() or
 | 
| +            name in self.DefaultAttributes())
 | 
| +
 | 
| +  def _ContentType(self):
 | 
| +    '''Returns the type of content this element can have.  Overridden by
 | 
| +    subclasses.  The content type can be one of the _CONTENT_TYPE_XXX constants
 | 
| +    above.'''
 | 
| +    return self._CONTENT_TYPE_NONE
 | 
| +
 | 
| +  def MandatoryAttributes(self):
 | 
| +    '''Returns a list of attribute names that are mandatory (non-optional)
 | 
| +    on the current element. One can specify a list of
 | 
| +    "mutually exclusive mandatory" attributes by specifying them as one
 | 
| +    element in the list, separated by a "|" character.
 | 
| +    '''
 | 
| +    return []
 | 
| +
 | 
| +  def DefaultAttributes(self):
 | 
| +    '''Returns a dictionary of attribute names that have defaults, mapped to
 | 
| +    the default value.  Overridden by subclasses.'''
 | 
| +    return {}
 | 
| +
 | 
| +  def GetCliques(self):
 | 
| +    '''Returns all MessageClique objects belonging to this node.  Overridden
 | 
| +    by subclasses.
 | 
| +
 | 
| +    Return:
 | 
| +      [clique1, clique2] or []
 | 
| +    '''
 | 
| +    return []
 | 
| +
 | 
| +  def ToRealPath(self, path_from_basedir):
 | 
| +    '''Returns a real path (which can be absolute or relative to the current
 | 
| +    working directory), given a path that is relative to the base directory
 | 
| +    set for the GRIT input file.
 | 
| +
 | 
| +    Args:
 | 
| +      path_from_basedir: '..'
 | 
| +
 | 
| +    Return:
 | 
| +      'resource'
 | 
| +    '''
 | 
| +    return util.normpath(os.path.join(self.GetRoot().GetBaseDir(),
 | 
| +                                      path_from_basedir))
 | 
| +
 | 
| +  def FilenameToOpen(self):
 | 
| +    '''Returns a path, either absolute or relative to the current working
 | 
| +    directory, that points to the file the node refers to.  This is only valid
 | 
| +    for nodes that have a 'file' or 'path' attribute.  Note that the attribute
 | 
| +    is a path to the file relative to the 'base-dir' of the .grd file, whereas
 | 
| +    this function returns a path that can be used to open the file.'''
 | 
| +    file_attribute = 'file'
 | 
| +    if not file_attribute in self.attrs:
 | 
| +      file_attribute = 'path'
 | 
| +    return self.ToRealPath(self.attrs[file_attribute])
 | 
| +
 | 
| +  def UberClique(self):
 | 
| +    '''Returns the uberclique that should be used for messages originating in
 | 
| +    a given node.  If the node itself has its uberclique set, that is what we
 | 
| +    use, otherwise we search upwards until we find one.  If we do not find one
 | 
| +    even at the root node, we set the root node's uberclique to a new
 | 
| +    uberclique instance.
 | 
| +    '''
 | 
| +    node = self
 | 
| +    while not node.uberclique and node.parent:
 | 
| +      node = node.parent
 | 
| +    if not node.uberclique:
 | 
| +      node.uberclique = clique.UberClique()
 | 
| +    return node.uberclique
 | 
| +
 | 
| +  def IsTranslateable(self):
 | 
| +    '''Returns false if the node has contents that should not be translated,
 | 
| +    otherwise returns false (even if the node has no contents).
 | 
| +    '''
 | 
| +    if not 'translateable' in self.attrs:
 | 
| +      return True
 | 
| +    else:
 | 
| +      return self.attrs['translateable'] == 'true'
 | 
| +
 | 
| +  def GetNodeById(self, id):
 | 
| +    '''Returns the node in the subtree parented by this node that has a 'name'
 | 
| +    attribute matching 'id'.  Returns None if no such node is found.
 | 
| +    '''
 | 
| +    for node in self:
 | 
| +      if 'name' in node.attrs and node.attrs['name'] == id:
 | 
| +        return node
 | 
| +    return None
 | 
| +
 | 
| +  def GetTextualIds(self):
 | 
| +    '''Returns the textual ids of this node, if it has some.
 | 
| +    Otherwise it just returns None.
 | 
| +    '''
 | 
| +    if 'name' in self.attrs:
 | 
| +      return [self.attrs['name']]
 | 
| +    return None
 | 
| +
 | 
| +  def EvaluateCondition(self, expr):
 | 
| +    '''Returns true if and only if the Python expression 'expr' evaluates
 | 
| +    to true.
 | 
| +
 | 
| +    The expression is given a few local variables:
 | 
| +      - 'lang' is the language currently being output
 | 
| +      - 'defs' is a map of C preprocessor-style define names to their values
 | 
| +      - 'os' is the current platform (likely 'linux2', 'win32' or 'darwin').
 | 
| +      - 'pp_ifdef(define)' which behaves just like the C preprocessors #ifdef,
 | 
| +        i.e. it is shorthand for "define in defs"
 | 
| +      - 'pp_if(define)' which behaves just like the C preprocessor's #if, i.e.
 | 
| +        it is shorthand for "define in defs and defs[define]".
 | 
| +    '''
 | 
| +    root = self.GetRoot()
 | 
| +    lang = ''
 | 
| +    defs = {}
 | 
| +    def pp_ifdef(define):
 | 
| +      return define in defs
 | 
| +    def pp_if(define):
 | 
| +      return define in defs and defs[define]
 | 
| +    if hasattr(root, 'output_language'):
 | 
| +      lang = root.output_language
 | 
| +    if hasattr(root, 'defines'):
 | 
| +      defs = root.defines
 | 
| +    variable_map = {
 | 
| +        'lang' : lang,
 | 
| +        'defs' : defs,
 | 
| +        'os': sys.platform,
 | 
| +        'is_linux': sys.platform.startswith('linux'),
 | 
| +        'is_macosx': sys.platform == 'darwin',
 | 
| +        'is_win': sys.platform in ('cygwin', 'win32'),
 | 
| +        'is_posix': (sys.platform in ('darwin', 'linux2', 'linux3', 'sunos5')
 | 
| +                     or sys.platform.find('bsd') != -1),
 | 
| +        'pp_ifdef' : pp_ifdef,
 | 
| +        'pp_if' : pp_if,
 | 
| +    }
 | 
| +    return eval(expr, {}, variable_map)
 | 
| +
 | 
| +  def OnlyTheseTranslations(self, languages):
 | 
| +    '''Turns off loading of translations for languages not in the provided list.
 | 
| +
 | 
| +    Attrs:
 | 
| +      languages: ['fr', 'zh_cn']
 | 
| +    '''
 | 
| +    for node in self:
 | 
| +      if (hasattr(node, 'IsTranslation') and
 | 
| +          node.IsTranslation() and
 | 
| +          node.GetLang() not in languages):
 | 
| +        node.DisableLoading()
 | 
| +
 | 
| +  def PseudoIsAllowed(self):
 | 
| +    '''Returns true if this node is allowed to use pseudo-translations.  This
 | 
| +    is true by default, unless this node is within a <release> node that has
 | 
| +    the allow_pseudo attribute set to false.
 | 
| +    '''
 | 
| +    p = self.parent
 | 
| +    while p:
 | 
| +      if 'allow_pseudo' in p.attrs:
 | 
| +        return (p.attrs['allow_pseudo'].lower() == 'true')
 | 
| +      p = p.parent
 | 
| +    return True
 | 
| +
 | 
| +  def ShouldFallbackToEnglish(self):
 | 
| +    '''Returns true iff this node should fall back to English when
 | 
| +    pseudotranslations are disabled and no translation is available for a
 | 
| +    given message.
 | 
| +    '''
 | 
| +    p = self.parent
 | 
| +    while p:
 | 
| +      if 'fallback_to_english' in p.attrs:
 | 
| +        return (p.attrs['fallback_to_english'].lower() == 'true')
 | 
| +      p = p.parent
 | 
| +    return False
 | 
| +
 | 
| +  def WhitelistMarkedAsSkip(self):
 | 
| +    '''Returns true if the node is marked to be skipped in the output by a
 | 
| +    whitelist.
 | 
| +    '''
 | 
| +    return self._whitelist_marked_as_skip
 | 
| +
 | 
| +  def SetWhitelistMarkedAsSkip(self, mark_skipped):
 | 
| +    '''Sets WhitelistMarkedAsSkip.
 | 
| +    '''
 | 
| +    self._whitelist_marked_as_skip = mark_skipped
 | 
| +
 | 
| +
 | 
| +class ContentNode(Node):
 | 
| +  '''Convenience baseclass for nodes that can have content.'''
 | 
| +  def _ContentType(self):
 | 
| +    return self._CONTENT_TYPE_MIXED
 | 
| +
 | 
| 
 | 
| Property changes on: grit/node/base.py
 | 
| ___________________________________________________________________
 | 
| Added: svn:eol-style
 | 
|    + LF
 | 
| 
 | 
| 
 |