Last active
December 21, 2018 21:52
-
-
Save avdwerff/6181603c802f47777828ce8ae1799a3c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
//https://en.wikipedia.org/wiki/XML | |
class ValidXMLProperties { | |
static let shared = ValidXMLProperties() | |
static let excludedElementNameCharactersPattern = "!|\"|#|\\$|%|&|\\'|\\(|\\)|\\*|\\+|,|/|;|\\<|=|\\>|\\?|\\@|\\[|\\\\|\\]|\\^|`|\\{|\\||\\}|~" | |
static let excludedElementNameStartCharactersPattern = "-|\\.|[\\d]" | |
lazy var stripExcludedCharactersRE: NSRegularExpression = { | |
do { | |
let re = try NSRegularExpression(pattern: ValidXMLProperties.excludedElementNameCharactersPattern, options: .caseInsensitive) | |
return re | |
} catch let error { | |
fatalError(error.localizedDescription) | |
} | |
}() | |
} | |
func stripElementName(_ name: String) -> String { | |
do { | |
let stripped = ValidXMLProperties.shared.stripExcludedCharactersRE | |
.stringByReplacingMatches(in: name, options: [], range: NSRange(location: 0, length: name.count), withTemplate: "") | |
return | |
stripped | |
.replacingOccurrences( | |
of: ValidXMLProperties.excludedElementNameStartCharactersPattern, with: "_", | |
options: .regularExpression, | |
range: Range(NSRange(location: 0, length: 1), in: name) | |
) | |
} catch let error { | |
fatalError(error.localizedDescription) | |
} | |
} | |
func escapeTextNode(_ text: String) -> String { | |
return text | |
.replacingOccurrences(of: "&", with: "&") | |
.replacingOccurrences(of: "<", with: "<") | |
.replacingOccurrences(of: ">", with: ">") | |
.replacingOccurrences(of: "\"", with: """) | |
.replacingOccurrences(of: "'", with: "'") | |
} | |
enum Node { | |
indirect case declaration([(String, String)], Node) | |
indirect case element(String, [(String, String)], [Node]) | |
case content(String) | |
} | |
extension Node: ExpressibleByStringLiteral { | |
init(stringLiteral value: String) { | |
self = .content(value) | |
} | |
} | |
func render(_ node: Node) -> String { | |
switch node { | |
case let .declaration(attrs, root): | |
let formattedAttrs = attrs.reduce(into: "") { (res, attr) in | |
res.append(" \(attr.0)=\"\(attr.1)\"") | |
} | |
let elements = render(root) | |
return "<?xml\(formattedAttrs)?>\(elements)" | |
case let .element(tag, attrs, children): | |
let formattedAttrs = attrs.reduce(into: "") { (res, attr) in | |
res.append(" \(attr.0)=\"\(attr.1)\"") | |
} | |
let formattedChildren = children.map(render).joined(separator: "") | |
let validName = stripElementName(tag) | |
return "<\(validName)\(formattedAttrs)>\(formattedChildren)</\(validName)>" | |
case let .content(string): | |
return escapeTextNode(string) | |
} | |
} | |
func xmlDeclaration(_ attrs: [(String, String)], _ root: Node) -> Node { | |
return .declaration(attrs, root) | |
} | |
func tag(_ tag: String, _ attrs: [(String, String)], _ children: [Node]) -> Node { | |
return .element(tag, attrs, children) | |
} | |
let root = tag("-1test!$&@", | |
[("t", "true"), ("t2", "false")], | |
[ | |
tag("test2", [], ["lorum & ipsum"]), | |
tag("test3", [], [tag("test4", [], ["epsum"])]) | |
] | |
) | |
let xmlDoc = xmlDeclaration([("version", "1.0"), ("encoding", "UTF-8")], root) | |
print(render(xmlDoc)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment