diff options
Diffstat (limited to 'tools/rro/resource_utils.py')
-rw-r--r-- | tools/rro/resource_utils.py | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/tools/rro/resource_utils.py b/tools/rro/resource_utils.py new file mode 100644 index 0000000..8628abf --- /dev/null +++ b/tools/rro/resource_utils.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import lxml.etree as etree + +class ResourceLocation: + def __init__(self, file, line=None): + self.file = file + self.line = line + def __str__(self): + if self.line is not None: + return self.file + ':' + str(self.line) + else: + return self.file + +class Resource: + def __init__(self, name, type, location=None): + self.name = name + self.type = type + self.locations = [] + if location is not None: + self.locations.append(location) + def __eq__(self, other): + if isinstance(other, _Grab): + return other == self + return self.name == other.name and self.type == other.type + def __ne__(self, other): + if isinstance(other, _Grab): + return other != self + return self.name != other.name or self.type != other.type + def __hash__(self): + return hash((self.name, self.type)) + def __str__(self): + result = '' + for location in self.locations: + result += str(location) + ': ' + result += '<'+self.type+' name="'+self.name+'"' + return result + '>' + def __repr__(self): + return str(self) + +def get_all_resources(resDir, excluded_resource_files=[]): + excluded_resource_files = [os.path.abspath(file) for file in excluded_resource_files] + allResDirs = [f for f in os.listdir(resDir) if os.path.isdir(os.path.join(resDir, f))] + valuesDirs = [f for f in allResDirs if f.startswith('values')] + fileDirs = [f for f in allResDirs if not f.startswith('values')] + resources = set() + # Get the filenames of the all the files in all the fileDirs + for dir in fileDirs: + type = dir.split('-')[0] + for file in os.listdir(os.path.join(resDir, dir)): + filePath = os.path.abspath(os.path.join(resDir, dir, file)) + if file.endswith('.xml') and filePath not in excluded_resource_files: + add_resource_to_set(resources, + Resource(file[:-4], type, + ResourceLocation(os.path.join(resDir, dir, file)))) + if dir.startswith("layout"): + for resource in get_ids_from_layout_file(os.path.join(resDir, dir, file)): + add_resource_to_set(resources, resource) + for dir in valuesDirs: + for file in os.listdir(os.path.join(resDir, dir)): + filePath = os.path.abspath(os.path.join(resDir, dir, file)) + if file.endswith('.xml') and filePath not in excluded_resource_files: + for resource in get_resources_from_single_file(os.path.join(resDir, dir, file), + dir != "values"): + add_resource_to_set(resources, resource) + return resources + +def get_ids_from_layout_file(filename): + result = set() + with open(filename, 'r') as file: + r = re.compile("@\+id/([a-zA-Z0-9_]+)") + for i in r.findall(file.read()): + add_resource_to_set(result, Resource(i, 'id', ResourceLocation(filename))) + return result + +def get_resources_from_single_file(filename, ignore_strings=False): + doc = etree.parse(filename) + root = doc.getroot() + result = set() + for resource in root: + if resource.tag is etree.Comment: + continue + if resource.tag == 'declare-styleable': + for attr in resource: + resName = attr.get('name') + # Skip resources beginning with 'android:' as they are part of the framework + # resources. This script finds only the app's resources. + if resName is None or resName.startswith('android:'): + continue + resType = "attr" + add_resource_to_set(result, Resource(resName, resType, ResourceLocation(filename, attr.sourceline))) + continue + resName = resource.get('name') + resType = resource.tag + if resType == "string-array" or resType == "integer-array": + resType = "array" + if resource.tag == 'item' or resource.tag == 'public': + resType = resource.get('type') + if resType == 'string' and ignore_strings: + continue + if resType == 'overlayable': + for policy in resource: + for overlayable in policy: + resName = overlayable.get('name') + resType = overlayable.get('type') + add_resource_to_set(result, Resource(resName, resType, + ResourceLocation(filename, resource.sourceline))) + else: + add_resource_to_set(result, Resource(resName, resType, + ResourceLocation(filename, resource.sourceline))) + return result + +# Used to get objects out of sets +class _Grab: + def __init__(self, value): + self.search_value = value + def __hash__(self): + return hash(self.search_value) + def __eq__(self, other): + if self.search_value == other: + self.actual_value = other + return True + return False + +def add_resource_to_set(resourceset, resource): + if (resource.name == None): + return + grabber = _Grab(resource) + if grabber in resourceset: + grabber.actual_value.locations.extend(resource.locations) + else: + resourceset.update([resource]) + +def merge_resources(set1, set2): + for resource in set2: + add_resource_to_set(set1, resource) |