#!/usr/bin/env python # Adapted from https://joris.kluivers.nl/blog/2014/02/10/storyboard-identifier-constants/ (https://github.com/kluivers/storyboard-constants) # * Support for accessibility identifiers and accessibility labels # * Easily extendable with xpath definitions # * Valid identifiers # * Namespaced constants, idea from https://www.mikeash.com/pyblog/friday-qa-2011-08-19-namespaced-constants-and-functions.html PREFIX = '' SPACING = '\t' DEFINITIONS = { 'SegueIdentifier': [{'xpath': './/segue[@identifier]', 'attribute': 'identifier'}], 'ControllerIdentifier': [{'xpath': './/*[@storyboardIdentifier]', 'attribute': 'storyboardIdentifier'}], 'ReuseIdentifier': [{'xpath': './/*[@reuseIdentifier]', 'attribute': 'reuseIdentifier'}], 'AccessibilityIdentifier': [{'xpath': './/accessibility[@identifier]', 'attribute': 'identifier'}, {'xpath': './/userDefinedRuntimeAttribute[@keyPath="accessibilityIdentifier"]', 'attribute': 'value'}], 'AccessibilityLabel': [{'xpath': './/accessibility[@label]', 'attribute': 'label'}, {'xpath': './/userDefinedRuntimeAttribute[@keyPath="accessibilityLabel"]', 'attribute': 'value'}], } import sys, os, re, unicodedata import xml.etree.ElementTree as ElementTree # From http://stackoverflow.com/questions/517923/what-is-the-best-way-to-remove-accents-in-a-python-unicode-string/518232#518232 def stripAccents(s): return ''.join(c for c in unicodedata.normalize('NFD', unicode(s)) if unicodedata.category(c) != 'Mn') def makeIdentifier(s): return re.sub('[^\w]', '_', stripAccents(s)) def processStoryboard(storyboardPath, identifiers): root = ElementTree.parse(storyboardPath).getroot() storyboardName = os.path.splitext(storyboardPath)[0] for key,descriptors in DEFINITIONS.items(): for descriptor in descriptors: identifiers[key].extend(map((lambda e: e.get(descriptor['attribute'])), root.findall(descriptor['xpath']))) return identifiers def writeIdentifiers(path, identifiers, importHeader, structBegin, structContent, structEnd): with open(path, 'w+') as f: f.write('/* Generated file. DO NOT MODIFY */\n\n') f.write('#import {0}\n\n'.format(importHeader)) for key,identifiers in identifiers.items(): if len(identifiers) == 0: continue f.write(structBegin(key)) for identifier in sorted(identifiers): f.write(structContent(identifier)) f.write(structEnd(key)) def main(headerPath, implementationPath): identifiers = {key: [] for key in DEFINITIONS.keys()} for n in range(int(os.environ['SCRIPT_INPUT_FILE_COUNT'])): identifiers = processStoryboard(os.environ['SCRIPT_INPUT_FILE_' + str(n)], identifiers) writeIdentifiers(headerPath, identifiers, '<Foundation/Foundation.h>', (lambda kind: 'extern const struct {0}{1}Struct {{\n'.format(PREFIX, kind)), (lambda value: '{0}__unsafe_unretained NSString *{1};\n'.format(SPACING, makeIdentifier(value))), (lambda kind: '}} {0}{1};\n\n'.format(PREFIX, kind))) writeIdentifiers(implementationPath, identifiers, '"' + os.path.basename(headerPath) + '"', (lambda kind: 'const struct {0}{1}Struct {0}{1} = {{\n'.format(PREFIX, kind)), (lambda value: '{0}.{1} = @"{2}",\n'.format(SPACING, makeIdentifier(value), value.encode('utf-8'))), (lambda kind: '};\n\n')) if __name__ == '__main__': if len(sys.argv) != 3: sys.exit('error: usage: {0} headerPath implementationPath'.format(os.path.basename(sys.argv[0]))) main(sys.argv[1], sys.argv[2])