File: CFBundle_Strings.c

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (222 lines) | stat: -rw-r--r-- 11,378 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/*      CFBundle_Strings.c
	Copyright (c) 1999-2019, Apple Inc. and the Swift project authors
 
	Portions Copyright (c) 2014-2019, Apple Inc. and the Swift project authors
	Licensed under Apache License v2.0 with Runtime Library Exception
	See http://swift.org/LICENSE.txt for license information
	See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
        Responsibility: Tony Parker
*/

#include "CFBundle_Internal.h"
#include "CFCollections_Internal.h"

#if TARGET_OS_OSX

#endif

#include "CFPreferences.h"
#include "CFURLAccess.h"

#pragma mark -
#pragma mark Localized Strings

static void __CFStringsDictMergeApplyFunction(const void *key, const void *value, void *context) {
    CFDictionarySetValue((CFMutableDictionaryRef)context, key, value);
}


CF_EXPORT CFStringRef CFBundleCopyLocalizedString(CFBundleRef bundle, CFStringRef key, CFStringRef value, CFStringRef tableName) {
    return CFBundleCopyLocalizedStringForLocalization(bundle, key, value, tableName, NULL);
}


/* outActualTableFile is the URL to a localization table file we're getting strings from. It may be set to NULL on return to mean that we have pulled this from the cache of the preferred language, which is fine since we want this URL to determine which localization was picked. */
static CFStringRef _copyStringFromTable(CFBundleRef bundle, CFStringRef tableName, CFStringRef key, CFStringRef localizationName, Boolean preventMarkdownParsing, CFURLRef *outActualLocalizationFile) {
    // Check the cache first. If it's not there, populate the cache and check again.
    
    __CFLock(&bundle->_lock);
    // Only consult the cache when a specific localization has not been requested. We only cache results for the preferred language as determined by normal bundle lookup rules.
    if (!localizationName && bundle->_stringTable) {
        CFDictionaryRef stringTable = (CFDictionaryRef)CFDictionaryGetValue(bundle->_stringTable, tableName);
        if (stringTable) {
            CFStringRef result = CFDictionaryGetValue(stringTable, key);
            if (result) {
                CFRetain(result);
            }
            __CFUnlock(&bundle->_lock);
            
            if (outActualLocalizationFile) {
                *outActualLocalizationFile = NULL; // Preferred localization.
            }
            return result;
        }
    }

    // Not in the local cache, so load the table. Unlock so we don't hold the lock across file system access.
    __CFUnlock(&bundle->_lock);

    CFDictionaryRef stringsTable = NULL;
    CFURLRef stringsTableURL = NULL;
    CFURLRef stringsDictTableURL = NULL;
    
    // Find the resource URL.
    if (localizationName) {
        stringsTableURL = CFBundleCopyResourceURLForLocalization(bundle, tableName, _CFBundleStringTableType, NULL, localizationName);
        stringsDictTableURL = CFBundleCopyResourceURLForLocalization(bundle, tableName, _CFBundleStringDictTableType, NULL, localizationName);
    } else {
        stringsTableURL = CFBundleCopyResourceURL(bundle, tableName, _CFBundleStringTableType, NULL);
        stringsDictTableURL = CFBundleCopyResourceURL(bundle, tableName, _CFBundleStringDictTableType, NULL);
    }

    // Next, look on disk for the regular strings file.
    if (stringsTableURL) {
        CFDataRef tableData = _CFDataCreateFromURL(stringsTableURL, NULL);
        if (tableData) {
            CFErrorRef error = NULL;
            stringsTable = (CFDictionaryRef)CFPropertyListCreateWithData(CFGetAllocator(bundle), tableData, kCFPropertyListImmutable, NULL, &error);
            CFRelease(tableData);
            
            if (stringsTable && CFDictionaryGetTypeID() != CFGetTypeID(stringsTable)) {
                os_log_error(_CFBundleLocalizedStringLogger(), "Unable to load .strings file: %@ / %@: Top-level object was not a dictionary", bundle, tableName);
                CFRelease(stringsTable);
                stringsTable = NULL;
            } else if (!stringsTable && error) {
                os_log_error(_CFBundleLocalizedStringLogger(), "Unable to load .strings file: %@ / %@: %@", bundle, tableName, error);
                CFRelease(error);
                error = NULL;
            }
        }        
    }
    
    // Check for a .stringsdict file.
    if (stringsDictTableURL) {
        CFDataRef tableData = _CFDataCreateFromURL(stringsDictTableURL, NULL);
        if (tableData) {
            CFErrorRef error = NULL;
            CFDictionaryRef stringsDictTable = (CFDictionaryRef)CFPropertyListCreateWithData(CFGetAllocator(bundle), tableData, kCFPropertyListImmutable, NULL, &error);
            CFRelease(tableData);
            
            if (!stringsDictTable && error) {
                os_log_error(_CFBundleLocalizedStringLogger(), "Unable to load .stringsdict file: %@ / %@: %@", bundle, tableName, error);
                CFRelease(error);
                error = NULL;
            } else if (stringsDictTable && CFDictionaryGetTypeID() != CFGetTypeID(stringsDictTable)) {
                os_log_error(_CFBundleLocalizedStringLogger(), "Unable to load .stringsdict file: %@ / %@: Top-level object was not a dictionary", bundle, tableName);
                CFRelease(stringsDictTable);
                stringsDictTable = NULL;
            } else if (stringsDictTable) {
                // Post-process the strings table.
                CFMutableDictionaryRef mutableStringsDictTable;
                if (stringsTable) {
                    // Any strings that are in the stringsTable that are not in the stringsDict must be added to the stringsDict.
                    // However, any entry in the stringsDictTable must override the content from stringsTable.
                    
                    // Start by copying the stringsTable.
                    mutableStringsDictTable = CFDictionaryCreateMutableCopy(NULL, 0, stringsTable);
                    
                    // Replace any stringsTable entries with entries from stringsDictTable. This will override any entries from the original stringsTable if they existed.
                    CFDictionaryApplyFunction(stringsDictTable, __CFStringsDictMergeApplyFunction, mutableStringsDictTable);
                } else {
                    // Start with a copy of the stringsDictTable on its own.
                    mutableStringsDictTable = CFDictionaryCreateMutableCopy(NULL, 0, stringsDictTable);
                }
                
                CFRelease(stringsDictTable);

                if (stringsTable) CFRelease(stringsTable);
                // The new strings table is the result of all the transforms above.
                stringsTable = mutableStringsDictTable;
                
                if (outActualLocalizationFile) {
                    *outActualLocalizationFile = CFRetain(stringsDictTableURL);
                }
            }
        }
    }
    
    if (outActualLocalizationFile && !*outActualLocalizationFile && stringsTableURL) {
        *outActualLocalizationFile = CFRetain(stringsTableURL);
    }
    
    if (stringsTableURL) CFRelease(stringsTableURL);
    if (stringsDictTableURL) CFRelease(stringsDictTableURL);
    
    // Last resort: create an empty table
    if (!stringsTable) {
        os_log_debug(_CFBundleLocalizedStringLogger(), "Hit last resort and creating empty strings table");
        stringsTable = CFDictionaryCreate(CFGetAllocator(bundle), NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    }
    
    // Insert the result into our local cache
    if ((!CFStringHasSuffix(tableName, CFSTR(".nocache")) || !_CFExecutableLinkedOnOrAfter(CFSystemVersionLeopard)) && localizationName == NULL) {
        // Take lock again, because this we will unlock after getting the value out of the table.
        __CFLock(&bundle->_lock);
        if (!bundle->_stringTable) bundle->_stringTable = CFDictionaryCreateMutable(CFGetAllocator(bundle), 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        
        // If another thread beat us to setting this tableName, then we'll just replace it here.
        CFDictionarySetValue(bundle->_stringTable, tableName, stringsTable);
    } else {
        // Take lock again, because this we will unlock after getting the value out of the table.
        __CFLock(&bundle->_lock);
    }
    
    // Finally, fetch the result from the table
    CFStringRef result = CFDictionaryGetValue(stringsTable, key);
    if (result) {
        CFRetain(result);
    }
    __CFUnlock(&bundle->_lock);

    CFRelease(stringsTable);
    
    return result;
}

CF_EXPORT CFStringRef _CFBundleCopyLocalizedStringForLocalizationTableURLAndMarkdownOption(CFBundleRef bundle, CFStringRef key, CFStringRef value, CFStringRef tableName, CFStringRef localizationName, Boolean preventMarkdownParsing, CFURLRef *outActualTableURL) {

    CF_ASSERT_TYPE(_kCFRuntimeIDCFBundle, bundle);
    if (!key) { return (value ? (CFStringRef)CFRetain(value) : (CFStringRef)CFRetain(CFSTR(""))); }
    
    // Make sure to check the mixed localizations key early -- if the main bundle has not yet been cached, then we need to create the cache of the Info.plist before we start asking for resources (11172381)
    (void)CFBundleAllowMixedLocalizations();
    
    if (!tableName || CFEqual(tableName, CFSTR(""))) tableName = _CFBundleDefaultStringTableName;
    
    CFURLRef actualTableURL = NULL;
    CFStringRef result = _copyStringFromTable(bundle, tableName, key, localizationName, preventMarkdownParsing, &actualTableURL);
    
    if (!result) {
        if (!value) {
            result = (CFStringRef)CFRetain(key);
        } else if (CFEqual(value, CFSTR(""))) {
            result = (CFStringRef)CFRetain(key);
        } else {
            result = (CFStringRef)CFRetain(value);
        }
        static Boolean capitalize = false;
        if (capitalize) {
            CFMutableStringRef capitalizedResult = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, result);
            os_log_error(_CFBundleLocalizedStringLogger(), "ERROR: %@ not found in table %@ of bundle %@", key, tableName, bundle);
            CFStringUppercase(capitalizedResult, NULL);
            CFRelease(result);
            result = capitalizedResult;
        }
    }
    if (outActualTableURL) {
        *outActualTableURL = actualTableURL;
    } else if (actualTableURL) {
        CFRelease(actualTableURL);
    }
    
    os_log_debug(_CFBundleLocalizedStringLogger(), "Bundle: %{private}@, key: %{public}@, value: %{public}@, table: %{public}@, localizationName: %{public}@, result: %{public}@", bundle, key, value, tableName, localizationName, result);
    return result;
}

CF_EXPORT CFStringRef _CFBundleCopyLocalizedStringForLocalizationAndTableURL(CFBundleRef bundle, CFStringRef key, CFStringRef value, CFStringRef tableName, CFStringRef localizationName, CFURLRef *outActualTableURL) {
    return _CFBundleCopyLocalizedStringForLocalizationTableURLAndMarkdownOption(bundle, key, value, tableName, localizationName, false, outActualTableURL);
}

CF_EXPORT CFStringRef CFBundleCopyLocalizedStringForLocalization(CFBundleRef bundle, CFStringRef key, CFStringRef value, CFStringRef tableName, CFStringRef localizationName) {
    return _CFBundleCopyLocalizedStringForLocalizationTableURLAndMarkdownOption(bundle, key, value, tableName, localizationName, false, NULL);
}