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'''Item formatters for RC headers.
7'''
8
9from grit import exception
10from grit import util
11from grit.extern import FP
12
13
14def Format(root, lang='en', output_dir='.'):
15  yield '''\
16// This file is automatically generated by GRIT. Do not edit.
17
18#pragma once
19'''
20  # Check for emit nodes under the rc_header. If any emit node
21  # is present, we assume it means the GRD file wants to override
22  # the default header, with no includes.
23  default_includes = ['#include <atlres.h>', '']
24  emit_lines = []
25  for output_node in root.GetOutputFiles():
26    if output_node.GetType() == 'rc_header':
27      for child in output_node.children:
28        if child.name == 'emit' and child.attrs['emit_type'] == 'prepend':
29          emit_lines.append(child.GetCdata())
30  for line in emit_lines or default_includes:
31    yield line + '\n'
32
33  for line in FormatDefines(root, root.ShouldOutputAllResourceDefines(),
34                            root.GetRcHeaderFormat()):
35    yield line
36
37
38def FormatDefines(root, output_all_resource_defines=True,
39                  rc_header_format=None):
40  '''Yields #define SYMBOL 1234 lines.
41
42  Args:
43    root: A GritNode.
44    output_all_resource_defines: If False, output only the symbols used in the
45      current output configuration.
46  '''
47  from grit.node import message
48  tids = GetIds(root)
49
50  if output_all_resource_defines:
51    items = root.Preorder()
52  else:
53    items = root.ActiveDescendants()
54
55  if not rc_header_format:
56    rc_header_format = "#define {textual_id} {numeric_id}"
57  rc_header_format += "\n"
58  seen = set()
59  for item in items:
60    if not isinstance(item, message.MessageNode):
61      with item:
62        for tid in item.GetTextualIds():
63          if tid in tids and tid not in seen:
64            seen.add(tid)
65            yield rc_header_format.format(textual_id=tid,numeric_id=tids[tid])
66
67  # Temporarily mimic old behavior: MessageNodes were only output if active,
68  # even with output_all_resource_defines set. TODO(benrg): Remove this after
69  # fixing problems in the Chrome tree.
70  for item in root.ActiveDescendants():
71    if isinstance(item, message.MessageNode):
72      with item:
73        for tid in item.GetTextualIds():
74          if tid in tids and tid not in seen:
75            seen.add(tid)
76            yield rc_header_format.format(textual_id=tid,numeric_id=tids[tid])
77
78
79_cached_ids = {}
80
81
82def GetIds(root):
83  '''Return a dictionary mapping textual ids to numeric ids for the given tree.
84
85  Args:
86    root: A GritNode.
87  '''
88  # TODO(benrg): Since other formatters use this, it might make sense to move it
89  # and _ComputeIds to GritNode and store the cached ids as an attribute. On the
90  # other hand, GritNode has too much random stuff already.
91  if root not in _cached_ids:
92    _cached_ids[root] = _ComputeIds(root)
93  return _cached_ids[root]
94
95
96def _ComputeIds(root):
97  from grit.node import empty, include, message, misc, structure
98
99  ids = {}  # Maps numeric id to textual id
100  tids = {}  # Maps textual id to numeric id
101  id_reasons = {}  # Maps numeric id to text id and a human-readable explanation
102  group = None
103  last_id = None
104
105  for item in root:
106    if isinstance(item, empty.GroupingNode):
107      # Note: this won't work if any GroupingNode can be contained inside
108      # another.
109      group = item
110      last_id = None
111      continue
112
113    assert not item.GetTextualIds() or isinstance(item,
114        (include.IncludeNode, message.MessageNode,
115         misc.IdentifierNode, structure.StructureNode))
116
117    # Resources that use the RES protocol don't need
118    # any numerical ids generated, so we skip them altogether.
119    # This is accomplished by setting the flag 'generateid' to false
120    # in the GRD file.
121    if item.attrs.get('generateid', 'true') == 'false':
122      continue
123
124    for tid in item.GetTextualIds():
125      if util.SYSTEM_IDENTIFIERS.match(tid):
126        # Don't emit a new ID for predefined IDs
127        continue
128
129      if tid in tids:
130        continue
131
132      # Some identifier nodes can provide their own id,
133      # and we use that id in the generated header in that case.
134      if hasattr(item, 'GetId') and item.GetId():
135        id = long(item.GetId())
136        reason = 'returned by GetId() method'
137
138      elif ('offset' in item.attrs and group and
139            group.attrs.get('first_id', '') != ''):
140         offset_text = item.attrs['offset']
141         parent_text = group.attrs['first_id']
142
143         try:
144          offset_id = long(offset_text)
145         except ValueError:
146          offset_id = tids[offset_text]
147
148         try:
149          parent_id = long(parent_text)
150         except ValueError:
151          parent_id = tids[parent_text]
152
153         id = parent_id + offset_id
154         reason = 'first_id %d + offset %d' % (parent_id, offset_id)
155
156      # We try to allocate IDs sequentially for blocks of items that might
157      # be related, for instance strings in a stringtable (as their IDs might be
158      # used e.g. as IDs for some radio buttons, in which case the IDs must
159      # be sequential).
160      #
161      # We do this by having the first item in a section store its computed ID
162      # (computed from a fingerprint) in its parent object.  Subsequent children
163      # of the same parent will then try to get IDs that sequentially follow
164      # the currently stored ID (on the parent) and increment it.
165      elif last_id is None:
166        # First check if the starting ID is explicitly specified by the parent.
167        if group and group.attrs.get('first_id', '') != '':
168          id = long(group.attrs['first_id'])
169          reason = "from parent's first_id attribute"
170        else:
171          # Automatically generate the ID based on the first clique from the
172          # first child of the first child node of our parent (i.e. when we
173          # first get to this location in the code).
174
175          # According to
176          # http://msdn.microsoft.com/en-us/library/t2zechd4(VS.71).aspx
177          # the safe usable range for resource IDs in Windows is from decimal
178          # 101 to 0x7FFF.
179
180          id = FP.UnsignedFingerPrint(tid)
181          id = id % (0x7FFF - 101) + 101
182          reason = 'chosen by random fingerprint -- use first_id to override'
183
184        last_id = id
185      else:
186        id = last_id = last_id + 1
187        reason = 'sequentially assigned'
188
189      reason = "%s (%s)" % (tid, reason)
190      # Don't fail when 'offset' is specified, as the base and the 0th
191      # offset will have the same ID.
192      if id in id_reasons and not 'offset' in item.attrs:
193        raise exception.IdRangeOverlap('ID %d was assigned to both %s and %s.'
194                                       % (id, id_reasons[id], reason))
195
196      if id < 101:
197        print ('WARNING: Numeric resource IDs should be greater than 100 to\n'
198               'avoid conflicts with system-defined resource IDs.')
199
200      ids[id] = tid
201      tids[tid] = id
202      id_reasons[id] = reason
203
204  return tids
205