aboutsummaryrefslogtreecommitdiff
path: root/tools/rro/resource_utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/rro/resource_utils.py')
-rw-r--r--tools/rro/resource_utils.py151
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)