Skip to content

Commit 1f29e7b

Browse files
committed
Parity: NSAttributedString
This implements: - encoding and decoding. - the partial ability to bridge NSAttributedString.Key to NSString as Darwin allows. It also adds a new fixture generator and fixtures produced on macOS 10.14. The generator only works on Darwin for now.
1 parent ef6f96e commit 1f29e7b

File tree

12 files changed

+937
-39
lines changed

12 files changed

+937
-39
lines changed

CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ if(ENABLE_TESTING)
378378
TestFoundation/HTTPServer.swift
379379
Foundation/ProgressFraction.swift
380380
TestFoundation/Utilities.swift
381+
TestFoundation/FixtureValues.swift
381382
# Test Cases
382383
TestFoundation/TestAffineTransform.swift
383384
TestFoundation/TestBundle.swift
@@ -499,6 +500,7 @@ if(ENABLE_TESTING)
499500
${CMAKE_SOURCE_DIR}/TestFoundation/Resources/NSKeyedUnarchiver-UUIDTest.plist
500501
${CMAKE_SOURCE_DIR}/TestFoundation/Resources/NSKeyedUnarchiver-OrderedSetTest.plist
501502
${CMAKE_SOURCE_DIR}/TestFoundation/Resources/TestFileWithZeros.txt
503+
${CMAKE_SOURCE_DIR}/TestFoundation/Fixtures
502504
SWIFT_FLAGS
503505
${deployment_enable_libdispatch}
504506
-I;${CMAKE_CURRENT_BINARY_DIR}/swift

Foundation.xcodeproj/project.pbxproj

