Skip to content

Commit 4c9122f

Browse files
committed
Merge branch 'release/3.7.0'
2 parents 7e7a70e + c480b71 commit 4c9122f

File tree

7 files changed

+76
-18
lines changed

7 files changed

+76
-18
lines changed

ExampleiOS/ViewController.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ class ViewController: UIViewController {
3030
fatalError()
3131
}
3232

33-
self.textView?.attributedText = text.set(style: xmlStyle)
33+
// self.textView?.attributedText = text.set(style: xmlStyle)
3434

35-
return
35+
// return
3636

3737
// self.textView?.attributedText = "ciao ciao " + AttributedString(image: UIImage(named: "rocket")!,
3838
// bounds: CGRect(x: 0, y: -20, width: 25, height: 25)) + "ciao ciao"
@@ -85,6 +85,7 @@ class ViewController: UIViewController {
8585
"b": boldStyle,
8686
"em": italicStyle,
8787
"i": italicStyle,
88+
"a": uppercasedRed,
8889
"li": Style {
8990
$0.paragraphSpacingBefore = self.baseFontSize / 2
9091
$0.firstLineHeadIndent = self.baseFontSize

Sources/SwiftRichString/Style/StyleGroup.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ public class StyleXML: StyleProtocol {
5252
public var baseStyle: StyleProtocol?
5353

5454
/// XML Parsing options.
55-
public var xmlParsingOptions: XMLParsingOptions = []
55+
/// By default `escapeString` is applied.
56+
public var xmlParsingOptions: XMLParsingOptions = [.escapeString]
5657

5758
/// Image provider is called to provide custom image when `StyleXML` encounter a `img` tag image.
5859
/// If not implemented the image is automatically searched inside any bundled `xcassets`.

Sources/SwiftRichString/Support/Extensions.swift

+48
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,54 @@ import AppKit
3535
import UIKit
3636
#endif
3737

38+
extension String {
39+
40+
// Current implementation by @alexaubry in
41+
// https://github.com/alexaubry/HTMLString
42+
public func escapeWithUnicodeEntities() -> String {
43+
var copy = self
44+
copy.addUnicodeEntities()
45+
return copy
46+
}
47+
48+
internal mutating func addUnicodeEntities() {
49+
var position: String.Index? = startIndex
50+
let requiredEscapes: Set<Character> = ["!", "\"", "$", "%", "&", "'", "+", ",", "<", "=", ">", "@", "[", "]", "`", "{", "}"]
51+
52+
while let cursorPosition = position {
53+
guard cursorPosition != endIndex else { break }
54+
let character = self[cursorPosition]
55+
56+
if requiredEscapes.contains(character) {
57+
// One of the required escapes for security reasons
58+
let escape = "&#\(character.asciiValue!);" // required escapes can can only be ASCII
59+
position = positionAfterReplacingCharacter(at: cursorPosition, with: escape)
60+
} else {
61+
// Not a required escape, no need to replace the character
62+
position = index(cursorPosition, offsetBy: 1, limitedBy: endIndex)
63+
}
64+
}
65+
}
66+
67+
/// Replaces the character at the given position with the escape and returns the new position.
68+
fileprivate mutating func positionAfterReplacingCharacter(at position: String.Index, with escape: String) -> String.Index? {
69+
let nextIndex = index(position, offsetBy: 1)
70+
71+
if let fittingPosition = index(position, offsetBy: escape.count, limitedBy: endIndex) {
72+
// Check if we can fit the whole escape in the receiver
73+
replaceSubrange(position ..< nextIndex, with: escape)
74+
return fittingPosition
75+
} else {
76+
// If we can't, remove the character and insert the escape to make it fit.
77+
remove(at: position)
78+
insert(contentsOf: escape, at: position)
79+
return index(position, offsetBy: escape.count, limitedBy: endIndex)
80+
}
81+
}
82+
83+
84+
}
85+
3886
extension NSNumber {
3987

4088
internal static func from(float: Float?) -> NSNumber? {

Sources/SwiftRichString/Support/XMLDynamicAttributesResolver.swift

+6-5
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,23 @@ extension XMLDynamicAttributesResolver {
9090

9191
open class StandardXMLAttributesResolver: XMLDynamicAttributesResolver {
9292

93-
public func applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle, fromStyle: StyleXML) {
93+
public init() {}
94+
95+
open func applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle, fromStyle: StyleXML) {
9496
let finalStyleToApply = Style()
9597
xmlStyle.enumerateAttributes { key, value in
9698
switch key {
9799
case "color": // color support
98100
finalStyleToApply.color = Color(hexString: value)
99101

100-
default:
101-
break
102+
default: break
102103
}
103104
}
104-
105+
self.styleForUnknownXMLTag(xmlStyle.tag, to: &attributedString, attributes: xmlStyle.xmlAttributes, fromStyle: fromStyle)
105106
attributedString.add(style: finalStyleToApply)
106107
}
107108

108-
public func styleForUnknownXMLTag(_ tag: String, to attributedString: inout AttributedString, attributes: [String: String]?, fromStyle: StyleXML) {
109+
open func styleForUnknownXMLTag(_ tag: String, to attributedString: inout AttributedString, attributes: [String: String]?, fromStyle: StyleXML) {
109110
let finalStyleToApply = Style()
110111
switch tag {
111112
case "a": // href support

Sources/SwiftRichString/Support/XMLStringBuilder.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ public struct XMLParsingOptions: OptionSet {
5252
/// recommended that you include a root node yourself and pass this option.
5353
public static let doNotWrapXML = XMLParsingOptions(rawValue: 1)
5454

55+
/// Perform string escaping to replace all characters which is not supported by NSXMLParser
56+
/// into the specified encoding with decimal entity.
57+
/// For example if your string contains '&' character parser will break the style.
58+
/// This option is active by default.
59+
public static let escapeString = XMLParsingOptions(rawValue: 2)
60+
5561
}
5662

5763
// MARK: - XMLStringBuilder
@@ -104,7 +110,8 @@ public class XMLStringBuilder: NSObject, XMLParserDelegate {
104110
public init(styleXML: StyleXML, string: String) {
105111
self.styleXML = styleXML
106112

107-
let xml = (styleXML.xmlParsingOptions.contains(.doNotWrapXML) ? string : "<\(XMLStringBuilder.topTag)>\(string)</\(XMLStringBuilder.topTag)>")
113+
let xmlString = (styleXML.xmlParsingOptions.contains(.escapeString) ? string.escapeWithUnicodeEntities() : string)
114+
let xml = (styleXML.xmlParsingOptions.contains(.doNotWrapXML) ? xmlString : "<\(XMLStringBuilder.topTag)>\(xmlString)</\(XMLStringBuilder.topTag)>")
108115
guard let data = xml.data(using: String.Encoding.utf8) else {
109116
fatalError("Unable to convert to UTF8")
110117
}

SwiftRichString.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "SwiftRichString"
3-
s.version = "3.6.1"
3+
s.version = "3.7.0"
44
s.summary = "Elegant Strings & Attributed Strings Toolkit for Swift"
55
s.description = <<-DESC
66
SwiftRichString is the best toolkit to work easily with Strings and Attributed Strings.

SwiftRichString.xcodeproj/project.pbxproj

+8-8
Original file line numberDiff line numberDiff line change
@@ -1640,7 +1640,7 @@
16401640
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
16411641
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
16421642
MACOSX_DEPLOYMENT_TARGET = 10.11;
1643-
MARKETING_VERSION = 3.6.1;
1643+
MARKETING_VERSION = 3.7.0;
16441644
ONLY_ACTIVE_ARCH = NO;
16451645
PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-iOS";
16461646
PRODUCT_NAME = SwiftRichString;
@@ -1666,7 +1666,7 @@
16661666
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
16671667
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
16681668
MACOSX_DEPLOYMENT_TARGET = 10.11;
1669-
MARKETING_VERSION = 3.6.1;
1669+
MARKETING_VERSION = 3.7.0;
16701670
PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-iOS";
16711671
PRODUCT_NAME = SwiftRichString;
16721672
SKIP_INSTALL = YES;
@@ -1718,7 +1718,7 @@
17181718
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
17191719
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
17201720
MACOSX_DEPLOYMENT_TARGET = 10.11;
1721-
MARKETING_VERSION = 3.6.1;
1721+
MARKETING_VERSION = 3.7.0;
17221722
PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-watchOS";
17231723
PRODUCT_NAME = SwiftRichString;
17241724
SDKROOT = watchos;
@@ -1745,7 +1745,7 @@
17451745
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
17461746
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
17471747
MACOSX_DEPLOYMENT_TARGET = 10.11;
1748-
MARKETING_VERSION = 3.6.1;
1748+
MARKETING_VERSION = 3.7.0;
17491749
PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-watchOS";
17501750
PRODUCT_NAME = SwiftRichString;
17511751
SDKROOT = watchos;
@@ -1772,7 +1772,7 @@
17721772
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
17731773
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
17741774
MACOSX_DEPLOYMENT_TARGET = 10.11;
1775-
MARKETING_VERSION = 3.6.1;
1775+
MARKETING_VERSION = 3.7.0;
17761776
PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-tvOS";
17771777
PRODUCT_NAME = SwiftRichString;
17781778
SDKROOT = appletvos;
@@ -1799,7 +1799,7 @@
17991799
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
18001800
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
18011801
MACOSX_DEPLOYMENT_TARGET = 10.11;
1802-
MARKETING_VERSION = 3.6.1;
1802+
MARKETING_VERSION = 3.7.0;
18031803
PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-tvOS";
18041804
PRODUCT_NAME = SwiftRichString;
18051805
SDKROOT = appletvos;
@@ -1828,7 +1828,7 @@
18281828
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
18291829
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
18301830
MACOSX_DEPLOYMENT_TARGET = 10.11;
1831-
MARKETING_VERSION = 3.6.1;
1831+
MARKETING_VERSION = 3.7.0;
18321832
PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-macOS";
18331833
PRODUCT_NAME = SwiftRichString;
18341834
SDKROOT = macosx;
@@ -1855,7 +1855,7 @@
18551855
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
18561856
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
18571857
MACOSX_DEPLOYMENT_TARGET = 10.11;
1858-
MARKETING_VERSION = 3.6.1;
1858+
MARKETING_VERSION = 3.7.0;
18591859
PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-macOS";
18601860
PRODUCT_NAME = SwiftRichString;
18611861
SDKROOT = macosx;

0 commit comments

Comments
 (0)