Skip to content
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
16 changes: 10 additions & 6 deletions Aztec/Classes/Extensions/NSAttributedString+Attachments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import Foundation
import UIKit


// MARK: - Constants
//
extension String {
/// String containing the NSTextAttachment Character
///
static let textAttachment = String(UnicodeScalar(NSAttachmentCharacter)!)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loved this little touch!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I absolutely LOVE 'extensible enums' (that's this trick's name!)

}


// MARK: - NSAttributedString Extension for Attachments
//
extension NSAttributedString
Expand All @@ -11,11 +20,6 @@ extension NSAttributedString
static let lengthOfTextAttachment = NSAttributedString(attachment: NSTextAttachment()).length


/// String containing the NSTextAttachment Character
///
static let textAttachmentString = String(UnicodeScalar(NSAttachmentCharacter)!)



/// Helper Initializer: returns an Attributed String, with the specified attachment, styled with a given
/// collection of attributes.
Expand All @@ -24,7 +28,7 @@ extension NSAttributedString
var attributesWithAttachment = attributes
attributesWithAttachment[.attachment] = attachment

self.init(string: NSAttributedString.textAttachmentString, attributes: attributesWithAttachment)
self.init(string: .textAttachment, attributes: attributesWithAttachment)
}

/// Loads any NSTextAttachment's lazy file reference, into a UIImage instance, in memory.
Expand Down
26 changes: 26 additions & 0 deletions Aztec/Classes/Libxml2/DOM/Data/ElementNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,32 @@ public class ElementNode: Node {
}


/// If there's exactly just one child node, this method will return it's instance. Otherwise, nil will be returned
///
func onlyChild() -> ElementNode? {
guard children.count == 1 else {
return nil
}

return children.first as? ElementNode
}


/// Returns the child ElementNode of the specified nodeType -whenever there's a *single* child-, or nil otherwise.
///
/// - Parameter type: Type of the 'single child' node to be retrieved.
///
/// - Returns: the requested child (if it's the only children in the collection, and if the type matches), or nil otherwise.
///
func onlyChild(ofType type: StandardElementType) -> ElementNode? {
guard let child = onlyChild(), child.isNodeType(type) else {
return nil
}

return child
}


/// Indicates whether the children of the specified node can be merged in, or not.
///
/// - Parameters:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,10 @@ extension AttributedStringSerializer {
}
}

private extension AttributedStringSerializer {

// MARK: - Implicit Representations
// MARK: - Implicit Representations
//
private extension AttributedStringSerializer {

/// Some elements have an implicit representation that doesn't really follow the standard
/// conversion logic. This method takes care of such specialized conversions.
Expand All @@ -376,23 +377,15 @@ private extension AttributedStringSerializer {
guard let elementType = element.standardName else {
return nil
}

if let imgElement = linkedImageElement(for: element) {
var attributesWithoutLink = attributes
attributesWithoutLink[.link] = nil
attributesWithoutLink[.linkHtmlRepresentation] = nil

let imgAttributes = self.attributes(for: imgElement, inheriting: attributesWithoutLink)
let attachment = imgAttributes[.attachment] as! ImageAttachment
let linkText = element.stringValueForAttribute(named: HTMLLinkAttribute.Href.rawValue) ?? ""
attachment.linkURL = URL(string: linkText)

return implicitRepresentation(for: imgElement, inheriting: imgAttributes)!

if let linkedImageAttributes = linkedImageAttributes(for: element, inheriting: attributes) {
return implicitRepresentation(for: .img, inheriting: linkedImageAttributes)
}

return implicitRepresentation(for: elementType, inheriting: attributes)
}


/// Some element types have an implicit representation that doesn't really follow the standard
/// conversion logic. This method takes care of such specialized conversions.
///
Expand All @@ -406,33 +399,47 @@ private extension AttributedStringSerializer {

switch elementType {
case .hr, .img, .video:
return NSAttributedString(string: String(UnicodeScalar(NSAttachmentCharacter)!), attributes: attributes)
return NSAttributedString(string: .textAttachment, attributes: attributes)
case .br:
return NSAttributedString(.lineSeparator, attributes: attributes)
default:
return nil
}
}

/// Checks if the specified node is of type '.a' with one child '.img' node.
/// If true, returns the '.img' node.


/// Whenever the `element`'s nodeType is `a` (link!), and there's a single child of the `.img` type, this method will return the
/// NSAttributedString attributes representing the 'Linked Image' Element.
///
/// - Parameters:
/// - element: the node to get the information from
/// - element: The container element.
/// - attributes: NSAttributedString attributes, to be inherited.
///
/// - Returns: the child '.img' node if there is one
/// - Returns: The collection of 'Inherited Attributes' with it's internal ImageAttachment modified, so that it carries the 'linkElement'
/// target URL.
///
private func linkedImageElement(for element: ElementNode) -> ElementNode? {
guard element.isNodeType(.a) && element.children.count == 1,
let imgElement = element.children.first as? ElementNode,
imgElement.isNodeType(.img) else {
return nil
private func linkedImageAttributes(for element: ElementNode, inheriting attributes: [AttributedStringKey: Any]) -> [AttributedStringKey: Any]? {
guard element.isNodeType(.a),
let imgElement = element.onlyChild(ofType: .img)
else {
return nil
}

return imgElement

var attributesWithoutLink = attributes
attributesWithoutLink[.link] = nil
attributesWithoutLink[.linkHtmlRepresentation] = nil

let imgAttributes = self.attributes(for: imgElement, inheriting: attributesWithoutLink)
let linkText = element.stringValueForAttribute(named: HTMLLinkAttribute.Href.rawValue) ?? ""

let attachment = imgAttributes[.attachment] as! ImageAttachment
attachment.linkURL = URL(string: linkText)

return imgAttributes
}
}



// MARK: - Text Sanitization for Rendering

private extension AttributedStringSerializer {
Expand Down
31 changes: 31 additions & 0 deletions AztecTests/HTML/Nodes/ElementNodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ElementNodeTests: XCTestCase {
XCTAssert(style1 == style1)
}


/// Verifies that two different ElementNode(s) instances, with the same name and the exact same Children array
/// return true when equality is checked.
///
Expand All @@ -37,4 +38,34 @@ class ElementNodeTests: XCTestCase {
XCTAssert(style1 == style2)
XCTAssert(style1 !== style2)
}


/// Verifies that `onlyChild` returns the receiver's only child, if it's type matches with the specified one.
///
func testOnlyChildReturnsSingleChildrenIfItRepresentsAnImage() {
let image = ElementNode(type: .img)
let parent = ElementNode(type: .a, attributes: [], children: [image])

XCTAssertEqual(parent.onlyChild(ofType: .img), image)
}


/// Verifies that `onlyChild` returns nil, if there is more than one children, no matter if their type match with the specified one.
///
func testOnlyChildReturnsNilIfThereIsMoreThanOneChild() {
let image = ElementNode(type: .img)
let parent = ElementNode(type: .a, attributes: [], children: [image, image])

XCTAssertNil(parent.onlyChild(ofType: .img))
}


/// Verifies that `onlyChild` returns nil, if there is at least one child, but with different type.
///
func testOnlyChildReturnsNilIfThereIsNoMatchingChild() {
let image = ElementNode(type: .img)
let parent = ElementNode(type: .a, attributes: [], children: [image])

XCTAssertNil(parent.onlyChild(ofType: .b))
}
}