@@ -5,12 +5,58 @@ import UIKit
55///
66class 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