Skip to content

Commit 355cd41

Browse files
committed
#77 Add XMLDynamicAttributesResolver to use custom xml attributes to alter the string during parsing
1 parent b7c89ef commit 355cd41

File tree

7 files changed

+223
-24
lines changed

7 files changed

+223
-24
lines changed

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ExampleiOS/ViewController.swift

+82-15
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@
88

99
import UIKit
1010

11+
extension UIFont {
12+
13+
/// Return the same font with given weight.
14+
///
15+
/// - Parameter weight: weight you want to get
16+
public func withWeight(_ weight: UIFont.Weight) -> UIFont {
17+
let descriptor = fontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.traits: [UIFontDescriptor.TraitKey.weight: weight]])
18+
return UIFont(descriptor: descriptor, size: 0) // size 0 means keep the size as it is
19+
}
20+
21+
}
22+
23+
1124
class ViewController: UIViewController {
1225

1326
@IBOutlet public var textView: UITextView?
@@ -16,24 +29,75 @@ class ViewController: UIViewController {
1629

1730
override func viewDidLoad() {
1831
super.viewDidLoad()
32+
33+
/* let baseStyle = Style {
34+
$0.font = SystemFonts.AmericanTypewriter.font(size: 17)//UIFont.systemFont(ofSize: 17)
35+
$0.color = UIColor.darkGray
36+
}
37+
38+
let redStyle = Style {
39+
$0.font = SystemFonts.Futura_Bold.font(size: 32)//UIFont.systemFont(ofSize: 17)
40+
$0.color = UIColor.red
41+
}
42+
43+
let strikeStyle = baseStyle.byAdding {
44+
$0.strikethrough = (style: .single, color: UIColor.blue)
45+
$0.textTransforms = [.custom({
46+
return "**\($0)**"
47+
})]
48+
}
49+
50+
let source = "Hello <b>wor<r>ld</r></b> my name"//is <i type='1 px'>Daniele</i>"
51+
let x = XMLStringBuilder(string: source, options: [], baseStyle: baseStyle, styles: ["b": strikeStyle, "r": redStyle])
52+
let att = try! x.parse()
53+
debugPrint("ciao")
54+
*/
55+
/*
56+
57+
let strikeStyle = baseStyle.byAdding {
58+
$0.strikethrough = (style: .single, color: UIColor.blue)
59+
$0.textTransform = .custom({
60+
return "**\($0)**"
61+
})
62+
}
63+
64+
let redBodyStyle = baseStyle.byAdding {
65+
$0.color = UIColor.red
66+
$0.font = SystemFonts.Zapfino.font(size: 32)//UIFont.systemFont(ofSize: 32).withWeight(.medium)
67+
}
68+
69+
let g = StyleGroup(base: baseStyle, [("s", strikeStyle), ("r", redBodyStyle)])*/
70+
71+
/*
72+
73+
let boldStyle = Style {
74+
// $0.font = UIFont.boldSystemFont(ofSize: self.baseFontSize)
75+
$0.font = SystemFonts.AmericanTypewriter.font(size: self.baseFontSize)
76+
$0.color = UIColor.blue
77+
/*if #available(iOS 11.0, *) {
78+
$0.dynamicText = DynamicText {
79+
$0.style = .body
80+
$0.maximumSize = 35.0
81+
$0.traitCollection = UITraitCollection(userInterfaceIdiom: .phone)
82+
}
83+
}*/
84+
}
85+
86+
let headerStyle = Style {
87+
$0.font = SystemFonts.ArialHebrew.font(size: 22)
88+
$0.color = UIColor.red
89+
}
90+
91+
let g = StyleGroup(base: headerStyle, ["c" : boldStyle])
92+
*/
93+
//self.textView?.attributedText = "ciao ciao <s>altro</s> merda <r>test</r>".set(style: g)
94+
// self.textView?.attributedText = "ciao ciao <s>altro</s> merda <r>test</r>".set(style: strikeStyle)
1995

20-
/*let baseStyle = Style {
21-
$0.font = UIFont.systemFont(ofSize: self.baseFontSize)
22-
$0.lineSpacing = 2
23-
}
24-
25-
let linkStyle = Style {
26-
$0.font = UIFont.boldSystemFont(ofSize: self.baseFontSize)
27-
$0.linkURL = URLRepresentable.tagAttribute("href")
28-
}
29-
30-
let text = "Stile <a href=\"www.facebook.com/agencearcantide/photos/a.346501409312886/346503839312643/?type=3&theater\">link</a>"
31-
let s = StyleGroup(base: baseStyle, ["a" : linkStyle])
32-
self.textView?.attributedText = text.set(style: s)
33-
*/
3496

97+
3598
let bodyHTML = try! String(contentsOfFile: Bundle.main.path(forResource: "file", ofType: "txt")!)
3699

100+
37101
let headerStyle = Style {
38102
$0.font = UIFont.boldSystemFont(ofSize: self.baseFontSize * 1.15)
39103
$0.lineSpacing = 1
@@ -53,7 +117,7 @@ class ViewController: UIViewController {
53117
$0.font = UIFont.italicSystemFont(ofSize: self.baseFontSize)
54118
}
55119

56-
let style = StyleGroup(base: Style {
120+
let style = StyleGroup2(base: Style {
57121
$0.font = UIFont.systemFont(ofSize: self.baseFontSize)
58122
$0.lineSpacing = 2
59123
$0.kerning = Kerning.adobe(-15)
@@ -79,5 +143,8 @@ class ViewController: UIViewController {
79143
if #available(iOS 10.0, *) {
80144
self.textView?.adjustsFontForContentSizeCategory = true
81145
}
146+
147+
148+
82149
}
83150
}

ExampleiOS/file.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<strong>Parler du don d'organe n'est plus tabou. Je me renseigne, j'en discute avec mes proches,... et je décide!</strong>
22

3-
En Belgique, au début des années 2000, le nombre de donneurs d’organes avait tendance à diminuer et les listes d’attente à augmenter considérablement ayant comme corollaire une augmentation de la mortalité des patients inscrits sur les listes d’attente.
3+
<strong color="red">En Belgique</strong>, au début des années 2000, le nombre de donneurs d’organes avait tendance à diminuer et les listes d’attente à augmenter considérablement ayant comme corollaire une augmentation de la mortalité des patients inscrits sur les listes d’attente.
44

55
Soucieux de cette situation, en juin 2005, le Ministre en charge de la Santé publique a souhaité mettre sur pied une vaste campagne de sensibilisation entièrement dédiée au don d’organes.
66

Sources/SwiftRichString/Style/StyleGroup2.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public class StyleGroup2: StyleProtocol {
5252
/// Parsing options.
5353
public var xmlParsingOptions: XMLParsingOptions = []
5454

55+
/// Dynamic attributes resolver.
56+
public var xmlAttributesResolver: XMLDynamicAttributesResolver = StandardXMLAttributesResolver()
57+
5558
// MARK: - Initialization
5659

5760
/// Initialize a new `StyleGroup` with a dictionary of style and names.
@@ -129,8 +132,10 @@ public class StyleGroup2: StyleProtocol {
129132
/// - Returns: modified attributed string, same instance of the `source`.
130133
public func apply(to attrStr: AttributedString, adding: Bool, range: NSRange?) -> AttributedString {
131134
do {
132-
let xml = XMLStringBuilder(string: attrStr.string, options: xmlParsingOptions, baseStyle: baseStyle, styles: styles)
133-
return try xml.parse()
135+
let xmlParser = XMLStringBuilder(string: attrStr.string, options: xmlParsingOptions,
136+
baseStyle: baseStyle, styles: styles,
137+
xmlAttributesResolver: xmlAttributesResolver)
138+
return try xmlParser.parse()
134139
} catch {
135140
debugPrint("Failed to generate attributed string from xml: \(error)")
136141
return attrStr
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// SwiftRichString
3+
// Elegant Strings & Attributed Strings Toolkit for Swift
4+
//
5+
// Created by Daniele Margutti.
6+
// Copyright © 2018 Daniele Margutti. All rights reserved.
7+
//
8+
// Web: http://www.danielemargutti.com
9+
10+
// Twitter: @danielemargutti
11+
//
12+
//
13+
// Permission is hereby granted, free of charge, to any person obtaining a copy
14+
// of this software and associated documentation files (the "Software"), to deal
15+
// in the Software without restriction, including without limitation the rights
16+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17+
// copies of the Software, and to permit persons to whom the Software is
18+
// furnished to do so, subject to the following conditions:
19+
//
20+
// The above copyright notice and this permission notice shall be included in
21+
// all copies or substantial portions of the Software.
22+
//
23+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29+
// THE SOFTWARE.
30+
31+
import Foundation
32+
#if os(OSX)
33+
import AppKit
34+
#else
35+
import UIKit
36+
#endif
37+
38+
// MARK: - XMLDynamicAttributesResolver
39+
40+
public protocol XMLDynamicAttributesResolver {
41+
42+
func applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle)
43+
44+
}
45+
46+
// MARK: - StandardXMLAttributesResolver
47+
48+
open class StandardXMLAttributesResolver: XMLDynamicAttributesResolver {
49+
50+
public func applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle) {
51+
#if os(iOS)
52+
xmlStyle.enumerateAttributes { key, value in
53+
switch key {
54+
case "color":
55+
attributedString.set(style: Style({
56+
$0.color = UIColor.yellow
57+
}))
58+
59+
default:
60+
break
61+
}
62+
}
63+
#endif
64+
}
65+
66+
}

Sources/SwiftRichString/Support/XMLStringBuilder.swift

+42-6
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public struct XMLParsingOptions: OptionSet {
5757
// MARK: - XMLStringBuilder
5858

5959
public class XMLStringBuilder: NSObject, XMLParserDelegate {
60-
60+
6161
// MARK: Private Properties
6262
private static let topTag = "source"
6363

@@ -77,7 +77,10 @@ public class XMLStringBuilder: NSObject, XMLParserDelegate {
7777
private var styles: [String: StyleProtocol]
7878

7979
/// Styles applied at each fragment.
80-
private var xmlStylers = [StyleProtocol]()
80+
private var xmlStylers = [XMLDynamicStyle]()
81+
82+
/// XML Attributes resolver
83+
public var xmlAttributesResolver: XMLDynamicAttributesResolver
8184

8285
// The XML parser sometimes splits strings, which can break localization-sensitive
8386
// string transforms. Work around this by using the currentString variable to
@@ -87,20 +90,23 @@ public class XMLStringBuilder: NSObject, XMLParserDelegate {
8790

8891
// MARK: - Initialization
8992

90-
public init(string: String, options: XMLParsingOptions, baseStyle: StyleProtocol?, styles: [String: StyleProtocol]) {
93+
public init(string: String, options: XMLParsingOptions,
94+
baseStyle: StyleProtocol?, styles: [String: StyleProtocol],
95+
xmlAttributesResolver: XMLDynamicAttributesResolver) {
9196
let xml = (options.contains(.doNotWrapXML) ? string : "<\(XMLStringBuilder.topTag)>\(string)</\(XMLStringBuilder.topTag)>")
9297
guard let data = xml.data(using: String.Encoding.utf8) else {
9398
fatalError("Unable to convert to UTF8")
9499
}
95100

96101
self.options = options
97102
self.attributedString = NSMutableAttributedString()
103+
self.xmlAttributesResolver = xmlAttributesResolver
98104
self.xmlParser = XMLParser(data: data)
99105
self.baseStyle = baseStyle
100106
self.styles = styles
101107

102108
if let baseStyle = baseStyle {
103-
self.xmlStylers.append(baseStyle)
109+
self.xmlStylers.append( XMLDynamicStyle(style: baseStyle) )
104110
}
105111

106112
super.init()
@@ -153,7 +159,7 @@ public class XMLStringBuilder: NSObject, XMLParserDelegate {
153159
}
154160

155161
if elementName != XMLStringBuilder.topTag, let namedStyle = styles[elementName] {
156-
xmlStylers.append(namedStyle)
162+
xmlStylers.append( XMLDynamicStyle(style: namedStyle, xmlAttributes: attributes) )
157163
}
158164
}
159165

@@ -166,9 +172,39 @@ public class XMLStringBuilder: NSObject, XMLParserDelegate {
166172
return
167173
}
168174

169-
let newAttributedString = newString.set(styles: xmlStylers)
175+
//let resolvedStyles = xmlStylers.map { $0.composedStyle }
176+
177+
var newAttributedString = AttributedString(string: newString)
178+
for xmlStyle in xmlStylers {
179+
newAttributedString.add(style: xmlStyle.style)
180+
181+
if xmlStyle.xmlAttributes != nil {
182+
xmlAttributesResolver.applyDynamicAttributes(to: &newAttributedString, xmlStyle: xmlStyle)
183+
}
184+
}
170185
attributedString.append(newAttributedString)
171186
currentString = nil
172187
}
173188

174189
}
190+
191+
public class XMLDynamicStyle {
192+
public let style: StyleProtocol
193+
public let xmlAttributes: [String: String]?
194+
195+
public init(style: StyleProtocol, xmlAttributes: [String: String]? = nil) {
196+
self.style = style
197+
self.xmlAttributes = ((xmlAttributes?.keys.isEmpty ?? true) == false ? xmlAttributes : nil)
198+
}
199+
200+
public func enumerateAttributes(_ handler: ((_ key: String, _ value: String) -> Void)) {
201+
guard let xmlAttributes = xmlAttributes else {
202+
return
203+
}
204+
205+
xmlAttributes.keys.forEach {
206+
handler($0, xmlAttributes[$0]!)
207+
}
208+
}
209+
210+
}

0 commit comments

Comments
 (0)