Skip to content

Commit

Permalink
Merge pull request #628 from wordpress-mobile/issue/627-copy-paste-ho…
Browse files Browse the repository at this point in the history
…tfix

Copy + Paste: Stripping Attributes that don't support NSCoding
  • Loading branch information
SergioEstevao authored Jul 19, 2017
2 parents 5194903 + 267391c commit 1087831
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 9 deletions.
16 changes: 16 additions & 0 deletions Aztec.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
59FEA0751D8BDFA700D138DF /* NodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FEA0661D8BDFA700D138DF /* NodeTests.swift */; };
59FEA0781D8BDFA700D138DF /* HTMLToAttributedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FEA06A1D8BDFA700D138DF /* HTMLToAttributedStringTests.swift */; };
59FEA07A1D8BDFA700D138DF /* InHTMLConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FEA06C1D8BDFA700D138DF /* InHTMLConverterTests.swift */; };
B50CE7321F1FA6260018CAA1 /* NSAttributedString+Strip.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50CE7311F1FA6260018CAA1 /* NSAttributedString+Strip.swift */; };
B50CE7341F1FABA00018CAA1 /* content.html in Resources */ = {isa = PBXBuildFile; fileRef = B50CE7331F1FABA00018CAA1 /* content.html */; };
B5375F471EC2566200F5D7EC /* String+HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5375F461EC2566200F5D7EC /* String+HTML.swift */; };
B5375F491EC2569500F5D7EC /* StringHTMLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5375F481EC2569500F5D7EC /* StringHTMLTests.swift */; };
B542D6421E9EB122009D12D3 /* PreFormaterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B542D6411E9EB122009D12D3 /* PreFormaterTests.swift */; };
Expand Down Expand Up @@ -207,6 +209,8 @@
59FEA06B1D8BDFA700D138DF /* InAttributeConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAttributeConverterTests.swift; sourceTree = "<group>"; };
59FEA06C1D8BDFA700D138DF /* InHTMLConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InHTMLConverterTests.swift; sourceTree = "<group>"; };
59FEA06D1D8BDFA700D138DF /* InNodeConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InNodeConverterTests.swift; sourceTree = "<group>"; };
B50CE7311F1FA6260018CAA1 /* NSAttributedString+Strip.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Strip.swift"; sourceTree = "<group>"; };
B50CE7331F1FABA00018CAA1 /* content.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = content.html; path = Example/Example/SampleContent/content.html; sourceTree = SOURCE_ROOT; };
B5375F461EC2566200F5D7EC /* String+HTML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+HTML.swift"; sourceTree = "<group>"; };
B5375F481EC2569500F5D7EC /* StringHTMLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StringHTMLTests.swift; path = Extensions/StringHTMLTests.swift; sourceTree = "<group>"; };
B542D6411E9EB122009D12D3 /* PreFormaterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreFormaterTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -374,6 +378,7 @@
isa = PBXGroup;
children = (
5951CB9E1D8BC93600E1866F /* Info.plist */,
B50CE7351F1FABA40018CAA1 /* Resources */,
59FEA05D1D8BDFA700D138DF /* Exporter */,
B5657C161EE99AE000579FE1 /* Converters */,
F18733C61DA0970E005AEB80 /* Extensions */,
Expand Down Expand Up @@ -484,6 +489,7 @@
FF7C89AF1E3BC52F000472A8 /* NSAttributedString+FontTraits.swift */,
B5BC4FED1DA2C17800614582 /* NSAttributedString+Lists.swift */,
F10BE6191EA7AE9D002E4625 /* NSAttributedString+ReplaceOcurrences.swift */,
B50CE7311F1FA6260018CAA1 /* NSAttributedString+Strip.swift */,
FFD0FEB61DAE59A700430586 /* NSLayoutManager+Attachments.swift */,
F10BE6171EA7ADA6002E4625 /* NSMutableAttributedString+ReplaceOcurrences.swift */,
F18733C41DA096EE005AEB80 /* NSRange+Helpers.swift */,
Expand Down Expand Up @@ -609,6 +615,14 @@
path = Importer;
sourceTree = "<group>";
};
B50CE7351F1FABA40018CAA1 /* Resources */ = {
isa = PBXGroup;
children = (
B50CE7331F1FABA00018CAA1 /* content.html */,
);
name = Resources;
sourceTree = "<group>";
};
B5657C161EE99AE000579FE1 /* Converters */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -918,6 +932,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B50CE7341F1FABA00018CAA1 /* content.html in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1022,6 +1037,7 @@
F1FA0E881E6EF514009D98EE /* TextNode.swift in Sources */,
599F253A1D8BC9A1002871D6 /* InNodeConverter.swift in Sources */,
F12F586F1EF20394008AE298 /* PreFormatter.swift in Sources */,
B50CE7321F1FA6260018CAA1 /* NSAttributedString+Strip.swift in Sources */,
B5B86D371DA3EC250083DB3F /* NSRange+Helpers.swift in Sources */,
B577DC671E7B18F80012A1F8 /* CommentNodeDescriptor.swift in Sources */,
599F25501D8BC9A1002871D6 /* FormattingIdentifier.swift in Sources */,
Expand Down
28 changes: 28 additions & 0 deletions Aztec/Classes/Extensions/NSAttributedString+Strip.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation


