Skip to content

Commit ce22a90

Browse files
updated escapeHTML macro behavior; added some documentation
1 parent 99a88c0 commit ce22a90

File tree

5 files changed

+47
-8
lines changed

5 files changed

+47
-8
lines changed

Sources/HTMLKit/HTMLKit.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public extension StringProtocol {
1919
}
2020

2121
@freestanding(expression)
22-
public macro escapeHTML<T: CustomStringConvertible>(_ innerHTML: T...) -> T = #externalMacro(module: "HTMLKitMacros", type: "EscapeHTML")
22+
public macro escapeHTML(_ innerHTML: CustomStringConvertible...) -> String = #externalMacro(module: "HTMLKitMacros", type: "EscapeHTML")
2323

2424
// MARK: HTML Representation
2525
@freestanding(expression)

Sources/HTMLKitUtilities/LiteralElements.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,18 +134,26 @@ macro HTMLElements(
134134

135135
// MARK: HTML
136136
public protocol HTMLElement : CustomStringConvertible {
137+
/// Whether or not this element is a void element.
137138
var isVoid : Bool { get }
139+
/// Whether or not this element should include a forward slash in the tag name.
138140
var trailingSlash : Bool { get }
139-
var tag: String { get }
141+
/// Whether or not to HTML escape the `<` & `>` characters directly adjacent of the opening and closing tag names when rendering.
142+
var escaped : Bool { get set }
143+
/// This element's tag name.
144+
var tag : String { get }
145+
/// The global attributes of this element.
140146
var attributes : [HTMLElementAttribute] { get }
147+
/// The inner HTML content of this element.
141148
var innerHTML : [CustomStringConvertible] { get }
142149
}
143150

144151
/// A custom HTML element.
145152
public struct custom : HTMLElement {
146153
public var isVoid:Bool
147154
public var trailingSlash:Bool
148-
public var tag:String
155+
public var escaped:Bool = false
156+
public let tag:String
149157
public var attributes:[HTMLElementAttribute]
150158
public var innerHTML:[CustomStringConvertible]
151159

@@ -171,7 +179,20 @@ public struct custom : HTMLElement {
171179
}
172180

173181
public var description : String {
174-
return "<" + tag + (isVoid && trailingSlash ? " /" : "") + ">" + (isVoid ? "" : "</" + tag + ">")
182+
let attributes_string:String = self.attributes.compactMap({
183+
guard let v:String = $0.htmlValue else { return nil }
184+
let delimiter:String = $0.htmlValueDelimiter
185+
return $0.key + ($0.htmlValueIsVoidable && v.isEmpty ? "" : "=\(delimiter)\(v)\(delimiter)")
186+
}).joined(separator: " ")
187+
let l:String, g:String
188+
if escaped {
189+
l = "&lt;"
190+
g = "&gt;"
191+
} else {
192+
l = "<"
193+
g = ">"
194+
}
195+
return l + tag + (isVoid && trailingSlash ? " /" : "") + g + (attributes_string.isEmpty ? "" : " " + attributes_string) + (isVoid ? "" : l + "/" + tag + g)
175196
}
176197
}
177198

Sources/HTMLKitUtilities/ParseData.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ public extension HTMLKitUtilities {
1414
static func escapeHTML(expansion: MacroExpansionExprSyntax, context: some MacroExpansionContext) -> String {
1515
return expansion.arguments.children(viewMode: .all).compactMap({
1616
guard let child:LabeledExprSyntax = $0.labeled,
17-
let c:CustomStringConvertible = HTMLKitUtilities.parseInnerHTML(context: context, child: child, lookupFiles: []) else {
17+
var c:CustomStringConvertible = HTMLKitUtilities.parseInnerHTML(context: context, child: child, lookupFiles: []) else {
1818
return nil
1919
}
20+
if var element:HTMLElement = c as? HTMLElement {
21+
element.escaped = true
22+
c = element
23+
}
2024
return String(describing: c)
2125
}).joined()
2226
}
@@ -112,7 +116,7 @@ public extension HTMLKitUtilities {
112116
) -> CustomStringConvertible? {
113117
if let expansion:MacroExpansionExprSyntax = child.expression.macroExpansion {
114118
if expansion.macroName.text == "escapeHTML" {
115-
return escapeHTML(expansion: expansion, context: context).escapingHTML(escapeAttributes: true)
119+
return escapeHTML(expansion: expansion, context: context)
116120
}
117121
return "" // TODO: fix?
118122
} else if let element:HTMLElement = parse_element(context: context, expr: child.expression) {

Sources/HTMLKitUtilityMacros/HTMLElements.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ enum HTMLElements : DeclarationMacro {
3636
var string:String = "/// MARK: \(element)\n/// The `\(element)` HTML element.\npublic struct \(element) : HTMLElement {\n"
3737
string += "public private(set) var isVoid:Bool = \(is_void)\n"
3838
string += "public var trailingSlash:Bool = false\n"
39-
string += "public private(set) var tag:String = \"\(element)\"\n"
39+
string += "public var escaped:Bool = false\n"
40+
string += "public let tag:String = \"\(element)\"\n"
4041
string += "public var attributes:[HTMLElementAttribute]\n"
4142

4243
var initializers:String = ""
@@ -180,7 +181,17 @@ enum HTMLElements : DeclarationMacro {
180181
render += attributes_func
181182
render += "let string:String = innerHTML.map({ String(describing: $0) }).joined()\n"
182183
let trailing_slash:String = is_void ? " + (trailingSlash ? \" /\" : \"\")" : ""
183-
render += "return \"\(element == "html" ? "<!DOCTYPE html>" : "")<\(element)\" + attributes()\(trailing_slash) + \">\" + string" + (is_void ? "" : " + \"</\(element)>\"")
184+
render += """
185+
let l:String, g:String
186+
if escaped {
187+
l = "&lt;"
188+
g = "&gt;"
189+
} else {
190+
l = "<"
191+
g = ">"
192+
}
193+
"""
194+
render += "return \(element == "html" ? "l + \"!DOCTYPE html\" + g + " : "")l + \"\(element)\" + attributes()\(trailing_slash) + g + string" + (is_void ? "" : " + l + \"/\(element)\" + g")
184195
render += "}"
185196

186197
string += render

Tests/HTMLKitTests/ElementTests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ struct ElementTests {
2121
string = #escapeHTML("<!DOCTYPE html><html>Test</html>")
2222
#expect(string == escaped)
2323

24+
string = #escapeHTML(html("Test"))
25+
#expect(string == escaped)
26+
2427
string = #html(p(#escapeHTML(html("Test"))))
2528
#expect(string == expected_result)
2629

0 commit comments

Comments
 (0)