+11-2
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
153E951120111DC500F250BE /* CFKnownLocations.h in Headers */ = {isa = PBXBuildFile; fileRef = 153E950F20111DC500F250BE /* CFKnownLocations.h */; settings = {ATTRIBUTES = (Private, ); }; };
1616
153E951220111DC500F250BE /* CFKnownLocations.c in Sources */ = {isa = PBXBuildFile; fileRef = 153E951020111DC500F250BE /* CFKnownLocations.c */; };
1717
15496CF1212CAEBA00450F5A /* CFAttributedStringPriv.h in Headers */ = {isa = PBXBuildFile; fileRef = 15496CF0212CAEBA00450F5A /* CFAttributedStringPriv.h */; };
18+
155D3BBC22401D1100B0D38E /* FixtureValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 155D3BBB22401D1100B0D38E /* FixtureValues.swift */; };
1819
1569BFA12187D04C009518FA /* CFCalendar_Enumerate.c in Sources */ = {isa = PBXBuildFile; fileRef = 1569BF9F2187D003009518FA /* CFCalendar_Enumerate.c */; };
1920
1569BFA22187D04F009518FA /* CFCalendar_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1569BF9D2187CFFF009518FA /* CFCalendar_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; };
2021
1569BFA52187D0E0009518FA /* CFDateInterval.c in Sources */ = {isa = PBXBuildFile; fileRef = 1569BFA32187D0E0009518FA /* CFDateInterval.c */; };
2122
1569BFA62187D0E0009518FA /* CFDateInterval.h in Headers */ = {isa = PBXBuildFile; fileRef = 1569BFA42187D0E0009518FA /* CFDateInterval.h */; settings = {ATTRIBUTES = (Private, ); }; };
2223
1569BFAD2187D529009518FA /* CFDateComponents.c in Sources */ = {isa = PBXBuildFile; fileRef = 1569BFAB2187D529009518FA /* CFDateComponents.c */; };
2324
1569BFAE2187D529009518FA /* CFDateComponents.h in Headers */ = {isa = PBXBuildFile; fileRef = 1569BFAC2187D529009518FA /* CFDateComponents.h */; settings = {ATTRIBUTES = (Private, ); }; };
25+
156C846E224069A100607D44 /* Fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 156C846D224069A000607D44 /* Fixtures */; };
2426
1578DA09212B4061003C9516 /* CFRuntime_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1578DA08212B4060003C9516 /* CFRuntime_Internal.h */; };
2527
1578DA0D212B4070003C9516 /* CFMachPort_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1578DA0A212B406F003C9516 /* CFMachPort_Internal.h */; };
2628
1578DA0E212B4070003C9516 /* CFMachPort_Lifetime.c in Sources */ = {isa = PBXBuildFile; fileRef = 1578DA0B212B406F003C9516 /* CFMachPort_Lifetime.c */; };
@@ -554,12 +556,14 @@
554556
153E950F20111DC500F250BE /* CFKnownLocations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CFKnownLocations.h; sourceTree = "<group>"; };
555557
153E951020111DC500F250BE /* CFKnownLocations.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = CFKnownLocations.c; sourceTree = "<group>"; };
556558
15496CF0212CAEBA00450F5A /* CFAttributedStringPriv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFAttributedStringPriv.h; sourceTree = "<group>"; };
559+
155D3BBB22401D1100B0D38E /* FixtureValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixtureValues.swift; sourceTree = "<group>"; };
557560
1569BF9D2187CFFF009518FA /* CFCalendar_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFCalendar_Internal.h; sourceTree = "<group>"; };
558561
1569BF9F2187D003009518FA /* CFCalendar_Enumerate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFCalendar_Enumerate.c; sourceTree = "<group>"; };
559562
1569BFA32187D0E0009518FA /* CFDateInterval.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFDateInterval.c; sourceTree = "<group>"; };
560563
1569BFA42187D0E0009518FA /* CFDateInterval.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFDateInterval.h; sourceTree = "<group>"; };
561564
1569BFAB2187D529009518FA /* CFDateComponents.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFDateComponents.c; sourceTree = "<group>"; };
562565
1569BFAC2187D529009518FA /* CFDateComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFDateComponents.h; sourceTree = "<group>"; };
566+
156C846D224069A000607D44 /* Fixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Fixtures; path = TestFoundation/Fixtures; sourceTree = SOURCE_ROOT; };
563567
1578DA08212B4060003C9516 /* CFRuntime_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFRuntime_Internal.h; sourceTree = "<group>"; };
564568
1578DA0A212B406F003C9516 /* CFMachPort_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFMachPort_Internal.h; sourceTree = "<group>"; };
565569
1578DA0B212B406F003C9516 /* CFMachPort_Lifetime.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFMachPort_Lifetime.c; sourceTree = "<group>"; };
@@ -1543,6 +1547,7 @@
15431547
EA66F6391BF1619600136161 /* Resources */ = {
15441548
isa = PBXGroup;
15451549
children = (
1550+
156C846D224069A000607D44 /* Fixtures */,
15461551
B98E33DC2136AA740044EBE9 /* TestFileWithZeros.txt */,
15471552
D370696D1C394FBF00295652 /* NSKeyedUnarchiver-RangeTest.plist */,
15481553
D3E8D6D41C36AC0C00295652 /* NSKeyedUnarchiver-RectTest.plist */,
@@ -1575,6 +1580,7 @@
15751580
children = (
15761581
C7DE1FCB21EEE67200174F35 /* TestUUID.swift */,
15771582
7D0DE86D211883F500540061 /* Utilities.swift */,
1583+
155D3BBB22401D1100B0D38E /* FixtureValues.swift */,
15781584
7D0DE86C211883F500540061 /* TestDateComponents.swift */,
15791585
6E203B8C1C1303BB003B2576 /* TestBundle.swift */,
15801586
A5A34B551C18C85D00FD972B /* TestByteCountFormatter.swift */,
@@ -2242,6 +2248,7 @@
22422248
developmentRegion = English;
22432249
hasScannedForEncodings = 0;
22442250
knownRegions = (
2251+
English,
22452252
en,
22462253
Base,
22472254
);
@@ -2284,6 +2291,7 @@
22842291
B910957A1EEF237800A71930 /* NSString-UTF16-LE-data.txt in Resources */,
22852292
D3E8D6D51C36AC0C00295652 /* NSKeyedUnarchiver-RectTest.plist in Resources */,
22862293
D3A597F81C3415CC00295652 /* NSKeyedUnarchiver-URLTest.plist in Resources */,
2294+
156C846E224069A100607D44 /* Fixtures in Resources */,
22872295
D3E8D6D31C36982700295652 /* NSKeyedUnarchiver-EdgeInsetsTest.plist in Resources */,
22882296
B907F36B20BB07A700013CBE /* NSString-ISO-8859-1-data.txt in Resources */,
22892297
D370696E1C394FBF00295652 /* NSKeyedUnarchiver-RangeTest.plist in Resources */,
@@ -2633,6 +2641,7 @@
26332641
5B13B3391C582D4C00651CE2 /* TestNSNull.swift in Sources */,
26342642
BD8042161E09857800487EB8 /* TestLengthFormatter.swift in Sources */,
26352643
5B13B3421C582D4C00651CE2 /* TestRunLoop.swift in Sources */,
2644+
155D3BBC22401D1100B0D38E /* FixtureValues.swift in Sources */,
26362645
5B13B34E1C582D4C00651CE2 /* TestXMLDocument.swift in Sources */,
26372646
5B13B32B1C582D4C00651CE2 /* TestNSData.swift in Sources */,
26382647
5B13B34C1C582D4C00651CE2 /* TestURLResponse.swift in Sources */,
@@ -3091,7 +3100,7 @@
30913100
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
30923101
LIBRARY_SEARCH_PATHS = "$(inherited)";
30933102
MACH_O_TYPE = mh_execute;
3094-
OTHER_SWIFT_FLAGS = "-DDEPLOYMENT_ENABLE_LIBDISPATCH -swift-version 4.2";
3103+
OTHER_SWIFT_FLAGS = "-DDEPLOYMENT_ENABLE_LIBDISPATCH -DDEPLOYMENT_RUNTIME_SWIFT";
30953104
PRODUCT_BUNDLE_IDENTIFIER = org.swift.TestFoundation;
30963105
PRODUCT_NAME = "$(TARGET_NAME)";
30973106
SKIP_INSTALL = YES;
@@ -3117,7 +3126,7 @@
31173126
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
31183127
LIBRARY_SEARCH_PATHS = "$(inherited)";
31193128
MACH_O_TYPE = mh_execute;
3120-
OTHER_SWIFT_FLAGS = "-DDEPLOYMENT_ENABLE_LIBDISPATCH -swift-version 4.2";
3129+
OTHER_SWIFT_FLAGS = "-DDEPLOYMENT_ENABLE_LIBDISPATCH -DDEPLOYMENT_RUNTIME_SWIFT";
31213130
PRODUCT_BUNDLE_IDENTIFIER = org.swift.TestFoundation;
31223131
PRODUCT_NAME = "$(TARGET_NAME)";
31233132
SKIP_INSTALL = YES;

Foundation/NSAttributedString.swift

+192-4
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,85 @@ extension NSAttributedString {
2727
}
2828
}
2929

30+
extension NSAttributedString.Key: _ObjectiveCBridgeable {
31+
public func _bridgeToObjectiveC() -> NSString {
32+
return rawValue as NSString
33+
}
34+
35+
public static func _forceBridgeFromObjectiveC(_ source: NSString, result: inout NSAttributedString.Key?) {
36+
result = NSAttributedString.Key(source as String)
37+
}
38+
39+
public static func _conditionallyBridgeFromObjectiveC(_ source: NSString, result: inout NSAttributedString.Key?) -> Bool {
40+
result = NSAttributedString.Key(source as String)
41+
return true
42+
}
43+
44+
public static func _unconditionallyBridgeFromObjectiveC(_ source: NSString?) -> NSAttributedString.Key {
45+
guard let source = source else { return NSAttributedString.Key("") }
46+
return NSAttributedString.Key(source as String)
47+
}
48+
}
49+
3050
@available(*, unavailable, renamed: "NSAttributedString.Key")
3151
public typealias NSAttributedStringKey = NSAttributedString.Key
3252

33-
3453
open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
3554

3655
private let _cfinfo = _CFInfo(typeID: CFAttributedStringGetTypeID())
3756
fileprivate var _string: NSString
3857
fileprivate var _attributeArray: CFRunArrayRef
3958

4059
public required init?(coder aDecoder: NSCoder) {
41-
NSUnimplemented()
60+
let mutableAttributedString = NSMutableAttributedString(string: "")
61+
guard _NSReadMutableAttributedStringWithCoder(aDecoder, mutableAttributedString: mutableAttributedString) else {
62+
return nil
63+
}
64+
65+
// use the resulting _string and _attributeArray to initialize a new instance, just like init
66+
_string = mutableAttributedString._string
67+
_attributeArray = mutableAttributedString._attributeArray
4268
}
4369

4470
open func encode(with aCoder: NSCoder) {
45-
NSUnimplemented()
71+
guard aCoder.allowsKeyedCoding else { fatalError("We do not support saving to a non-keyed coder.") }
72+
73+
aCoder.encode(string, forKey: "NSString")
74+
let length = self.length
75+
76+
if length > 0 {
77+
var range = NSMakeRange(NSNotFound, NSNotFound)
78+
var loc = 0
79+
var dict = attributes(at: loc, effectiveRange: &range) as NSDictionary
80+
if range.length == length {
81+
// Special single-attribute run case
82+
// If NSAttributeInfo is not written, then NSAttributes is a dictionary
83+
aCoder.encode(dict, forKey: "NSAttributes")
84+
} else {
85+
let attrsArray = NSMutableArray(capacity: 20)
86+
let data = NSMutableData(capacity: 100) ?? NSMutableData()
87+
let attrsTable = NSMutableDictionary()
88+
while true {
89+
var arraySlot = 0
90+
if let cachedSlot = attrsTable.object(forKey: dict) as? Int {
91+
arraySlot = cachedSlot
92+
} else {
93+
arraySlot = attrsArray.count
94+
attrsTable.setObject(arraySlot, forKey: dict)
95+
attrsArray.add(dict)
96+
}
97+
98+
_NSWriteIntToMutableAttributedStringCoding(range.length, data)
99+
_NSWriteIntToMutableAttributedStringCoding(arraySlot, data)
100+
101+
loc += range.length
102+
guard loc < length else { break }
103+
dict = attributes(at: loc, effectiveRange: &range) as NSDictionary
104+
}
105+
aCoder.encode(attrsArray, forKey: "NSAttributes")
106+
aCoder.encode(data, forKey: "NSAttributeInfo")
107+
}
108+
}
46109
}
47110

48111
static public var supportsSecureCoding: Bool {
@@ -117,6 +180,11 @@ open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCo
117180
return _attribute(attrName, atIndex: location, rangeInfo: rangeInfo)
118181
}
119182

183+
open override func isEqual(_ object: Any?) -> Bool {
184+
guard let other = object as? NSAttributedString else { return false }
185+
return isEqual(to: other)
186+
}
187+
120188
/// Returns a Boolean value that indicates whether the receiver is equal to another given attributed string.
121189
open func isEqual(to other: NSAttributedString) -> Bool {
122190
guard let runtimeClass = _CFRuntimeGetClassWithTypeID(CFAttributedStringGetTypeID()) else {
@@ -412,7 +480,13 @@ open class NSMutableAttributedString : NSAttributedString {
412480
}
413481

414482
public required init?(coder aDecoder: NSCoder) {
415-
NSUnimplemented()
483+
let mutableAttributedString = NSMutableAttributedString(string: "")
484+
guard _NSReadMutableAttributedStringWithCoder(aDecoder, mutableAttributedString: mutableAttributedString) else {
485+
return nil
486+
}
487+
488+
super.init(attributedString: mutableAttributedString)
489+
_string = NSMutableString(string: mutableAttributedString.string)
416490
}
417491

418492
}
@@ -431,3 +505,117 @@ private extension NSMutableAttributedString {
431505
return attributesDictionary._cfObject
432506
}
433507
}
508+
509+
// MARK: Coding
510+
511+
fileprivate let _allowedCodingClasses: [AnyClass] = [
512+
NSNumber.self,
513+
NSArray.self,
514+
NSDictionary.self,
515+
NSURL.self,
516+
NSString.self,
517+
]
518+
519+
internal func _NSReadIntFromMutableAttributedStringCoding(_ data: NSData, _ startingOffset: Int) -> (value: Int, newOffset: Int)? {
520+
var multiplier = 1
521+
var offset = startingOffset
522+
let length = data.length
523+
524+
var value = 0
525+
526+
while offset < length {
527+
let i = Int(data.bytes.load(fromByteOffset: offset, as: UInt8.self))
528+
529+
offset += 1
530+
531+
let isLast = i < 128
532+
533+
let intermediateValue = multiplier.multipliedReportingOverflow(by: isLast ? i : (i - 128))
534+
guard !intermediateValue.overflow else { return nil }
535+
536+
let newValue = value.addingReportingOverflow(intermediateValue.partialValue)
537+
guard !newValue.overflow else { return nil }
538+
539+
value = newValue.partialValue
540+
541+
if isLast {
542+
return (value: value, newOffset: offset)
543+
}
544+
545+
multiplier *= 128
546+
}
547+
548+
return nil // Getting to the end of the stream indicates error, since we were still expecting more bytes
549+
}
550+
551+
internal func _NSWriteIntToMutableAttributedStringCoding(_ i: Int, _ data: NSMutableData) {
552+
if i > 127 {
553+
let byte = UInt8(128 + i % 128);
554+
data.append(Data([byte]))
555+
_NSWriteIntToMutableAttributedStringCoding(i / 128, data)
556+
} else {
557+
data.append(Data([UInt8(i)]))
558+
}
559+
}
560+
561+
internal func _NSReadMutableAttributedStringWithCoder(_ decoder: NSCoder, mutableAttributedString: NSMutableAttributedString) -> Bool {
562+
563+
// NSAttributedString.Key is not currently bridging correctly every time we'd like it to.
564+
// Ensure we manually go through String in the meanwhile. SR-XXXX.
565+
func toAttributesDictionary(_ ns: NSDictionary) -> [NSAttributedString.Key: Any]? {
566+
if let bridged = __SwiftValue.fetch(ns) as? [String: Any] {
567+
return Dictionary(bridged.map { (NSAttributedString.Key($0.key), $0.value) }, uniquingKeysWith: { $1 })
568+
} else {
569+
return nil
570+
}
571+
}
572+
573+
guard decoder.allowsKeyedCoding else { /* Unkeyed unarchiving is not supported. */ return false }
574+
575+
let string = decoder.decodeObject(of: NSString.self, forKey: "NSString") ?? ""
576+
577+
mutableAttributedString.replaceCharacters(in: NSMakeRange(0, 0), with: string as String)
578+
579+
guard string.length > 0 else { return true }
580+
581+
var allowed = _allowedCodingClasses
582+
for aClass in decoder.allowedClasses ?? [] {
583+
if !allowed.contains(where: { $0 === aClass }) {
584+
allowed.append(aClass)
585+
}
586+
}
587+
588+
let attributes = decoder.decodeObject(of: allowed, forKey: "NSAttributes")
589+
// If this is present, 'attributes' should be an array; otherwise, a dictionary:
590+
let attrData = decoder.decodeObject(of: NSData.self, forKey: "NSAttributeInfo")
591+
if attrData == nil, let attributes = attributes as? [NSAttributedString.Key : Any] {
592+
mutableAttributedString.setAttributes(attributes, range: NSMakeRange(0, string.length))
593+
return true
594+
} else if let attrData = attrData, let attributesNS = attributes as? [NSDictionary] {
595+
let attributes = attributesNS.compactMap { toAttributesDictionary($0) }
596+
guard attributes.count == attributesNS.count else { return false }
597+
598+
var loc = 0
599+
var offset = 0
600+
let length = string.length
601+
while loc < length {
602+
var rangeLen = 0, arraySlot = 0
603+
guard let intResult1 = _NSReadIntFromMutableAttributedStringCoding(attrData, offset) else { return false }
604+
rangeLen = intResult1.value
605+
offset = intResult1.newOffset
606+
607+
guard let intResult2 = _NSReadIntFromMutableAttributedStringCoding(attrData, offset) else { return false }
608+
arraySlot = intResult2.value
609+
offset = intResult2.newOffset
610+
611+
guard arraySlot < attributes.count else { return false }
612+
mutableAttributedString.setAttributes(attributes[arraySlot], range: NSMakeRange(loc, rangeLen))
613+
614+
loc += rangeLen
615+
}
616+
617+
return true
618+
}
619+
620+
return false
621+
}

0 commit comments

Comments
 (0)