// MARK: - NSAttributedString: Stripping Attributes
//
extension NSAttributedString {

/// Removes attributes of the specified Types, and returns a clean copy of the receiver.
///
func stripAttributes(of kinds: [Any.Type]) -> NSAttributedString {
guard let clean = mutableCopy() as? NSMutableAttributedString else {
fatalError()
}

let range = clean.rangeOfEntireString
clean.enumerateAttributes(in: range, options: []) { (attributes, range, _) in
for (key, value) in attributes {
guard kinds.contains(where: { type(of: value) == $0 }) else {
continue
}

clean.removeAttribute(key, range: range)
}
}

return clean
}
}
14 changes: 12 additions & 2 deletions Aztec/Classes/TextKit/ImageAttachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import UIKit

/// Custom text attachment.
///
open class ImageAttachment: MediaAttachment
{
open class ImageAttachment: MediaAttachment {

/// Attachment Alignment
///
open var alignment: Alignment = .center {
Expand Down Expand Up @@ -54,6 +54,15 @@ open class ImageAttachment: MediaAttachment
}
}

/// Required Initializer
///
required public init(data contentData: Data?, ofType uti: String?) {
super.init(data: contentData, ofType: uti)
}


// MARK: - NSCoder Support

override open func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(alignment.rawValue, forKey: EncodeKeys.alignment.rawValue)
Expand All @@ -64,6 +73,7 @@ open class ImageAttachment: MediaAttachment
case alignment
case size
}

// MARK: - Origin calculation

override func xPosition(forContainerWidth containerWidth: CGFloat) -> CGFloat {
Expand Down
2 changes: 1 addition & 1 deletion Aztec/Classes/TextKit/MediaAttachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ open class MediaAttachment: NSTextAttachment
}
}

