Skip to content

Commit 10a0d63

Browse files
committed
Handle escaping of the control character
1 parent 4c8381d commit 10a0d63

File tree

3 files changed

+42
-5
lines changed

3 files changed

+42
-5
lines changed

sources/LocalizationEditor/Providers/Parser.swift

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,19 +192,53 @@ class Parser {
192192
}
193193
return nil
194194
}
195-
/// This function finds the index where a given enclosing control character can be found. This index determies where this token may be terminated.
195+
196+
/// Returns the first unescaped index where the control character was found in the input string.
197+
/// - Parameters:
198+
/// - control: The enclosing control character whose first appearance should be found.
199+
/// - input: The string to search for the control character in.
200+
/// - escape: The escape character to check for when searching the input string.
201+
/// - Returns: The input's first index where the control character was found.
202+
private func firstUnescapedInstance(of control: EnclosingControlCharacters, in input: String, escape: Character = "\\") -> String.Index? {
203+
let controlString = control.rawValue
204+
205+
// If the control is a single character in length, then check for the escape character. This allows for value strings to contain an escaped quote.
206+
if controlString.count == 1 {
207+
let controlCharacter = controlString[controlString.startIndex]
208+
var iterator = input.indices.makeIterator()
209+
while let index = iterator.next() {
210+
switch input[index] {
211+
// We've found an unescaped instance of the control character.
212+
case controlCharacter:
213+
return index
214+
// If we find the escape character then we should skip a character.
215+
case escape:
216+
_ = iterator.next()
217+
default:
218+
break
219+
}
220+
}
221+
222+
return nil
223+
// Otherwise just do a simple substring search.
224+
} else {
225+
return input.index(of: controlString)
226+
}
227+
}
228+
229+
/// This function finds the index where a given enclosing control character can be found. This index determines where this token may be terminated.
196230
///
197231
/// - Parameter control: The enclosing control character whose first appearance should be found.
198232
/// - Returns: The index of the input control character relative to the start index of the input string.
199233
private func endIndex(for control: EnclosingControlCharacters) -> String.Index {
200234
// Search for the end of the command.
201235
let endIndex: String.Index
202-
if let closeIndex = input.index(of: control.rawValue) {
236+
if let closeIndex = firstUnescapedInstance(of: control, in: input) {
203237
// Closing index found.
204238
endIndex = closeIndex
205239
} else {
206240
// Find another way to end the enclosed text. Most likely the input is not well formatted. Keep on trying.
207-
print("Badly formatted control characters! Maybe because the user has some \" in the comments that can not be handled, yet.")
241+
print("Badly formatted control characters!")
208242

209243
var recoveryIndex: String.Index
210244
if let messageEndIndex = input.index(of: EnclosingControlCharacters.messageBoundaryClose.rawValue) {

sources/LocalizationEditorTests/Data/Special.strings

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66
Copyright © 2019 Igor Kulman. All rights reserved.
77
*/
88

9-
"quoted" = "some \"quoted\" message";
9+
/* The presence of a non-quoted string and a quoted string would previously cause the quoted string to be dropped. This string is here to check for regressions in quoted string parsing. */
10+
"regression_test" = "DO NOT DELETE";
11+
12+
"quoted" = "\"http://\" or \"https://\"";

sources/LocalizationEditorTests/LocalizationProviderParsingTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,6 @@ class LocalizationProviderParsingTests: XCTestCase {
7474
let provider = LocalizationProvider()
7575
let groups = provider.getLocalizations(url: createTestingDirectory(with: [TestFile(originalFileName: "Special.strings", destinationFileName: "LocalizableStrings.strings", destinationFolder: "Base.lproj")]))
7676

77-
XCTAssertEqual(groups[0].localizations[0].translations.first(where: {$0.key == "quoted"})?.value, "some \"quoted\" message")
77+
XCTAssertEqual(groups[0].localizations[0].translations.first(where: {$0.key == "quoted"})?.value, "\"http://\" or \"https://\"")
7878
}
7979
}

0 commit comments

Comments
 (0)