From 494cd1338a71fa9f7fa00a9aa010e25f95b7daeb Mon Sep 17 00:00:00 2001 From: Stefano Mondino Date: Sun, 26 Jan 2020 19:35:15 +0100 Subject: [PATCH 1/4] fixes 102 --- ExampleiOS/ViewController.swift | 5 +++-- .../Support/XMLDynamicAttributesResolver.swift | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ExampleiOS/ViewController.swift b/ExampleiOS/ViewController.swift index 3e9238f..b35e35b 100644 --- a/ExampleiOS/ViewController.swift +++ b/ExampleiOS/ViewController.swift @@ -30,9 +30,9 @@ class ViewController: UIViewController { fatalError() } - self.textView?.attributedText = text.set(style: xmlStyle) +// self.textView?.attributedText = text.set(style: xmlStyle) - return +// return // self.textView?.attributedText = "ciao ciao " + AttributedString(image: UIImage(named: "rocket")!, // bounds: CGRect(x: 0, y: -20, width: 25, height: 25)) + "ciao ciao" @@ -85,6 +85,7 @@ class ViewController: UIViewController { "b": boldStyle, "em": italicStyle, "i": italicStyle, + "a": uppercasedRed, "li": Style { $0.paragraphSpacingBefore = self.baseFontSize / 2 $0.firstLineHeadIndent = self.baseFontSize diff --git a/Sources/SwiftRichString/Support/XMLDynamicAttributesResolver.swift b/Sources/SwiftRichString/Support/XMLDynamicAttributesResolver.swift index e9421da..065f775 100644 --- a/Sources/SwiftRichString/Support/XMLDynamicAttributesResolver.swift +++ b/Sources/SwiftRichString/Support/XMLDynamicAttributesResolver.swift @@ -90,22 +90,21 @@ extension XMLDynamicAttributesResolver { open class StandardXMLAttributesResolver: XMLDynamicAttributesResolver { - public func applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle, fromStyle: StyleXML) { + open func applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle, fromStyle: StyleXML) { let finalStyleToApply = Style() xmlStyle.enumerateAttributes { key, value in switch key { case "color": // color support finalStyleToApply.color = Color(hexString: value) - default: - break + default: break } } - + self.styleForUnknownXMLTag(xmlStyle.tag, to: &attributedString, attributes: xmlStyle.xmlAttributes, fromStyle: fromStyle) attributedString.add(style: finalStyleToApply) } - public func styleForUnknownXMLTag(_ tag: String, to attributedString: inout AttributedString, attributes: [String: String]?, fromStyle: StyleXML) { + open func styleForUnknownXMLTag(_ tag: String, to attributedString: inout AttributedString, attributes: [String: String]?, fromStyle: StyleXML) { let finalStyleToApply = Style() switch tag { case "a": // href support From 392095335f06072b9bf0870a0ac1506df2ec77e6 Mon Sep 17 00:00:00 2001 From: Stefano Mondino Date: Sun, 26 Jan 2020 19:39:25 +0100 Subject: [PATCH 2/4] public init for StandardXMLAttributesResolver --- .../SwiftRichString/Support/XMLDynamicAttributesResolver.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/SwiftRichString/Support/XMLDynamicAttributesResolver.swift b/Sources/SwiftRichString/Support/XMLDynamicAttributesResolver.swift index 065f775..9a8a7a2 100644 --- a/Sources/SwiftRichString/Support/XMLDynamicAttributesResolver.swift +++ b/Sources/SwiftRichString/Support/XMLDynamicAttributesResolver.swift @@ -90,6 +90,8 @@ extension XMLDynamicAttributesResolver { open class StandardXMLAttributesResolver: XMLDynamicAttributesResolver { + public init() {} + open func applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle, fromStyle: StyleXML) { let finalStyleToApply = Style() xmlStyle.enumerateAttributes { key, value in From 2a355eea9487c7dfcd09cffd757facbd8d0a5138 Mon Sep 17 00:00:00 2001 From: daniele margutti Date: Sun, 26 Jan 2020 19:57:27 +0100 Subject: [PATCH 3/4] #104 Added escapeString options in XMLParsingOptions of StyleXML. Applied by default. --- .../SwiftRichString/Style/StyleGroup.swift | 3 +- .../SwiftRichString/Support/Extensions.swift | 48 +++++++++++++++++++ .../Support/XMLStringBuilder.swift | 9 +++- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftRichString/Style/StyleGroup.swift b/Sources/SwiftRichString/Style/StyleGroup.swift index 326527a..1547a2f 100644 --- a/Sources/SwiftRichString/Style/StyleGroup.swift +++ b/Sources/SwiftRichString/Style/StyleGroup.swift @@ -52,7 +52,8 @@ public class StyleXML: StyleProtocol { public var baseStyle: StyleProtocol? /// XML Parsing options. - public var xmlParsingOptions: XMLParsingOptions = [] + /// By default `escapeString` is applied. + public var xmlParsingOptions: XMLParsingOptions = [.escapeString] /// Image provider is called to provide custom image when `StyleXML` encounter a `img` tag image. /// If not implemented the image is automatically searched inside any bundled `xcassets`. diff --git a/Sources/SwiftRichString/Support/Extensions.swift b/Sources/SwiftRichString/Support/Extensions.swift index 4f8fdb5..8a9be4b 100644 --- a/Sources/SwiftRichString/Support/Extensions.swift +++ b/Sources/SwiftRichString/Support/Extensions.swift @@ -35,6 +35,54 @@ import AppKit import UIKit #endif +extension String { + + // Current implementation by @alexaubry in + // https://github.com/alexaubry/HTMLString + public func escapeWithUnicodeEntities() -> String { + var copy = self + copy.addUnicodeEntities() + return copy + } + + internal mutating func addUnicodeEntities() { + var position: String.Index? = startIndex + let requiredEscapes: Set = ["!", "\"", "$", "%", "&", "'", "+", ",", "<", "=", ">", "@", "[", "]", "`", "{", "}"] + + while let cursorPosition = position { + guard cursorPosition != endIndex else { break } + let character = self[cursorPosition] + + if requiredEscapes.contains(character) { + // One of the required escapes for security reasons + let escape = "&#\(character.asciiValue!);" // required escapes can can only be ASCII + position = positionAfterReplacingCharacter(at: cursorPosition, with: escape) + } else { + // Not a required escape, no need to replace the character + position = index(cursorPosition, offsetBy: 1, limitedBy: endIndex) + } + } + } + + /// Replaces the character at the given position with the escape and returns the new position. + fileprivate mutating func positionAfterReplacingCharacter(at position: String.Index, with escape: String) -> String.Index? { + let nextIndex = index(position, offsetBy: 1) + + if let fittingPosition = index(position, offsetBy: escape.count, limitedBy: endIndex) { + // Check if we can fit the whole escape in the receiver + replaceSubrange(position ..< nextIndex, with: escape) + return fittingPosition + } else { + // If we can't, remove the character and insert the escape to make it fit. + remove(at: position) + insert(contentsOf: escape, at: position) + return index(position, offsetBy: escape.count, limitedBy: endIndex) + } + } + + +} + extension NSNumber { internal static func from(float: Float?) -> NSNumber? { diff --git a/Sources/SwiftRichString/Support/XMLStringBuilder.swift b/Sources/SwiftRichString/Support/XMLStringBuilder.swift index 786b119..4ace995 100644 --- a/Sources/SwiftRichString/Support/XMLStringBuilder.swift +++ b/Sources/SwiftRichString/Support/XMLStringBuilder.swift @@ -52,6 +52,12 @@ public struct XMLParsingOptions: OptionSet { /// recommended that you include a root node yourself and pass this option. public static let doNotWrapXML = XMLParsingOptions(rawValue: 1) + /// Perform string escaping to replace all characters which is not supported by NSXMLParser + /// into the specified encoding with decimal entity. + /// For example if your string contains '&' character parser will break the style. + /// This option is active by default. + public static let escapeString = XMLParsingOptions(rawValue: 2) + } // MARK: - XMLStringBuilder @@ -104,7 +110,8 @@ public class XMLStringBuilder: NSObject, XMLParserDelegate { public init(styleXML: StyleXML, string: String) { self.styleXML = styleXML - let xml = (styleXML.xmlParsingOptions.contains(.doNotWrapXML) ? string : "<\(XMLStringBuilder.topTag)>\(string)") + let xmlString = (styleXML.xmlParsingOptions.contains(.escapeString) ? string.escapeWithUnicodeEntities() : string) + let xml = (styleXML.xmlParsingOptions.contains(.doNotWrapXML) ? xmlString : "<\(XMLStringBuilder.topTag)>\(xmlString)") guard let data = xml.data(using: String.Encoding.utf8) else { fatalError("Unable to convert to UTF8") } From c480b711d7bc630aa12d04b910955a9bcc8111d6 Mon Sep 17 00:00:00 2001 From: daniele margutti Date: Sun, 26 Jan 2020 20:06:18 +0100 Subject: [PATCH 4/4] Bump to 3.7.0 --- SwiftRichString.podspec | 2 +- SwiftRichString.xcodeproj/project.pbxproj | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SwiftRichString.podspec b/SwiftRichString.podspec index 4e13073..aa43763 100644 --- a/SwiftRichString.podspec +++ b/SwiftRichString.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SwiftRichString" - s.version = "3.6.1" + s.version = "3.7.0" s.summary = "Elegant Strings & Attributed Strings Toolkit for Swift" s.description = <<-DESC SwiftRichString is the best toolkit to work easily with Strings and Attributed Strings. diff --git a/SwiftRichString.xcodeproj/project.pbxproj b/SwiftRichString.xcodeproj/project.pbxproj index 96e3ded..9065141 100644 --- a/SwiftRichString.xcodeproj/project.pbxproj +++ b/SwiftRichString.xcodeproj/project.pbxproj @@ -1640,7 +1640,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - MARKETING_VERSION = 3.6.1; + MARKETING_VERSION = 3.7.0; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-iOS"; PRODUCT_NAME = SwiftRichString; @@ -1666,7 +1666,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - MARKETING_VERSION = 3.6.1; + MARKETING_VERSION = 3.7.0; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-iOS"; PRODUCT_NAME = SwiftRichString; SKIP_INSTALL = YES; @@ -1718,7 +1718,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - MARKETING_VERSION = 3.6.1; + MARKETING_VERSION = 3.7.0; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-watchOS"; PRODUCT_NAME = SwiftRichString; SDKROOT = watchos; @@ -1745,7 +1745,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - MARKETING_VERSION = 3.6.1; + MARKETING_VERSION = 3.7.0; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-watchOS"; PRODUCT_NAME = SwiftRichString; SDKROOT = watchos; @@ -1772,7 +1772,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - MARKETING_VERSION = 3.6.1; + MARKETING_VERSION = 3.7.0; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-tvOS"; PRODUCT_NAME = SwiftRichString; SDKROOT = appletvos; @@ -1799,7 +1799,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - MARKETING_VERSION = 3.6.1; + MARKETING_VERSION = 3.7.0; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-tvOS"; PRODUCT_NAME = SwiftRichString; SDKROOT = appletvos; @@ -1828,7 +1828,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - MARKETING_VERSION = 3.6.1; + MARKETING_VERSION = 3.7.0; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-macOS"; PRODUCT_NAME = SwiftRichString; SDKROOT = macosx; @@ -1855,7 +1855,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - MARKETING_VERSION = 3.6.1; + MARKETING_VERSION = 3.7.0; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftRichString.SwiftRichString-macOS"; PRODUCT_NAME = SwiftRichString; SDKROOT = macosx;