From 5a3e86e3e30a11eecda138215dd7ac3636d107b7 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 1 Aug 2017 12:55:05 -0300 Subject: [PATCH 1/7] UnsupportedHTML: Wiring HTMLElementRepresentation --- .../Styles/UnsupportedHTML.swift | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/Aztec/Classes/NSAttributedString/Styles/UnsupportedHTML.swift b/Aztec/Classes/NSAttributedString/Styles/UnsupportedHTML.swift index 8468aa36c..8cf59fd76 100644 --- a/Aztec/Classes/NSAttributedString/Styles/UnsupportedHTML.swift +++ b/Aztec/Classes/NSAttributedString/Styles/UnsupportedHTML.swift @@ -10,34 +10,24 @@ let UnsupportedHTMLAttributeName = "UnsupportedHTMLAttributeName" // class UnsupportedHTML: NSObject { - /// HTML Snippets not (natively) supported by the Editor (which will be re-serialized!!) + /// ElementRepresentation for Unsupported HTML /// - private(set) var snippets = [String]() + let representations: [HTMLElementRepresentation] - /// HTML Snippets not supported, converted back to their ElementNode representations + /// Default Initializer /// - var elements: [ElementNode] { - let converter = InHTMLConverter() - - return snippets.flatMap { snippet in - // Strip the Root Node(s): Always return the first child element - let root = converter.convert(snippet) - return root.children.first as? ElementNode - } + init(representations: [HTMLElementRepresentation]) { + self.representations = representations } /// Required Initializers /// - public required convenience init?(coder aDecoder: NSCoder) { - self.init() - self.snippets = aDecoder.decodeObject(forKey: Keys.elements) as? [String] ?? [] - } + public required init?(coder aDecoder: NSCoder) { + guard let representations = aDecoder.decodeObject(forKey: Keys.representations) as? [HTMLElementRepresentation] else { + return nil + } - /// Appends the specified Element Representation - /// - func append(element: ElementNode) { - let snippet = OutHTMLConverter().convert(element) - snippets.append(snippet) + self.representations = representations } } @@ -47,10 +37,10 @@ class UnsupportedHTML: NSObject { extension UnsupportedHTML: NSCoding { struct Keys { - static let elements = "elements" + static let representations = "representations" } open func encode(with aCoder: NSCoder) { - aCoder.encode(snippets, forKey: Keys.elements) + aCoder.encode(representations, forKey: Keys.representations) } } From 48b310a9f616862ba7c1a24866b0ffe113dc9175 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 1 Aug 2017 12:55:17 -0300 Subject: [PATCH 2/7] UnsupportedHTMLTests: Updates Tests --- AztecTests/TextKit/UnsupportedHTMLTests.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/AztecTests/TextKit/UnsupportedHTMLTests.swift b/AztecTests/TextKit/UnsupportedHTMLTests.swift index c00c286d3..c20cda798 100644 --- a/AztecTests/TextKit/UnsupportedHTMLTests.swift +++ b/AztecTests/TextKit/UnsupportedHTMLTests.swift @@ -9,9 +9,7 @@ class UnsupportedHTMLTests: XCTestCase { /// Verifies that a UnsupportedHTML Instance can get properly serialized back and forth /// func testSnippetsGetProperlyEncodedAndDecoded() { - let unsupported = UnsupportedHTML() - unsupported.append(element: sampleElement) - unsupported.append(element: sampleElement) + let unsupported = UnsupportedHTML(representations: [sampleRepresentation, sampleRepresentation]) let data = NSKeyedArchiver.archivedData(withRootObject: unsupported) guard let restored = NSKeyedUnarchiver.unarchiveObject(with: data) as? UnsupportedHTML else { @@ -19,12 +17,10 @@ class UnsupportedHTMLTests: XCTestCase { return } - let elements = restored.elements - XCTAssert(elements.count == 2) + XCTAssert(restored.representations.count == 2) - for element in elements { - XCTAssert(element.children == sampleChildren) - XCTAssert(element.attributes == sampleAttributes) + for representation in restored.representations { + XCTAssert(representation == sampleRepresentation) } } } @@ -55,4 +51,8 @@ private extension UnsupportedHTMLTests { var sampleElement: ElementNode { return ElementNode(name: "Test", attributes: self.sampleAttributes, children: self.sampleChildren) } + + var sampleRepresentation: HTMLElementRepresentation { + return HTMLElementRepresentation(self.sampleElement) + } } From 3cb38c9d5fb6d8d6808a23e72051ef1342c4dfa5 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 1 Aug 2017 12:57:54 -0300 Subject: [PATCH 3/7] NSAttributedStringToNodes: Wiring UnsupportedHTML New Property --- Aztec/Classes/Converters/NSAttributedStringToNodes.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Aztec/Classes/Converters/NSAttributedStringToNodes.swift b/Aztec/Classes/Converters/NSAttributedStringToNodes.swift index ab99b7b91..9f59ed768 100644 --- a/Aztec/Classes/Converters/NSAttributedStringToNodes.swift +++ b/Aztec/Classes/Converters/NSAttributedStringToNodes.swift @@ -685,11 +685,13 @@ private extension NSAttributedStringToNodes { /// Extracts all of the Unsupported HTML Snippets contained within a collection of Attributes. /// private func processUnsupportedHTML(in attributes: [String: Any]) -> [ElementNode] { - guard let unsupported = attributes[UnsupportedHTMLAttributeName] as? UnsupportedHTML else { + guard let unsupportedHTML = attributes[UnsupportedHTMLAttributeName] as? UnsupportedHTML else { return [] } - return unsupported.elements + return unsupportedHTML.representations.map { representation in + return representation.toElementNode() + } } } From df1c2b1a8e079208ef2ca15bd0b7b060c74a367b Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 1 Aug 2017 12:58:08 -0300 Subject: [PATCH 4/7] HTMLNodeToNSAttributedString: Wiring UnsupportedHTML's API --- .../HTMLNodeToNSAttributedString.swift | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Aztec/Classes/Converters/HTMLNodeToNSAttributedString.swift b/Aztec/Classes/Converters/HTMLNodeToNSAttributedString.swift index 8843cd4e7..b8cb25e04 100644 --- a/Aztec/Classes/Converters/HTMLNodeToNSAttributedString.swift +++ b/Aztec/Classes/Converters/HTMLNodeToNSAttributedString.swift @@ -250,17 +250,17 @@ private extension HTMLNodeToNSAttributedString { return attributes } - let representation = HTMLRepresentation(for: .element(HTMLElementRepresentation(element))) + let elementRepresentation = HTMLElementRepresentation(element) + let representation = HTMLRepresentation(for: .element(elementRepresentation)) var finalAttributes = attributes if let elementFormatter = formatter(for: element) { finalAttributes = elementFormatter.apply(to: finalAttributes, andStore: representation) - } else if element.name == StandardElementType.li.rawValue { + } else if element.name == StandardElementType.li.rawValue { // ^ Since LI is handled by the OL and UL formatters, we can safely ignore it here. - finalAttributes = attributes } else { - finalAttributes = self.attributes(storing: element, in: finalAttributes) + finalAttributes = self.attributes(storing: elementRepresentation, in: finalAttributes) } for attribute in element.attributes { @@ -304,12 +304,17 @@ private extension HTMLNodeToNSAttributedString { /// /// - Returns: A collection of NSAttributedString Attributes, including the specified HTMLElementRepresentation. /// - private func attributes(storing element: ElementNode, in attributes: [String: Any]) -> [String: Any] { - let unsupportedHTML = attributes[UnsupportedHTMLAttributeName] as? UnsupportedHTML ?? UnsupportedHTML() - unsupportedHTML.append(element: element) - + private func attributes(storing representation: HTMLElementRepresentation, in attributes: [String: Any]) -> [String: Any] { + let unsupportedHTML = attributes[UnsupportedHTMLAttributeName] as? UnsupportedHTML + var representations = unsupportedHTML?.representations ?? [] + representations.append(representation) + + // Note: + // We'll *ALWAYS* store a copy of the UnsupportedHTML instance. Reason is: reusing the old instance + // would mean affecting a range that may fall beyond what we expected! + // var updated = attributes - updated[UnsupportedHTMLAttributeName] = unsupportedHTML + updated[UnsupportedHTMLAttributeName] = UnsupportedHTML(representations: representations) return updated } From f1d5ae62192eb0f860e12b8df3b81faf9d7c42ec Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 1 Aug 2017 12:59:33 -0300 Subject: [PATCH 5/7] HTMLNodeToNSAttributedStringTests: Fixing Tests --- .../Converters/HTMLNodeToNSAttributedStringTests.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/AztecTests/Converters/HTMLNodeToNSAttributedStringTests.swift b/AztecTests/Converters/HTMLNodeToNSAttributedStringTests.swift index 7804bd1d1..2b2e265c1 100644 --- a/AztecTests/Converters/HTMLNodeToNSAttributedStringTests.swift +++ b/AztecTests/Converters/HTMLNodeToNSAttributedStringTests.swift @@ -33,17 +33,18 @@ class HTMLNodeToNSAttributedStringTests: XCTestCase { return } + let representations = unsupportedHTML.representations XCTAssert(range.length == textNode.length()) - XCTAssert(unsupportedHTML.elements.count == 2) + XCTAssert(representations.count == 2) - let restoredSpanElement2 = unsupportedHTML.elements.last + let restoredSpanElement2 = representations.last XCTAssertEqual(restoredSpanElement2?.name, "span") let restoredSpanAttribute2 = restoredSpanElement2?.attributes.first XCTAssertEqual(restoredSpanAttribute2?.name, "class") XCTAssertEqual(restoredSpanAttribute2?.value.toString(), "aztec") - let restoredSpanElement1 = unsupportedHTML.elements.first + let restoredSpanElement1 = representations.first XCTAssertEqual(restoredSpanElement1?.name, "span") let restoredSpanAttribute1 = restoredSpanElement1?.attributes.first @@ -103,7 +104,7 @@ class HTMLNodeToNSAttributedStringTests: XCTestCase { extension HTMLNodeToNSAttributedStringTests { func attributedString(from node: Node) -> NSAttributedString { - let descriptor = UIFont.boldSystemFont(ofSize: 14).fontDescriptor + let descriptor = UIFont.systemFont(ofSize: 14).fontDescriptor let converter = HTMLNodeToNSAttributedString(usingDefaultFontDescriptor: descriptor) return converter.convert(node) From b3641ea9252dc1f05c73a4022bb2d16ee6aeade4 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 1 Aug 2017 13:01:35 -0300 Subject: [PATCH 6/7] NSAttributedStringToNodesTests: Fixing Unit Test --- .../Converters/NSAttributedStringToNodesTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AztecTests/Converters/NSAttributedStringToNodesTests.swift b/AztecTests/Converters/NSAttributedStringToNodesTests.swift index 34de17d5d..972a6b87a 100644 --- a/AztecTests/Converters/NSAttributedStringToNodesTests.swift +++ b/AztecTests/Converters/NSAttributedStringToNodesTests.swift @@ -615,11 +615,11 @@ class NSAttributedStringToNodesTests: XCTestCase { let testingString = NSMutableAttributedString(string: text) let spanElement = ElementNode(type: .span) + let representation = HTMLElementRepresentation(spanElement) - let unsupported = UnsupportedHTML() - unsupported.append(element: spanElement) - - testingString.addAttribute(UnsupportedHTMLAttributeName, value: unsupported, range: testingString.rangeOfEntireString) + // Store + let unsupportedHTML = UnsupportedHTML(representations: [representation]) + testingString.addAttribute(UnsupportedHTMLAttributeName, value: unsupportedHTML, range: testingString.rangeOfEntireString) // Convert + Verify let node = NSAttributedStringToNodes().convert(testingString) From 1046f1e19b09ca2c347bb219a0a07f513062e816 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 1 Aug 2017 13:06:05 -0300 Subject: [PATCH 7/7] HTMLNodeToNSAttributedStringTests: New Tests --- .../HTMLNodeToNSAttributedStringTests.swift | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/AztecTests/Converters/HTMLNodeToNSAttributedStringTests.swift b/AztecTests/Converters/HTMLNodeToNSAttributedStringTests.swift index 2b2e265c1..8b1bbe1e0 100644 --- a/AztecTests/Converters/HTMLNodeToNSAttributedStringTests.swift +++ b/AztecTests/Converters/HTMLNodeToNSAttributedStringTests.swift @@ -52,6 +52,7 @@ class HTMLNodeToNSAttributedStringTests: XCTestCase { XCTAssertEqual(restoredSpanAttribute1?.value.toString(), "first") } + /// Verifies that the DivFormatter effectively appends the DIV Element Representation, to the properties collection. /// func testHtmlDivFormatterEffectivelyAppendsNewDivProperty() { @@ -96,6 +97,41 @@ class HTMLNodeToNSAttributedStringTests: XCTestCase { XCTAssert(restoredDiv3.name == divNode3.name) XCTAssert(restoredDiv3.attributes == [divAttr3]) } + + + /// Verifies that BR elements contained within div tags do not cause any side effect. + /// Ref. #658 + /// + func testLineBreakTagWithinHTMLDivGetsProperlyEncodedAndDecoded() { + let inHtml = "

Aztec, don't forget me!
" + + let inNode = InHTMLConverter().convert(inHtml) + let attrString = attributedString(from: inNode) + + let outNode = NSAttributedStringToNodes().convert(attrString) + let outHtml = OutHTMLConverter().convert(outNode) + + XCTAssertEqual(outHtml, inHtml) + } + + + /// Verifies that BR elements contained within span tags do not cause Data Loss. + /// Ref. #658 + /// + func testLineBreakTagWithinUnsupportedHTMLDoesNotCauseDataLoss() { + let inHtml = "
Aztec, don't forget me!
" + let expectedHtml = "
Aztec, don't forget me!" +// TODO: The actual expected html should wrap the BR within a span tag. To be addressed in another PR! +// let expectedHtml = "
Aztec, don't forget me!" + + let inNode = InHTMLConverter().convert(inHtml) + let attrString = attributedString(from: inNode) + + let outNode = NSAttributedStringToNodes().convert(attrString) + let outHtml = OutHTMLConverter().convert(outNode) + + XCTAssertEqual(outHtml, expectedHtml) + } }