Skip to content

Commit 280d6a8

Browse files
authored
Merge pull request #2452 from millenomi/nsstring-enumeratesubstrings
2 parents a264bfd + 430a5c1 commit 280d6a8

File tree

2 files changed

+251
-73
lines changed

2 files changed

+251
-73
lines changed

Foundation/NSString.swift

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,13 @@ extension NSString {
7474
public static let byLines = EnumerationOptions(rawValue: 0)
7575
public static let byParagraphs = EnumerationOptions(rawValue: 1)
7676
public static let byComposedCharacterSequences = EnumerationOptions(rawValue: 2)
77+
78+
@available(*, unavailable, message: "Enumeration by words isn't supported in swift-corelibs-foundation")
7779
public static let byWords = EnumerationOptions(rawValue: 3)
80+
81+
@available(*, unavailable, message: "Enumeration by sentences isn't supported in swift-corelibs-foundation")
7882
public static let bySentences = EnumerationOptions(rawValue: 4)
83+
7984
public static let reverse = EnumerationOptions(rawValue: 1 << 8)
8085
public static let substringNotRequired = EnumerationOptions(rawValue: 1 << 9)
8186
public static let localized = EnumerationOptions(rawValue: 1 << 10)
@@ -825,8 +830,95 @@ extension NSString {
825830
return NSRange(location: start, length: parEnd - start)
826831
}
827832

833+
private enum EnumerateBy {
834+
case lines
835+
case paragraphs
836+
case composedCharacterSequences
837+
838+
init?(parsing options: EnumerationOptions) {
839+
var me: EnumerateBy?
840+
841+
// We don't test for .byLines because .byLines.rawValue == 0, which unfortunately means _every_ NSString.EnumerationOptions contains .byLines.
842+
// Instead, we just default to .lines below.
843+
844+
if options.contains(.byParagraphs) {
845+
guard me == nil else { return nil }
846+
me = .paragraphs
847+
}
848+
849+
if options.contains(.byComposedCharacterSequences) {
850+
guard me == nil else { return nil }
851+
me = .composedCharacterSequences
852+
}
853+
854+
self = me ?? .lines
855+
}
856+
}
857+
828858
public func enumerateSubstrings(in range: NSRange, options opts: EnumerationOptions = [], using block: (String?, NSRange, NSRange, UnsafeMutablePointer<ObjCBool>) -> Void) {
829-
NSUnimplemented()
859+
guard let enumerateBy = EnumerateBy(parsing: opts) else {
860+
fatalError("You must specify only one of the .by… enumeration options.")
861+
}
862+
863+
// We do not heed the .localized flag because it affects only by-words and by-sentences enumeration, which we do not support in s-c-f.
864+
865+
var currentIndex = opts.contains(.reverse) ? length - 1 : 0
866+
867+
func shouldContinue() -> Bool {
868+
opts.contains(.reverse) ? currentIndex >= 0 : currentIndex < length
869+
}
870+
871+
let reverse = opts.contains(.reverse)
872+
873+
func nextIndex(after fullRange: NSRange, compensatingForLengthDifferenceFrom oldLength: Int) -> Int {
874+
var index = reverse ? fullRange.location - 1 : fullRange.location + fullRange.length
875+
876+
if !reverse {
877+
index += (oldLength - length)
878+
}
879+
880+
return index
881+
}
882+
883+
while shouldContinue() {
884+
var range = NSRange(location: currentIndex, length: 0)
885+
var fullRange = range
886+
887+
switch enumerateBy {
888+
case .lines:
889+
var start = 0, end = 0, contentsEnd = 0
890+
getLineStart(&start, end: &end, contentsEnd: &contentsEnd, for: range)
891+
range.location = start
892+
range.length = contentsEnd - start
893+
fullRange.location = start
894+
fullRange.length = end - start
895+
case .paragraphs:
896+
var start = 0, end = 0, contentsEnd = 0
897+
getParagraphStart(&start, end: &end, contentsEnd: &contentsEnd, for: range)
898+
range.location = start
899+
range.length = contentsEnd - start
900+
fullRange.location = start
901+
fullRange.length = end - start
902+
case .composedCharacterSequences:
903+
range = rangeOfComposedCharacterSequences(for: range)
904+
fullRange = range
905+
}
906+
907+
var substring: String?
908+
if !opts.contains(.substringNotRequired) {
909+
substring = self.substring(with: range)
910+
}
911+
912+
let oldLength = length
913+
914+
var stop: ObjCBool = false
915+
block(substring, range, fullRange, &stop)
916+
if stop.boolValue {
917+
return
918+
}
919+
920+
currentIndex = nextIndex(after: fullRange, compensatingForLengthDifferenceFrom: oldLength)
921+
}
830922
}
831923

832924
public func enumerateLines(_ block: (String, UnsafeMutablePointer<ObjCBool>) -> Void) {

0 commit comments

Comments
 (0)