Skip to content

Instantly share code, notes, and snippets.

@SteveTrewick
Last active July 22, 2020 15:56
Show Gist options
  • Save SteveTrewick/e982397a82e3deb2a7a7ed47ef98573d to your computer and use it in GitHub Desktop.
Save SteveTrewick/e982397a82e3deb2a7a7ed47ef98573d to your computer and use it in GitHub Desktop.
Second and much nicer cut on HTML DSL in swift. No attributes etc yet, but this is a cleaner base design (in some ways at least)
import Foundation
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 {
static func buildBlock( _ content: String) -> XMLNode {
XMLNode(kind: .element).string(content)
}
static func buildBlock(_ content: XMLNode...) -> XMLNode {
XMLNode(kind: .element).children(content.map{$0})
}
static func buildBlock(_ content: String, _ children: XMLNode...) -> XMLNode {
XMLNode(kind: .element).string(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" }
}
}
}
)
/*
<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>
</body>
</html>
*/
@SteveTrewick
Copy link
Author

Updated to clean up a bit

@SteveTrewick
Copy link
Author

Added in some css attributes. Note we can lose the underscore and use variadic notation for the first param while avoiding the need to actually use the name of the trailing closure param. Fun!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment