Skip to content
This repository has been archived by the owner on Jul 11, 2024. It is now read-only.

Fix crash caused by combining unicode characters #31

Merged
merged 6 commits into from
Sep 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 22 additions & 43 deletions Source/Extensions/String+ListItemFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,58 +24,37 @@ import Foundation

extension String {

init(format: String, ranges: inout [Range<String.Index>], _ args: CVarArg...) {
init(format: String, ranges: inout [Range<String.Index>], _ args: String...) {
self.init(format: format, ranges: &ranges, arguments: args)
}

init(format: String, ranges: inout [Range<String.Index>], arguments: [CVarArg]) {
self.init(format: format, locale: nil, ranges: &ranges, arguments: arguments)
}

init(format: String, locale: Locale?, ranges: inout [Range<String.Index>], _ args: CVarArg...) {
self.init(format: format, locale: locale, ranges: &ranges, arguments: args)
}
init(format: String, ranges: inout [Range<String.Index>], arguments: [String]) {
var string = ""
var lowerBound: String.Index = format.startIndex

init(format _format: String, locale: Locale?, ranges: inout [Range<String.Index>], arguments: [CVarArg]) {
self.init(format: _format, locale: locale, arguments: arguments)
let regex = try! NSRegularExpression(pattern: "\\{([0-9]+)\\}")
regex.enumerateMatches(in: format, range: NSRange(format.startIndex ..< format.endIndex, in: format)) { result, _, _ in
let result = result!

let format = _format as NSString
var nextUnassignedIndex: Int = 0
var locationOffset: String.IndexDistance = 0
let resultRange = Range(result.range(at: 0), in: format)!
let indexRange = Range(result.range(at: 1), in: format)!
let index = Int(format[indexRange])!
let argument = arguments[index]

let pattern = "%(([0-9]+)\\$)?(\\.[0-9]+)?([@aAcCdDeEfFgGopsSuUxX]|ld|lu|lx|zx)"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
let range = NSRange(location: 0, length: format.length)
regex.enumerateMatches(in: _format, options: [], range: range) { _match, _, stop in
let match = _match!
let range = match.range
let prefixRange = lowerBound ..< resultRange.lowerBound
string += format[prefixRange]

let argIndex: Int
let indexValueRange = match.range(at: 2)
if indexValueRange.location != NSNotFound,
let index = Int(format.substring(with: indexValueRange)) {
argIndex = index - 1
} else {
argIndex = nextUnassignedIndex
nextUnassignedIndex += 1
}
let argumentStartIndex = string.endIndex
string += argument
let argumentEndIndex = string.endIndex

let argValueFormat: NSString
let indexRange = match.range(at: 1)
if indexRange.location != NSNotFound {
let placeholder = format.substring(with: range) as NSString
let indexString = format.substring(with: indexRange)
argValueFormat = placeholder.replacingOccurrences(of: indexString, with: "") as NSString
} else {
argValueFormat = format.substring(with: range) as NSString
}
ranges.append(argumentStartIndex ..< argumentEndIndex)

let argValue = NSString(format: argValueFormat, locale: locale, arguments[argIndex])
let argRange = NSRange(location: range.location + locationOffset, length: argValue.length)

ranges.append(Range(argRange, in: self)!)

locationOffset += (argValue.length - range.length)
lowerBound = resultRange.upperBound
}

// Close up
string += format[lowerBound ..< format.endIndex]
self = string
}
}
35 changes: 8 additions & 27 deletions Source/ListPatternBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class ListPatternBuilder {

var items = items
var string = items.removeLast()
var itemRanges = [(string.startIndex ..< string.endIndex).sameRange(in: string.utf16)!]
var itemRanges = [string.startIndex ..< string.endIndex]
var remainingItems = items
var nextFormat = patterns.end

Expand All @@ -79,13 +79,14 @@ class ListPatternBuilder {
var ranges: [Range<String.Index>] = []
let formatted = String(format: nextFormat, ranges: &ranges, nextItem, string)

let formattedUtf16 = formatted.utf16
let itemRange = ranges.first(where: { formatted[$0] == nextItem })!.sameRange(in: formattedUtf16)!
let stringRange = ranges.first(where: { formatted[$0] == string })!.sameRange(in: formattedUtf16)!
let itemRange = ranges.first(where: { formatted[$0] == nextItem })!
let stringRange = ranges.first(where: { formatted[$0] == string })!

let offset = formattedUtf16.distance(from: formattedUtf16.startIndex, to: stringRange.lowerBound)
let baseIndex = stringRange.lowerBound.samePosition(in: formatted.unicodeScalars)!
itemRanges = itemRanges.map {
formattedUtf16.index($0.lowerBound, offsetBy: offset) ..< formattedUtf16.index($0.upperBound, offsetBy: offset)
let startIndex = formatted.unicodeScalars.index(baseIndex, offsetBy: string.unicodeScalars.distance(from: string.unicodeScalars.startIndex, to: $0.lowerBound))
let endIndex = formatted.unicodeScalars.index(baseIndex, offsetBy: string.unicodeScalars.distance(from: string.unicodeScalars.startIndex, to: $0.upperBound))
return startIndex ..< endIndex
}

string = formatted
Expand All @@ -95,26 +96,6 @@ class ListPatternBuilder {

return List(string: string,
items: items,
itemRanges: itemRanges.map({ $0.sameRange(in: string)! }))
}
}

extension Range where Bound == String.Index {

func sameRange(in utf16: String.UTF16View) -> Range<String.UTF16View.Index>? {

guard let lowerBound = lowerBound.samePosition(in: utf16),
let upperBound = upperBound.samePosition(in: utf16) else { return nil }
return lowerBound ..< upperBound
}
}

extension Range where Bound == String.UTF16View.Index {

func sameRange(in string: String) -> Range<String.Index>? {

guard let lowerBound = lowerBound.samePosition(in: string),
let upperBound = upperBound.samePosition(in: string) else { return nil }
return lowerBound ..< upperBound
itemRanges: itemRanges)
}
}
2 changes: 1 addition & 1 deletion Source/Models/Format.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ private extension ListItemFormatter.Style {
private extension String {

func argCount() -> Int {
let regex = try? NSRegularExpression(pattern: "%([0-9]+\\$)?@", options: [])
let regex = try? NSRegularExpression(pattern: "\\{[0-9]+\\}", options: [])
return regex?.numberOfMatches(in: self, options: [], range: NSRange(startIndex ..< endIndex, in: self)) ?? 0
}
}
72 changes: 36 additions & 36 deletions Source/Resources.xcassets/af.dataset/af.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,58 @@
"localeIdentifier": "af",
"listPatterns": {
"standard": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@ en %2$@",
"2": "%1$@ en %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0} en {1}",
"2": "{0} en {1}"
},
"or": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@ of %2$@",
"2": "%1$@ of %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0} of {1}",
"2": "{0} of {1}"
},
"orNarrow": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@ of %2$@",
"2": "%1$@ of %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0} of {1}",
"2": "{0} of {1}"
},
"orShort": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@ of %2$@",
"2": "%1$@ of %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0} of {1}",
"2": "{0} of {1}"
},
"standardNarrow": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@ en %2$@",
"2": "%1$@ en %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0} en {1}",
"2": "{0} en {1}"
},
"standardShort": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@ en %2$@",
"2": "%1$@ en %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0} en {1}",
"2": "{0} en {1}"
},
"unit": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@ en %2$@",
"2": "%1$@ en %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0} en {1}",
"2": "{0} en {1}"
},
"unitNarrow": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@ en %2$@",
"2": "%1$@ en %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0} en {1}",
"2": "{0} en {1}"
},
"unitShort": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@ en %2$@",
"2": "%1$@ en %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0} en {1}",
"2": "{0} en {1}"
}
}
}
72 changes: 36 additions & 36 deletions Source/Resources.xcassets/agq.dataset/agq.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,58 @@
"localeIdentifier": "agq",
"listPatterns": {
"standard": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@, %2$@",
"2": "%1$@, %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0}, {1}",
"2": "{0}, {1}"
},
"or": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@, or %2$@",
"2": "%1$@ or %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0}, or {1}",
"2": "{0} or {1}"
},
"orNarrow": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@, or %2$@",
"2": "%1$@ or %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0}, or {1}",
"2": "{0} or {1}"
},
"orShort": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@, or %2$@",
"2": "%1$@ or %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0}, or {1}",
"2": "{0} or {1}"
},
"standardNarrow": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@, %2$@",
"2": "%1$@, %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0}, {1}",
"2": "{0}, {1}"
},
"standardShort": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@, %2$@",
"2": "%1$@, %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0}, {1}",
"2": "{0}, {1}"
},
"unit": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@, %2$@",
"2": "%1$@, %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0}, {1}",
"2": "{0}, {1}"
},
"unitNarrow": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@, %2$@",
"2": "%1$@, %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0}, {1}",
"2": "{0}, {1}"
},
"unitShort": {
"start": "%1$@, %2$@",
"middle": "%1$@, %2$@",
"end": "%1$@, %2$@",
"2": "%1$@, %2$@"
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0}, {1}",
"2": "{0}, {1}"
}
}
}
Loading