tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

resources_parser.py (6085B)


      1 # Copyright 2020 The Chromium Authors
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import collections
      6 import os
      7 import re
      8 from xml.etree import ElementTree
      9 
     10 from util import build_utils
     11 from util import resource_utils
     12 import action_helpers  # build_utils adds //build to sys.path.
     13 
     14 _TextSymbolEntry = collections.namedtuple(
     15    'RTextEntry', ('java_type', 'resource_type', 'name', 'value'))
     16 
     17 _DUMMY_RTXT_ID = '0x7f010001'
     18 _DUMMY_RTXT_INDEX = '1'
     19 
     20 
     21 def _ResourceNameToJavaSymbol(resource_name):
     22  return re.sub(r'[\.:]', '_', resource_name)
     23 
     24 
     25 class RTxtGenerator:
     26  def __init__(self,
     27               res_dirs,
     28               ignore_pattern=resource_utils.AAPT_IGNORE_PATTERN):
     29    self.res_dirs = res_dirs
     30    self.ignore_pattern = ignore_pattern
     31 
     32  def _ParseDeclareStyleable(self, node):
     33    ret = set()
     34    stylable_name = _ResourceNameToJavaSymbol(node.attrib['name'])
     35    ret.add(
     36        _TextSymbolEntry('int[]', 'styleable', stylable_name,
     37                         '{{{}}}'.format(_DUMMY_RTXT_ID)))
     38    for child in node:
     39      if child.tag == 'eat-comment':
     40        continue
     41      if child.tag != 'attr':
     42        # This parser expects everything inside <declare-stylable/> to be either
     43        # an attr or an eat-comment. If new resource xml files are added that do
     44        # not conform to this, this parser needs updating.
     45        raise Exception('Unexpected tag {} inside <delcare-stylable/>'.format(
     46            child.tag))
     47      entry_name = '{}_{}'.format(
     48          stylable_name, _ResourceNameToJavaSymbol(child.attrib['name']))
     49      ret.add(
     50          _TextSymbolEntry('int', 'styleable', entry_name, _DUMMY_RTXT_INDEX))
     51      if not child.attrib['name'].startswith('android:'):
     52        resource_name = _ResourceNameToJavaSymbol(child.attrib['name'])
     53        ret.add(_TextSymbolEntry('int', 'attr', resource_name, _DUMMY_RTXT_ID))
     54      for entry in child:
     55        if entry.tag not in ('enum', 'flag'):
     56          # This parser expects everything inside <attr/> to be either an
     57          # <enum/> or an <flag/>. If new resource xml files are added that do
     58          # not conform to this, this parser needs updating.
     59          raise Exception('Unexpected tag {} inside <attr/>'.format(entry.tag))
     60        resource_name = _ResourceNameToJavaSymbol(entry.attrib['name'])
     61        ret.add(_TextSymbolEntry('int', 'id', resource_name, _DUMMY_RTXT_ID))
     62    return ret
     63 
     64  def _ExtractNewIdsFromNode(self, node):
     65    ret = set()
     66    # Sometimes there are @+id/ in random attributes (not just in android:id)
     67    # and apparently that is valid. See:
     68    # https://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html
     69    for value in node.attrib.values():
     70      if value.startswith('@+id/'):
     71        resource_name = value[5:]
     72        ret.add(_TextSymbolEntry('int', 'id', resource_name, _DUMMY_RTXT_ID))
     73    for child in node:
     74      ret.update(self._ExtractNewIdsFromNode(child))
     75    return ret
     76 
     77  def _ParseXml(self, xml_path):
     78    try:
     79      return ElementTree.parse(xml_path).getroot()
     80    except Exception as e:
     81      raise RuntimeError('Failure parsing {}:\n'.format(xml_path)) from e
     82 
     83  def _ExtractNewIdsFromXml(self, xml_path):
     84    return self._ExtractNewIdsFromNode(self._ParseXml(xml_path))
     85 
     86  def _ParseValuesXml(self, xml_path):
     87    ret = set()
     88    root = self._ParseXml(xml_path)
     89 
     90    assert root.tag == 'resources'
     91    for child in root:
     92      if child.tag in ('eat-comment', 'skip', 'overlayable', 'macro'):
     93        # These tags do not create real resources
     94        continue
     95      if child.tag == 'declare-styleable':
     96        ret.update(self._ParseDeclareStyleable(child))
     97      else:
     98        if child.tag in ('item', 'public'):
     99          resource_type = child.attrib['type']
    100        elif child.tag in ('array', 'integer-array', 'string-array'):
    101          resource_type = 'array'
    102        else:
    103          resource_type = child.tag
    104        parsed_element = ElementTree.tostring(child, encoding='unicode').strip()
    105        assert resource_type in resource_utils.ALL_RESOURCE_TYPES, (
    106            f'Infered resource type ({resource_type}) from xml entry '
    107            f'({parsed_element}) (found in {xml_path}) is not listed in '
    108            'resource_utils.ALL_RESOURCE_TYPES. Teach resources_parser.py how '
    109            'to parse this entry and then add to the list.')
    110        name = _ResourceNameToJavaSymbol(child.attrib['name'])
    111        ret.add(_TextSymbolEntry('int', resource_type, name, _DUMMY_RTXT_ID))
    112    return ret
    113 
    114  def _CollectResourcesListFromDirectory(self, res_dir):
    115    ret = set()
    116    globs = resource_utils._GenerateGlobs(self.ignore_pattern)
    117    for root, _, files in os.walk(res_dir):
    118      resource_type = os.path.basename(root)
    119      if '-' in resource_type:
    120        resource_type = resource_type[:resource_type.index('-')]
    121      for f in files:
    122        if build_utils.MatchesGlob(f, globs):
    123          continue
    124        if resource_type == 'values':
    125          ret.update(self._ParseValuesXml(os.path.join(root, f)))
    126        else:
    127          if '.' in f:
    128            resource_name = f[:f.index('.')]
    129          else:
    130            resource_name = f
    131          ret.add(
    132              _TextSymbolEntry('int', resource_type, resource_name,
    133                               _DUMMY_RTXT_ID))
    134          # Other types not just layouts can contain new ids (eg: Menus and
    135          # Drawables). Just in case, look for new ids in all files.
    136          if f.endswith('.xml'):
    137            ret.update(self._ExtractNewIdsFromXml(os.path.join(root, f)))
    138    return ret
    139 
    140  def _CollectResourcesListFromDirectories(self):
    141    ret = set()
    142    for res_dir in self.res_dirs:
    143      ret.update(self._CollectResourcesListFromDirectory(res_dir))
    144    return sorted(ret)
    145 
    146  def WriteRTxtFile(self, rtxt_path):
    147    resources = self._CollectResourcesListFromDirectories()
    148    with action_helpers.atomic_output(rtxt_path, mode='w') as f:
    149      for resource in resources:
    150        line = '{0.java_type} {0.resource_type} {0.name} {0.value}\n'.format(
    151            resource)
    152        f.write(line)