@@ -74,8 +74,13 @@ extension NSString {
74
74
public static let byLines = EnumerationOptions ( rawValue: 0 )
75
75
public static let byParagraphs = EnumerationOptions ( rawValue: 1 )
76
76
public static let byComposedCharacterSequences = EnumerationOptions ( rawValue: 2 )
77
+
78
+ @available ( * , unavailable, message: " Enumeration by words isn't supported in swift-corelibs-foundation " )
77
79
public static let byWords = EnumerationOptions ( rawValue: 3 )
80
+
81
+ @available ( * , unavailable, message: " Enumeration by sentences isn't supported in swift-corelibs-foundation " )
78
82
public static let bySentences = EnumerationOptions ( rawValue: 4 )
83
+
79
84
public static let reverse = EnumerationOptions ( rawValue: 1 << 8 )
80
85
public static let substringNotRequired = EnumerationOptions ( rawValue: 1 << 9 )
81
86
public static let localized = EnumerationOptions ( rawValue: 1 << 10 )
@@ -825,8 +830,95 @@ extension NSString {
825
830
return NSRange ( location: start, length: parEnd - start)
826
831
}
827
832
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
+
828
858
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
+ }
830
922
}
831
923
832
924
public func enumerateLines( _ block: ( String , UnsafeMutablePointer < ObjCBool > ) -> Void ) {
0 commit comments