Skip to content

Parity: NSAttributedString #2082

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 11, 2019
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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ if(ENABLE_TESTING)
TestFoundation/HTTPServer.swift
Foundation/ProgressFraction.swift
TestFoundation/Utilities.swift
TestFoundation/FixtureValues.swift
# Test Cases
TestFoundation/TestAffineTransform.swift
TestFoundation/TestBundle.swift
Expand Down Expand Up @@ -499,6 +500,7 @@ if(ENABLE_TESTING)
${CMAKE_SOURCE_DIR}/TestFoundation/Resources/NSKeyedUnarchiver-UUIDTest.plist
${CMAKE_SOURCE_DIR}/TestFoundation/Resources/NSKeyedUnarchiver-OrderedSetTest.plist
${CMAKE_SOURCE_DIR}/TestFoundation/Resources/TestFileWithZeros.txt
${CMAKE_SOURCE_DIR}/TestFoundation/Fixtures
SWIFT_FLAGS
${deployment_enable_libdispatch}
-I;${CMAKE_CURRENT_BINARY_DIR}/swift
Expand Down
13 changes: 11 additions & 2 deletions Foundation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
153E951120111DC500F250BE /* CFKnownLocations.h in Headers */ = {isa = PBXBuildFile; fileRef = 153E950F20111DC500F250BE /* CFKnownLocations.h */; settings = {ATTRIBUTES = (Private, ); }; };
153E951220111DC500F250BE /* CFKnownLocations.c in Sources */ = {isa = PBXBuildFile; fileRef = 153E951020111DC500F250BE /* CFKnownLocations.c */; };
15496CF1212CAEBA00450F5A /* CFAttributedStringPriv.h in Headers */ = {isa = PBXBuildFile; fileRef = 15496CF0212CAEBA00450F5A /* CFAttributedStringPriv.h */; };
155D3BBC22401D1100B0D38E /* FixtureValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 155D3BBB22401D1100B0D38E /* FixtureValues.swift */; };
1569BFA12187D04C009518FA /* CFCalendar_Enumerate.c in Sources */ = {isa = PBXBuildFile; fileRef = 1569BF9F2187D003009518FA /* CFCalendar_Enumerate.c */; };
1569BFA22187D04F009518FA /* CFCalendar_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1569BF9D2187CFFF009518FA /* CFCalendar_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; };
1569BFA52187D0E0009518FA /* CFDateInterval.c in Sources */ = {isa = PBXBuildFile; fileRef = 1569BFA32187D0E0009518FA /* CFDateInterval.c */; };
1569BFA62187D0E0009518FA /* CFDateInterval.h in Headers */ = {isa = PBXBuildFile; fileRef = 1569BFA42187D0E0009518FA /* CFDateInterval.h */; settings = {ATTRIBUTES = (Private, ); }; };
1569BFAD2187D529009518FA /* CFDateComponents.c in Sources */ = {isa = PBXBuildFile; fileRef = 1569BFAB2187D529009518FA /* CFDateComponents.c */; };
1569BFAE2187D529009518FA /* CFDateComponents.h in Headers */ = {isa = PBXBuildFile; fileRef = 1569BFAC2187D529009518FA /* CFDateComponents.h */; settings = {ATTRIBUTES = (Private, ); }; };
156C846E224069A100607D44 /* Fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 156C846D224069A000607D44 /* Fixtures */; };
1578DA09212B4061003C9516 /* CFRuntime_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1578DA08212B4060003C9516 /* CFRuntime_Internal.h */; };
1578DA0D212B4070003C9516 /* CFMachPort_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1578DA0A212B406F003C9516 /* CFMachPort_Internal.h */; };
1578DA0E212B4070003C9516 /* CFMachPort_Lifetime.c in Sources */ = {isa = PBXBuildFile; fileRef = 1578DA0B212B406F003C9516 /* CFMachPort_Lifetime.c */; };
Expand Down Expand Up @@ -554,12 +556,14 @@
153E950F20111DC500F250BE /* CFKnownLocations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CFKnownLocations.h; sourceTree = "<group>"; };
153E951020111DC500F250BE /* CFKnownLocations.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = CFKnownLocations.c; sourceTree = "<group>"; };
15496CF0212CAEBA00450F5A /* CFAttributedStringPriv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFAttributedStringPriv.h; sourceTree = "<group>"; };
155D3BBB22401D1100B0D38E /* FixtureValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixtureValues.swift; sourceTree = "<group>"; };
1569BF9D2187CFFF009518FA /* CFCalendar_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFCalendar_Internal.h; sourceTree = "<group>"; };
1569BF9F2187D003009518FA /* CFCalendar_Enumerate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFCalendar_Enumerate.c; sourceTree = "<group>"; };
1569BFA32187D0E0009518FA /* CFDateInterval.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFDateInterval.c; sourceTree = "<group>"; };
1569BFA42187D0E0009518FA /* CFDateInterval.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFDateInterval.h; sourceTree = "<group>"; };
1569BFAB2187D529009518FA /* CFDateComponents.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFDateComponents.c; sourceTree = "<group>"; };
1569BFAC2187D529009518FA /* CFDateComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFDateComponents.h; sourceTree = "<group>"; };
156C846D224069A000607D44 /* Fixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Fixtures; path = TestFoundation/Fixtures; sourceTree = SOURCE_ROOT; };
1578DA08212B4060003C9516 /* CFRuntime_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFRuntime_Internal.h; sourceTree = "<group>"; };
1578DA0A212B406F003C9516 /* CFMachPort_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFMachPort_Internal.h; sourceTree = "<group>"; };
1578DA0B212B406F003C9516 /* CFMachPort_Lifetime.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFMachPort_Lifetime.c; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1543,6 +1547,7 @@
EA66F6391BF1619600136161 /* Resources */ = {
isa = PBXGroup;
children = (
156C846D224069A000607D44 /* Fixtures */,
B98E33DC2136AA740044EBE9 /* TestFileWithZeros.txt */,
D370696D1C394FBF00295652 /* NSKeyedUnarchiver-RangeTest.plist */,
D3E8D6D41C36AC0C00295652 /* NSKeyedUnarchiver-RectTest.plist */,
Expand Down Expand Up @@ -1575,6 +1580,7 @@
children = (
C7DE1FCB21EEE67200174F35 /* TestUUID.swift */,
7D0DE86D211883F500540061 /* Utilities.swift */,
155D3BBB22401D1100B0D38E /* FixtureValues.swift */,
7D0DE86C211883F500540061 /* TestDateComponents.swift */,
6E203B8C1C1303BB003B2576 /* TestBundle.swift */,
A5A34B551C18C85D00FD972B /* TestByteCountFormatter.swift */,
Expand Down Expand Up @@ -2242,6 +2248,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
Expand Down Expand Up @@ -2284,6 +2291,7 @@
B910957A1EEF237800A71930 /* NSString-UTF16-LE-data.txt in Resources */,
D3E8D6D51C36AC0C00295652 /* NSKeyedUnarchiver-RectTest.plist in Resources */,
D3A597F81C3415CC00295652 /* NSKeyedUnarchiver-URLTest.plist in Resources */,
156C846E224069A100607D44 /* Fixtures in Resources */,
D3E8D6D31C36982700295652 /* NSKeyedUnarchiver-EdgeInsetsTest.plist in Resources */,
B907F36B20BB07A700013CBE /* NSString-ISO-8859-1-data.txt in Resources */,
D370696E1C394FBF00295652 /* NSKeyedUnarchiver-RangeTest.plist in Resources */,
Expand Down Expand Up @@ -2633,6 +2641,7 @@
5B13B3391C582D4C00651CE2 /* TestNSNull.swift in Sources */,
BD8042161E09857800487EB8 /* TestLengthFormatter.swift in Sources */,
5B13B3421C582D4C00651CE2 /* TestRunLoop.swift in Sources */,
155D3BBC22401D1100B0D38E /* FixtureValues.swift in Sources */,
5B13B34E1C582D4C00651CE2 /* TestXMLDocument.swift in Sources */,
5B13B32B1C582D4C00651CE2 /* TestNSData.swift in Sources */,
5B13B34C1C582D4C00651CE2 /* TestURLResponse.swift in Sources */,
Expand Down Expand Up @@ -3091,7 +3100,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
MACH_O_TYPE = mh_execute;
OTHER_SWIFT_FLAGS = "-DDEPLOYMENT_ENABLE_LIBDISPATCH -swift-version 4.2";
OTHER_SWIFT_FLAGS = "-DDEPLOYMENT_ENABLE_LIBDISPATCH -DDEPLOYMENT_RUNTIME_SWIFT";
PRODUCT_BUNDLE_IDENTIFIER = org.swift.TestFoundation;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand All @@ -3117,7 +3126,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
MACH_O_TYPE = mh_execute;
OTHER_SWIFT_FLAGS = "-DDEPLOYMENT_ENABLE_LIBDISPATCH -swift-version 4.2";
OTHER_SWIFT_FLAGS = "-DDEPLOYMENT_ENABLE_LIBDISPATCH -DDEPLOYMENT_RUNTIME_SWIFT";
PRODUCT_BUNDLE_IDENTIFIER = org.swift.TestFoundation;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down
196 changes: 192 additions & 4 deletions Foundation/NSAttributedString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,85 @@ extension NSAttributedString {
}
}

extension NSAttributedString.Key: _ObjectiveCBridgeable {
public func _bridgeToObjectiveC() -> NSString {
return rawValue as NSString
}

public static func _forceBridgeFromObjectiveC(_ source: NSString, result: inout NSAttributedString.Key?) {
result = NSAttributedString.Key(source as String)
}

public static func _conditionallyBridgeFromObjectiveC(_ source: NSString, result: inout NSAttributedString.Key?) -> Bool {
result = NSAttributedString.Key(source as String)
return true
}

public static func _unconditionallyBridgeFromObjectiveC(_ source: NSString?) -> NSAttributedString.Key {
guard let source = source else { return NSAttributedString.Key("") }
return NSAttributedString.Key(source as String)
}
}

@available(*, unavailable, renamed: "NSAttributedString.Key")
public typealias NSAttributedStringKey = NSAttributedString.Key


open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCoding {

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

public required init?(coder aDecoder: NSCoder) {
NSUnimplemented()
let mutableAttributedString = NSMutableAttributedString(string: "")
guard _NSReadMutableAttributedStringWithCoder(aDecoder, mutableAttributedString: mutableAttributedString) else {
return nil
}

// use the resulting _string and _attributeArray to initialize a new instance, just like init
_string = mutableAttributedString._string
_attributeArray = mutableAttributedString._attributeArray
}

open func encode(with aCoder: NSCoder) {
NSUnimplemented()
guard aCoder.allowsKeyedCoding else { fatalError("We do not support saving to a non-keyed coder.") }

aCoder.encode(string, forKey: "NSString")
let length = self.length

if length > 0 {
var range = NSMakeRange(NSNotFound, NSNotFound)
var loc = 0
var dict = attributes(at: loc, effectiveRange: &range) as NSDictionary
if range.length == length {
// Special single-attribute run case
// If NSAttributeInfo is not written, then NSAttributes is a dictionary
aCoder.encode(dict, forKey: "NSAttributes")
} else {
let attrsArray = NSMutableArray(capacity: 20)
let data = NSMutableData(capacity: 100) ?? NSMutableData()
let attrsTable = NSMutableDictionary()
while true {
var arraySlot = 0
if let cachedSlot = attrsTable.object(forKey: dict) as? Int {
arraySlot = cachedSlot
} else {
arraySlot = attrsArray.count
attrsTable.setObject(arraySlot, forKey: dict)
attrsArray.add(dict)
}

_NSWriteIntToMutableAttributedStringCoding(range.length, data)
_NSWriteIntToMutableAttributedStringCoding(arraySlot, data)

loc += range.length
guard loc < length else { break }
dict = attributes(at: loc, effectiveRange: &range) as NSDictionary
}
aCoder.encode(attrsArray, forKey: "NSAttributes")
aCoder.encode(data, forKey: "NSAttributeInfo")
}
}
}

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

open override func isEqual(_ object: Any?) -> Bool {
guard let other = object as? NSAttributedString else { return false }
return isEqual(to: other)
}

/// Returns a Boolean value that indicates whether the receiver is equal to another given attributed string.
open func isEqual(to other: NSAttributedString) -> Bool {
guard let runtimeClass = _CFRuntimeGetClassWithTypeID(CFAttributedStringGetTypeID()) else {
Expand Down Expand Up @@ -412,7 +480,13 @@ open class NSMutableAttributedString : NSAttributedString {
}

public required init?(coder aDecoder: NSCoder) {
NSUnimplemented()
let mutableAttributedString = NSMutableAttributedString(string: "")
guard _NSReadMutableAttributedStringWithCoder(aDecoder, mutableAttributedString: mutableAttributedString) else {
return nil
}

super.init(attributedString: mutableAttributedString)
_string = NSMutableString(string: mutableAttributedString.string)
}

}
Expand All @@ -431,3 +505,117 @@ private extension NSMutableAttributedString {
return attributesDictionary._cfObject
}
}

// MARK: Coding

fileprivate let _allowedCodingClasses: [AnyClass] = [
NSNumber.self,
NSArray.self,
NSDictionary.self,
NSURL.self,
NSString.self,
]

internal func _NSReadIntFromMutableAttributedStringCoding(_ data: NSData, _ startingOffset: Int) -> (value: Int, newOffset: Int)? {
var multiplier = 1
var offset = startingOffset
let length = data.length

var value = 0

while offset < length {
let i = Int(data.bytes.load(fromByteOffset: offset, as: UInt8.self))

offset += 1

let isLast = i < 128

let intermediateValue = multiplier.multipliedReportingOverflow(by: isLast ? i : (i - 128))
guard !intermediateValue.overflow else { return nil }

let newValue = value.addingReportingOverflow(intermediateValue.partialValue)
guard !newValue.overflow else { return nil }

value = newValue.partialValue

if isLast {
return (value: value, newOffset: offset)
}

multiplier *= 128
}

return nil // Getting to the end of the stream indicates error, since we were still expecting more bytes
}

internal func _NSWriteIntToMutableAttributedStringCoding(_ i: Int, _ data: NSMutableData) {
if i > 127 {
let byte = UInt8(128 + i % 128);
data.append(Data([byte]))
_NSWriteIntToMutableAttributedStringCoding(i / 128, data)
} else {
data.append(Data([UInt8(i)]))
}
}

internal func _NSReadMutableAttributedStringWithCoder(_ decoder: NSCoder, mutableAttributedString: NSMutableAttributedString) -> Bool {

// NSAttributedString.Key is not currently bridging correctly every time we'd like it to.
// Ensure we manually go through String in the meanwhile. SR-XXXX.
func toAttributesDictionary(_ ns: NSDictionary) -> [NSAttributedString.Key: Any]? {
if let bridged = __SwiftValue.fetch(ns) as? [String: Any] {
return Dictionary(bridged.map { (NSAttributedString.Key($0.key), $0.value) }, uniquingKeysWith: { $1 })
} else {
return nil
}
}

guard decoder.allowsKeyedCoding else { /* Unkeyed unarchiving is not supported. */ return false }

let string = decoder.decodeObject(of: NSString.self, forKey: "NSString") ?? ""

mutableAttributedString.replaceCharacters(in: NSMakeRange(0, 0), with: string as String)

guard string.length > 0 else { return true }

var allowed = _allowedCodingClasses
for aClass in decoder.allowedClasses ?? [] {
if !allowed.contains(where: { $0 === aClass }) {
allowed.append(aClass)
}
}

let attributes = decoder.decodeObject(of: allowed, forKey: "NSAttributes")
// If this is present, 'attributes' should be an array; otherwise, a dictionary:
let attrData = decoder.decodeObject(of: NSData.self, forKey: "NSAttributeInfo")
if attrData == nil, let attributesNS = attributes as? NSDictionary, let attributes = toAttributesDictionary(attributesNS) {
mutableAttributedString.setAttributes(attributes, range: NSMakeRange(0, string.length))
return true
} else if let attrData = attrData, let attributesNS = attributes as? [NSDictionary] {
let attributes = attributesNS.compactMap { toAttributesDictionary($0) }
guard attributes.count == attributesNS.count else { return false }

var loc = 0
var offset = 0
let length = string.length
while loc < length {
var rangeLen = 0, arraySlot = 0
guard let intResult1 = _NSReadIntFromMutableAttributedStringCoding(attrData, offset) else { return false }
rangeLen = intResult1.value
offset = intResult1.newOffset

guard let intResult2 = _NSReadIntFromMutableAttributedStringCoding(attrData, offset) else { return false }
arraySlot = intResult2.value
offset = intResult2.newOffset

guard arraySlot < attributes.count else { return false }
mutableAttributedString.setAttributes(attributes[arraySlot], range: NSMakeRange(loc, rangeLen))

loc += rangeLen
}

return true
}

return false
}
Loading