• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#  Copyright (C) 2021 The Android Open Source Project
3#
4#  Licensed under the Apache License, Version 2.0 (the "License");
5#  you may not use this file except in compliance with the License.
6#  You may obtain a copy of the License at
7#
8#       http://www.apache.org/licenses/LICENSE-2.0
9#
10#  Unless required by applicable law or agreed to in writing, software
11#  distributed under the License is distributed on an "AS IS" BASIS,
12#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13#  See the License for the specific language governing permissions and
14#  limitations under the License.
15
16import os
17import re
18import lxml.etree as etree
19
20class ResourceLocation:
21    def __init__(self, file, line=None):
22        self.file = file
23        self.line = line
24    def __str__(self):
25        if self.line is not None:
26            return self.file + ':' + str(self.line)
27        else:
28            return self.file
29
30class Resource:
31    def __init__(self, name, type, location=None):
32        self.name = name
33        self.type = type
34        self.locations = []
35        if location is not None:
36            self.locations.append(location)
37    def __eq__(self, other):
38        if isinstance(other, _Grab):
39            return other == self
40        return self.name == other.name and self.type == other.type
41    def __ne__(self, other):
42        if isinstance(other, _Grab):
43            return other != self
44        return self.name != other.name or self.type != other.type
45    def __hash__(self):
46        return hash((self.name, self.type))
47    def __str__(self):
48        result = ''
49        for location in self.locations:
50            result += str(location) + ': '
51        result += '<'+self.type+' name="'+self.name+'"'
52        return result + '>'
53    def __repr__(self):
54        return str(self)
55
56def get_all_resources(resDir, excluded_resource_files=[]):
57    excluded_resource_files = [os.path.abspath(file) for file in excluded_resource_files]
58    allResDirs = [f for f in os.listdir(resDir) if os.path.isdir(os.path.join(resDir, f))]
59    valuesDirs = [f for f in allResDirs if f.startswith('values')]
60    fileDirs = [f for f in allResDirs if not f.startswith('values')]
61    resources = set()
62    # Get the filenames of the all the files in all the fileDirs
63    for dir in fileDirs:
64        type = dir.split('-')[0]
65        for file in os.listdir(os.path.join(resDir, dir)):
66            filePath = os.path.abspath(os.path.join(resDir, dir, file))
67            if file.endswith('.xml') and filePath not in excluded_resource_files:
68                add_resource_to_set(resources,
69                                    Resource(file[:-4], type,
70                                             ResourceLocation(os.path.join(resDir, dir, file))))
71                if dir.startswith("layout"):
72                    for resource in get_ids_from_layout_file(os.path.join(resDir, dir, file)):
73                        add_resource_to_set(resources, resource)
74    for dir in valuesDirs:
75        for file in os.listdir(os.path.join(resDir, dir)):
76            filePath = os.path.abspath(os.path.join(resDir, dir, file))
77            if file.endswith('.xml') and filePath not in excluded_resource_files:
78                for resource in get_resources_from_single_file(os.path.join(resDir, dir, file),
79                                                               dir != "values"):
80                    add_resource_to_set(resources, resource)
81    return resources
82
83def get_ids_from_layout_file(filename):
84    result = set()
85    with open(filename, 'r') as file:
86        r = re.compile("@\+id/([a-zA-Z0-9_]+)")
87        for i in r.findall(file.read()):
88            add_resource_to_set(result, Resource(i, 'id', ResourceLocation(filename)))
89    return result
90
91def get_resources_from_single_file(filename, ignore_strings=False):
92    doc = etree.parse(filename)
93    root = doc.getroot()
94    result = set()
95    for resource in root:
96        if resource.tag is etree.Comment:
97            continue
98        if resource.tag == 'declare-styleable':
99            for attr in resource:
100                resName = attr.get('name')
101                # Skip resources beginning with 'android:' as they are part of the framework
102                # resources. This script finds only the app's resources.
103                if resName is None or resName.startswith('android:'):
104                    continue
105                resType = "attr"
106                add_resource_to_set(result, Resource(resName, resType, ResourceLocation(filename, attr.sourceline)))
107            continue
108        resName = resource.get('name')
109        resType = resource.tag
110        if resType == "string-array" or resType == "integer-array":
111            resType = "array"
112        if resource.tag == 'item' or resource.tag == 'public':
113            resType = resource.get('type')
114        if resType == 'string' and ignore_strings:
115            continue
116        if resType == 'overlayable':
117            for policy in resource:
118                for overlayable in policy:
119                    resName = overlayable.get('name')
120                    resType = overlayable.get('type')
121                    add_resource_to_set(result, Resource(resName, resType,
122                                                         ResourceLocation(filename, resource.sourceline)))
123        else:
124            add_resource_to_set(result, Resource(resName, resType,
125                                                 ResourceLocation(filename, resource.sourceline)))
126    return result
127
128# Used to get objects out of sets
129class _Grab:
130    def __init__(self, value):
131        self.search_value = value
132    def __hash__(self):
133        return hash(self.search_value)
134    def __eq__(self, other):
135        if self.search_value == other:
136            self.actual_value = other
137            return True
138        return False
139
140def add_resource_to_set(resourceset, resource):
141    if (resource.name == None):
142        return
143    grabber = _Grab(resource)
144    if grabber in resourceset:
145        grabber.actual_value.locations.extend(resource.locations)
146    else:
147        resourceset.update([resource])
148
149def merge_resources(set1, set2):
150    for resource in set2:
151        add_resource_to_set(set1, resource)
152