Skip to content

Commit 6dca557

Browse files
Fixes all unit tests.
1 parent 72b8754 commit 6dca557

File tree

6 files changed

+205
-180
lines changed

6 files changed

+205
-180
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>

Aztec/Classes/ElementConverters/Implementations/GenericElementConverter.swift

+187-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,58 @@ import UIKit
55
///
66
class GenericElementConverter: ElementConverter {
77

8+
// MARK: - Element Support
9+
810
/// This is a list of elements that don't produce an HTML attachment when they go through this converter
911
/// At some point we should modify how the conversion works, so that any supported element never goes through this
1012
/// converter at all, and this converter is turned into an `UnsupportedElementConverter()` exclusively.
1113
///
1214
private static let supportedElements: [Element] = [.a, .aztecRootNode, .b, .br, .blockquote, .del, .div, .em, .figure, .figcaption, .h1, .h2, .h3, .h4, .h5, .h6, .hr, .i, .img, .li, .ol, .p, .pre, .s, .span, .strike, .strong, .u, .ul, .video, .code]
1315

16+
// MARK: - Built-in formatter instances
17+
18+
lazy var blockquoteFormatter = BlockquoteFormatter()
19+
lazy var boldFormatter = BoldFormatter()
20+
lazy var divFormatter = HTMLDivFormatter()
21+
lazy var h1Formatter = HeaderFormatter(headerLevel: .h1)
22+
lazy var h2Formatter = HeaderFormatter(headerLevel: .h2)
23+
lazy var h3Formatter = HeaderFormatter(headerLevel: .h3)
24+
lazy var h4Formatter = HeaderFormatter(headerLevel: .h4)
25+
lazy var h5Formatter = HeaderFormatter(headerLevel: .h5)
26+
lazy var h6Formatter = HeaderFormatter(headerLevel: .h6)
27+
lazy var italicFormatter = ItalicFormatter()
28+
lazy var linkFormatter = LinkFormatter()
29+
lazy var orderedListFormatter = TextListFormatter(style: .ordered, increaseDepth: true)
30+
lazy var paragraphFormatter = HTMLParagraphFormatter()
31+
lazy var preFormatter = PreFormatter()
32+
lazy var strikethroughFormatter = StrikethroughFormatter()
33+
lazy var underlineFormatter = UnderlineFormatter()
34+
lazy var unorderedListFormatter = TextListFormatter(style: .unordered, increaseDepth: true)
35+
lazy var codeFormatter = CodeFormatter()
36+
37+
public lazy var elementFormattersMap: [Element: AttributeFormatter] = {
38+
return [
39+
.blockquote: self.blockquoteFormatter,
40+
.div: self.divFormatter,
41+
.ol: self.orderedListFormatter,
42+
.ul: self.unorderedListFormatter,
43+
.strong: self.boldFormatter,
44+
.em: self.italicFormatter,
45+
.u: self.underlineFormatter,
46+
.del: self.strikethroughFormatter,
47+
.a: self.linkFormatter,
48+
.h1: self.h1Formatter,
49+
.h2: self.h2Formatter,
50+
.h3: self.h3Formatter,
51+
.h4: self.h4Formatter,
52+
.h5: self.h5Formatter,
53+
.h6: self.h6Formatter,
54+
.p: self.paragraphFormatter,
55+
.pre: self.preFormatter,
56+
.code: self.codeFormatter
57+
]
58+
}()
59+
1460
// MARK: - ElementConverter
1561

1662
func convert(
@@ -22,15 +68,16 @@ class GenericElementConverter: ElementConverter {
2268
return convert(unsupported: element, inheriting: attributes)
2369
}
2470

25-
return serializeChildren(element.children, attributes)
71+
return convert(supported: element, inheriting: attributes, childrenSerializer: serializeChildren)
2672
}
2773

2874
private func isSupportedByEditor(_ element: ElementNode) -> Bool {
2975
return GenericElementConverter.supportedElements.contains(element.type)
3076
}
3177

3278
/// Converts an unsupported `ElementNode` into it's `NSAttributedString` representation.
33-
/// This method basically packs the `ElementNode` into an attachment.
79+
/// This method basically packs the `ElementNode` into an attachment, making it completely
80+
/// safe against data loss. All attributes and children will be perfectly stored.
3481
///
3582
/// - Parameters:
3683
/// - element: the node to convert to `NSAttributedString`.
@@ -47,5 +94,143 @@ class GenericElementConverter: ElementConverter {
4794

4895
return NSAttributedString(attachment: attachment, attributes: attributes)
4996
}
97+
98+
private func convert(
99+
supported element: ElementNode,
100+
inheriting inheritedAttributes: [NSAttributedStringKey: Any],
101+
childrenSerializer serializeChildren: ChildrenSerializer) -> NSAttributedString {
102+
103+
let childrenAttributes = attributes(for: element, inheriting: inheritedAttributes)
104+
105+
return serializeChildren(element.children, childrenAttributes)
106+
}
107+
}
108+
109+
private extension GenericElementConverter {
110+
111+
// MARK: - NSAttributedString attribute generation
112+
113+
/// Calculates the attributes for the specified node. Returns a dictionary including inherited
114+
/// attributes.
115+
///
116+
/// - Parameters:
117+
/// - element: the node to get the information from.
118+
/// - inheritedAttributes: the inherited attributes from parent nodes.
119+
///
120+
/// - Returns: an attributes dictionary, for use in an NSAttributedString.
121+
///
122+
func attributes(for element: ElementNode, inheriting inheritedAttributes: [NSAttributedStringKey: Any]) -> [NSAttributedStringKey: Any] {
123+
124+
guard !(element is RootNode) else {
125+
return inheritedAttributes
126+
}
127+
128+
let elementRepresentation = HTMLElementRepresentation(element)
129+
let representation = HTMLRepresentation(for: .element(elementRepresentation))
130+
var finalAttributes: [NSAttributedStringKey: Any]
131+
132+
if let elementFormatter = formatter(for: element) {
133+
finalAttributes = elementFormatter.apply(to: inheritedAttributes, andStore: representation)
134+
} else if element.type == .li {
135+
finalAttributes = inheritedAttributes
136+
} else {
137+
finalAttributes = attributes(storing: elementRepresentation, in: inheritedAttributes)
138+
}
139+
140+
return finalAttributes
141+
}
142+
143+
/// Calculates the attributes for the specified HTML attributes. Returns a dictionary
144+
/// including the inherited attributes.
145+
///
146+
/// - Parameters:
147+
/// - htmlAttributes: the HTML attributes to calculate the string attributes from.
148+
/// - inheritedAttributes: the attributes that will be inherited.
149+
///
150+
/// - Returns: an attributes dictionary, for use in an NSAttributedString.
151+
///
152+
private func attributes(for htmlAttributes: [Attribute], inheriting inheritedAttributes: [NSAttributedStringKey: Any]) -> [NSAttributedStringKey: Any] {
153+
154+
let finalAttributes = htmlAttributes.reduce(inheritedAttributes) { (previousAttributes, htmlAttribute) -> [NSAttributedStringKey: Any] in
155+
return attributes(for: htmlAttribute, inheriting: previousAttributes)
156+
}
157+
158+
return finalAttributes
159+
}
160+
161+
162+
/// Calculates the attributes for the specified HTML attribute. Returns a dictionary
163+
/// including inherited attributes.
164+
///
165+
/// - Parameters:
166+
/// - attribute: the attribute to calculate the string attributes from.
167+
/// - inheritedAttributes: the attributes that will be inherited.
168+
///
169+
/// - Returns: an attributes dictionary, for use in an NSAttributedString.
170+
///
171+
private func attributes(for attribute: Attribute, inheriting inheritedAttributes: [NSAttributedStringKey: Any]) -> [NSAttributedStringKey: Any] {
172+
173+
let attributes: [NSAttributedStringKey: Any]
174+
175+
if let attributeFormatter = formatter(for: attribute) {
176+
let attributeHTMLRepresentation = HTMLRepresentation(for: .attribute(attribute))
177+
178+
attributes = attributeFormatter.apply(to: inheritedAttributes, andStore: attributeHTMLRepresentation)
179+
} else {
180+
attributes = inheritedAttributes
181+
}
182+
183+
return attributes
184+
}
185+
186+
187+
/// Stores the specified HTMLElementRepresentation in a collection of NSAttributedString Attributes.
188+
///
189+
/// - Parameters:
190+
/// - representation: Instance of HTMLElementRepresentation to be stored.
191+
/// - attributes: Attributes where we should store the HTML Representation.
192+
///
193+
/// - Returns: A collection of NSAttributedString Attributes, including the specified HTMLElementRepresentation.
194+
///
195+
private func attributes(storing representation: HTMLElementRepresentation, in attributes: [NSAttributedStringKey: Any]) -> [NSAttributedStringKey: Any] {
196+
let unsupportedHTML = attributes[.unsupportedHtml] as? UnsupportedHTML
197+
var representations = unsupportedHTML?.representations ?? []
198+
representations.append(representation)
199+
200+
// Note:
201+
// We'll *ALWAYS* store a copy of the UnsupportedHTML instance. Reason is: reusing the old instance
202+
// would mean affecting a range that may fall beyond what we expected!
203+
//
204+
var updated = attributes
205+
updated[.unsupportedHtml] = UnsupportedHTML(representations: representations)
206+
207+
return updated
208+
}
209+
}
210+
211+
// MARK: - Attribute formatters
212+
213+
private extension GenericElementConverter {
214+
215+
// MARK: - Formatters
216+
217+
func formatter(for attribute: Attribute) -> AttributeFormatter? {
218+
// TODO: implement attribute representation formatters
219+
//
220+
return nil
221+
}
222+
223+
func formatter(for element: ElementNode) -> AttributeFormatter? {
224+
let equivalentNames = element.type.equivalentNames
225+
226+
for (key, formatter) in elementFormattersMap {
227+
if equivalentNames.contains(key.rawValue) {
228+
return formatter
229+
}
230+
}
231+
232+
return nil
233+
}
234+
50235
}
51236

0 commit comments

Comments
 (0)