Skip to content

Commit

Permalink
#104 Added escapeString options in XMLParsingOptions of StyleXML. App…
Browse files Browse the repository at this point in the history
…lied by default.
  • Loading branch information
malcommac committed Jan 26, 2020
1 parent b432258 commit 2a355ee
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 2 deletions.
3 changes: 2 additions & 1 deletion Sources/SwiftRichString/Style/StyleGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
48 changes: 48 additions & 0 deletions Sources/SwiftRichString/Support/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Character> = ["!", "\"", "$", "%", "&", "'", "+", ",", "<", "=", ">", "@", "[", "]", "`", "{", "}"]

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? {
Expand Down
9 changes: 8 additions & 1 deletion Sources/SwiftRichString/Support/XMLStringBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)</\(XMLStringBuilder.topTag)>")
let xmlString = (styleXML.xmlParsingOptions.contains(.escapeString) ? string.escapeWithUnicodeEntities() : string)
let xml = (styleXML.xmlParsingOptions.contains(.doNotWrapXML) ? xmlString : "<\(XMLStringBuilder.topTag)>\(xmlString)</\(XMLStringBuilder.topTag)>")
guard let data = xml.data(using: String.Encoding.utf8) else {
fatalError("Unable to convert to UTF8")
}
Expand Down

0 comments on commit 2a355ee

Please sign in to comment.