override init(data contentData: Data?, ofType uti: String?) {
override required public init(data contentData: Data?, ofType uti: String?) {
identifier = ""
url = nil
super.init(data: contentData, ofType: uti)
Expand Down
10 changes: 8 additions & 2 deletions Aztec/Classes/TextKit/TextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,21 @@ open class TextView: UITextView {

// MARK: - Intercept copy paste operations

private let unsupportedCopyAttributes: [Any.Type] = [HTMLElementRepresentation.self, UnsupportedHTML.self]

open override func cut(_ sender: Any?) {
let data = storage.attributedSubstring(from: selectedRange).archivedData()
// FIXME: This is a temporary workaround for Issue #626
let substring = storage.attributedSubstring(from: selectedRange).stripAttributes(of: unsupportedCopyAttributes)
let data = substring.archivedData()
super.cut(sender)

storeInPasteboard(encoded: data)
}

open override func copy(_ sender: Any?) {
let data = storage.attributedSubstring(from: selectedRange).archivedData()
// FIXME: This is a temporary workaround for Issue #626
let substring = storage.attributedSubstring(from: selectedRange).stripAttributes(of: unsupportedCopyAttributes)
let data = substring.archivedData()
super.copy(sender)

storeInPasteboard(encoded: data)
Expand Down
15 changes: 14 additions & 1 deletion Aztec/Classes/TextKit/VideoAttachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,23 @@ open class VideoAttachment: MediaAttachment {
srcURL = aDecoder.decodeObject(forKey: EncodeKeys.srcURL.rawValue) as? URL
}
}


/// Required Initializer
///
required public init(identifier: String, url: URL?) {
self.srcURL = nil
super.init(identifier: identifier, url: url)
}

/// Required Initializer
///
required public init(data contentData: Data?, ofType uti: String?) {
super.init(data: contentData, ofType: uti)
}


// MARK: - NSCoder Support

override open func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
if let url = self.srcURL {
Expand All @@ -70,6 +81,8 @@ open class VideoAttachment: MediaAttachment {
fileprivate enum EncodeKeys: String {
case srcURL
}


// MARK: - Origin calculation

override func xPosition(forContainerWidth containerWidth: CGFloat) -> CGFloat {
Expand Down
42 changes: 39 additions & 3 deletions AztecTests/TextViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ class TextViewTests: XCTestCase {

// MARK: - TextView construction

func createEmptyTextView() -> Aztec.TextView {
func createEmptyTextView() -> TextView {
let richTextView = Aztec.TextView(defaultFont: UIFont.systemFont(ofSize: 14), defaultMissingImage: UIImage())
richTextView.textAttachmentDelegate = attachmentDelegate
richTextView.registerAttachmentImageProvider(attachmentDelegate)
return richTextView
}

func createTextView(withHTML html: String) -> Aztec.TextView {
func createTextView(withHTML html: String) -> TextView {
let richTextView = Aztec.TextView(defaultFont: UIFont.systemFont(ofSize: 14), defaultMissingImage: UIImage())
richTextView.textAttachmentDelegate = attachmentDelegate
richTextView.setHTML(html)
Expand All @@ -47,7 +47,7 @@ class TextViewTests: XCTestCase {
return richTextView
}

func createTextViewWithContent() -> Aztec.TextView {
func createTextViewWithContent() -> TextView {
let paragraph = "Lorem ipsum dolar sit amet.\n"
let richTextView = Aztec.TextView(defaultFont: UIFont.systemFont(ofSize: 14), defaultMissingImage: UIImage())
richTextView.textAttachmentDelegate = attachmentDelegate
Expand All @@ -63,6 +63,24 @@ class TextViewTests: XCTestCase {
return richTextView
}

func createTextViewWithSampleHTML() -> TextView {
return createTextView(withHTML: loadSampleHTML())
}


// MARK: - Sample HTML Retrieval

func loadSampleHTML() -> String {
guard let path = Bundle(for: type(of: self)).path(forResource: "content", ofType: "html"),
let sample = try? String(contentsOfFile: path)
else {
fatalError()
}

return sample
}


// Confirm the composed textView is property configured.

func testTextViewReferencesStorage() {
Expand Down Expand Up @@ -1516,4 +1534,22 @@ class TextViewTests: XCTestCase {

XCTAssertEqual(pristineHTML, generatedHTML)
}

/// This test verifies that copying the Sample HTML Document does not trigger a crash.
/// Ref. Issue #626: NSKeyedArchiver Crash
///
func testCopyDoesNotCauseAztecToCrash() {
let textView = createTextViewWithSampleHTML()
textView.selectedRange = textView.storage.rangeOfEntireString
textView.copy(nil)
}

/// This test verifies that cutting the Sample HTML Document does not trigger a crash.
/// Ref. Issue #626: NSKeyedArchiver Crash
///
func testCutDoesNotCauseAztecToCrash() {
let textView = createTextViewWithSampleHTML()
textView.selectedRange = textView.storage.rangeOfEntireString
textView.cut(nil)
}
}

0 comments on commit 1087831

Please sign in to comment.