Skip to content

Instantly share code, notes, and snippets.

@avdwerff
Last active December 21, 2018 21:52
Show Gist options
  • Save avdwerff/6181603c802f47777828ce8ae1799a3c to your computer and use it in GitHub Desktop.
Save avdwerff/6181603c802f47777828ce8ae1799a3c to your computer and use it in GitHub Desktop.
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: "&amp;")
.replacingOccurrences(of: "<", with: "&lt;")
.replacingOccurrences(of: ">", with: "&gt;")
.replacingOccurrences(of: "\"", with: "&quot;")
.replacingOccurrences(of: "'", with: "&apos;")
}
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