Skip to content

Instantly share code, notes, and snippets.

@SteveTrewick
Last active July 22, 2020 18:14
Show Gist options
  • Save SteveTrewick/6f16a700fad9efea823f8a1c582c1318 to your computer and use it in GitHub Desktop.
Save SteveTrewick/6f16a700fad9efea823f8a1c582c1318 to your computer and use it in GitHub Desktop.
Now with some embedded text tags. I'm not sold on the method or the code, but it beats writing a ton of weird combinations of buildBlock. This is one way. There is another!
import Foundation
// so the main limitation of the previous efforts was that we couldn't do e.g.
// p { "here is " em { "some" } "emphasis" }
// without starting to write crazy amounts of buildBlock functions, so...
extension XMLNode {
func named(_ name: String) -> XMLNode {
self.name = name
return self
}
func string (_ string: String) -> XMLNode {
self.stringValue = string
return self
}
func children(_ children: [XMLNode] ) -> XMLNode {
_ = children.map((self as! XMLElement).addChild)
return self
}
func attributes( _ attributes: [XMLNode] ) -> XMLNode {
_ = attributes.map((self as! XMLElement).addAttribute)
return self
}
}
@_functionBuilder struct XMLBuilder {
// I mean, we could parse markdown...
static func textNode( _ string: String) -> XMLNode {
let doc = try! XMLDocument(xmlString: "<span>\(string)</span>")
let node = XMLElement()
for child in doc.rootElement()?.children ?? [] {
child.detach()
node.addChild(child)
}
return node
}
static func buildBlock( _ content: String) -> XMLNode {
textNode(content)
}
static func buildBlock(_ content: XMLNode...) -> XMLNode {
XMLNode(kind: .element).children(content.map{$0})
}
static func buildBlock(_ content: String, _ children: XMLNode...) -> XMLNode {
textNode(content).children(children.map{$0})
}
}
enum CssAttribute {
case id (String)
case `class` (String)
func attribute(_ name: String, _ value: String) -> XMLNode {
XMLNode.attribute(withName: name, stringValue: value) as! XMLNode
}
var node: XMLNode {
switch self {
case .id (let value): return attribute("id", value)
case .class(let value): return attribute("class", value)
}
}
}
func html (@XMLBuilder _ content: () -> XMLNode) -> XMLElement {
content().named("html") as! XMLElement
}
func body (_ attribs: CssAttribute..., @XMLBuilder content: () -> XMLNode) -> XMLNode {
content().named("body").attributes( attribs.map{$0.node} )
}
func div (_ attribs: CssAttribute..., @XMLBuilder content: () -> XMLNode) -> XMLNode {
content().named("div").attributes( attribs.map{$0.node} )
}
func p (_ attribs: CssAttribute..., @XMLBuilder content: () -> XMLNode) -> XMLNode {
content().named("p").attributes( attribs.map{$0.node} )
}
func pretty(_ element: XMLElement) {
let xmldoc = XMLDocument(rootElement: element)
xmldoc.documentContentKind = .html
print(xmldoc.xmlString(options: [ .nodePrettyPrint, .documentTidyHTML, .nodePreserveWhitespace]))
}
pretty (
html {
body {
div ( .id("main"), .class("content") ) {
p { "paragraph 1" }
p { "paragraph 2" }
}
div ( .id("secondary"), .class("content") ) {
p { "paragraph 3" }
p { "paragraph 4" }
}
div {
// now let's add some in line tags...
// if we didn't parse these with XMLDoc they'd be escaped
// we lose some type safety, but we would if we went with markdown or similar as well
p {
"It was the <em>best</em> of days, it was the <strong>worst of <em>all</em></strong> of the days"
}
}
}
}
)
/*
<html>
<body>
<div id="main" class="content">
<p>paragraph 1</p>
<p>paragraph 2</p>
</div>
<div id="secondary" class="content">
<p>paragraph 3</p>
<p>paragraph 4</p>
</div>
<div>
<p>It was the <em>best</em>
of days, it was the <strong>worst of <em>all</em>
</strong>
of the days</p>
</div>
</body>
</html>
XMLDocument has some weird ideas about how this should be formatted /shrug/
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment