@@ -5,12 +5,58 @@ import UIKit
5
5
///
6
6
class GenericElementConverter : ElementConverter {
7
7
8
+ // MARK: - Element Support
9
+
8
10
/// This is a list of elements that don't produce an HTML attachment when they go through this converter
9
11
/// At some point we should modify how the conversion works, so that any supported element never goes through this
10
12
/// converter at all, and this converter is turned into an `UnsupportedElementConverter()` exclusively.
11
13
///
12
14
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]
13
15
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
+
14
60
// MARK: - ElementConverter
15
61
16
62
func convert(
@@ -22,15 +68,16 @@ class GenericElementConverter: ElementConverter {
22
68
return convert ( unsupported: element, inheriting: attributes)
23
69
}
24
70
25
- return serializeChildren ( element. children , attributes)
71
+ return convert ( supported : element, inheriting : attributes, childrenSerializer : serializeChildren )
26
72
}
27
73
28
74
private func isSupportedByEditor( _ element: ElementNode ) -> Bool {
29
75
return GenericElementConverter . supportedElements. contains ( element. type)
30
76
}
31
77
32
78
/// 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.
34
81
///
35
82
/// - Parameters:
36
83
/// - element: the node to convert to `NSAttributedString`.
@@ -47,5 +94,143 @@ class GenericElementConverter: ElementConverter {
47
94
48
95
return NSAttributedString ( attachment: attachment, attributes: attributes)
49
96
}
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
+
50
235
}
51
236
0 commit comments