Skip to content

Commit 666bb86

Browse files
committed
Use a regex to parse .strings files
1 parent 18a19f1 commit 666bb86

File tree

1 file changed

+48
-13
lines changed

1 file changed

+48
-13
lines changed

R.swift/ResourceTypes/LocalizableStrings.swift

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ struct LocalizableStrings : WhiteListedExtensionsResourceType {
4040
let dictionary: [String : (params: [StringParam], commentValue: String)]
4141
switch url.pathExtension {
4242
case "strings"?:
43-
dictionary = try parseStrings(nsDictionary, source: locale.withFilename("\(filename).strings"))
43+
dictionary = try parseStrings(String(contentsOfURL: url), source: locale.withFilename("\(filename).strings"))
4444
case "stringsdict"?:
4545
dictionary = try parseStringsdict(nsDictionary, source: locale.withFilename("\(filename).stringsdict"))
4646
default:
@@ -53,35 +53,70 @@ struct LocalizableStrings : WhiteListedExtensionsResourceType {
5353
}
5454
}
5555

56-
private func parseStrings(nsDictionary: NSDictionary, source: String) throws -> [String : (params: [StringParam], commentValue: String)] {
56+
private func parseStrings(stringsFile: String, source: String) throws -> [String : (params: [StringParam], commentValue: String)] {
5757
var dictionary: [String : (params: [StringParam], commentValue: String)] = [:]
5858

59-
for (key, obj) in nsDictionary {
60-
if let
61-
key = key as? String,
62-
val = obj as? String
63-
{
59+
for entry in StringsEntry.parse(stringsFile) {
6460
var params: [StringParam] = []
6561

66-
for part in FormatPart.formatParts(formatString: val) {
62+
for part in FormatPart.formatParts(formatString: entry.val) {
6763
switch part {
6864
case .Reference:
69-
throw ResourceParsingError.ParsingFailed("Non-specifier reference in \(source): \(key) = \(val)")
65+
throw ResourceParsingError.ParsingFailed("Non-specifier reference in \(source): \(entry.key) = \(entry.val)")
7066

7167
case .Spec(let formatSpecifier):
7268
params.append(StringParam(name: nil, spec: formatSpecifier))
7369
}
7470
}
7571

72+
dictionary[entry.key] = (params, entry.val)
73+
}
74+
75+
return dictionary
76+
}
7677

77-
dictionary[key] = (params, val)
78+
private struct StringsEntry {
79+
let comment: String?
80+
let key: String
81+
let val: String
82+
83+
static let regex: NSRegularExpression = {
84+
let capturedTrimmedComment = "(?s: /[*] \\s* (.*?) \\s* [*]/ )"
85+
let whitespaceOrComment = "(?s: \\s | /[*] .*? [*]/)"
86+
let slash = "\\\\"
87+
let quotedString = "(?s: \" .*? (?<! \(slash))\" )"
88+
let unquotedString = "[^\\s\(slash)\"=]+"
89+
let string = "(?: \(quotedString) | \(unquotedString) )"
90+
let pattern = "(?: \(capturedTrimmedComment) (\\s*) )? ( \(string) ) \(whitespaceOrComment)* = \(whitespaceOrComment)* ( \(string) ) \(whitespaceOrComment)* ;"
91+
return try! NSRegularExpression(pattern: pattern, options: .AllowCommentsAndWhitespace)
92+
}()
93+
94+
init(source: String, match: NSTextCheckingResult) {
95+
guard match.numberOfRanges == 5 else { fatalError("must be used with StringsEntry.regex") }
96+
97+
func extract(range: NSRange, unescape: Bool) -> String? {
98+
guard range.location != NSNotFound else { return nil }
99+
let raw = (source as NSString).substringWithRange(range)
100+
if !unescape { return raw }
101+
return try! NSPropertyListSerialization.propertyListWithData(raw.dataUsingEncoding(NSUTF8StringEncoding)!, options: [], format: nil) as! String
102+
}
103+
104+
let preKeySpacing = extract(match.rangeAtIndex(2), unescape: false)
105+
if preKeySpacing == nil || preKeySpacing?.componentsSeparatedByString("\n").count <= 2 {
106+
comment = extract(match.rangeAtIndex(1), unescape: false)
78107
}
79108
else {
80-
throw ResourceParsingError.ParsingFailed("Non-string value in \(source): \(key) = \(obj)")
109+
comment = nil
81110
}
111+
112+
key = extract(match.rangeAtIndex(3), unescape: true)!
113+
val = extract(match.rangeAtIndex(4), unescape: true)!
114+
}
115+
116+
static func parse(stringsFileContents: String) -> [StringsEntry] {
117+
return regex.matchesInString(stringsFileContents, options: [], range: NSRange(0..<stringsFileContents.utf16.count))
118+
.map { StringsEntry(source: stringsFileContents, match: $0) }
82119
}
83-
84-
return dictionary
85120
}
86121

87122
private func parseStringsdict(nsDictionary: NSDictionary, source: String) throws -> [String : (params: [StringParam], commentValue: String)] {

0 commit comments

Comments
 (0)