Skip to content

Commit b4cdb57

Browse files
fixes
1 parent a774d89 commit b4cdb57

File tree

8 files changed

+83
-47
lines changed

8 files changed

+83
-47
lines changed

Sources/HTMLKitUtilities/HTMLElementAttribute.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,15 @@ public enum HTMLElementAttribute : Hashable {
221221
case .event(_, let value): return value
222222
}
223223
}
224+
225+
public var htmlValueIsVoidable : Bool {
226+
switch self {
227+
case .autofocus(_), .hidden(_), .inert(_), .itemscope(_):
228+
return true
229+
case .htmx(let value):
230+
return value?.htmlValueIsVoidable ?? false
231+
default:
232+
return false
233+
}
234+
}
224235
}

Sources/HTMLKitUtilities/HTMLElementAttributeExtra.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ public protocol HTMLInitializable : Hashable {
1414

1515
var key : String { get }
1616
var htmlValue : String? { get }
17+
var htmlValueIsVoidable : Bool { get }
1718
}
1819
public extension HTMLInitializable where Self: RawRepresentable, RawValue == String {
1920
var key : String { rawValue }
2021
var htmlValue : String? { rawValue }
22+
var htmlValueIsVoidable : Bool { false }
2123

2224
init?(context: some MacroExpansionContext, key: String, arguments: LabeledExprListSyntax) {
2325
guard let value:Self = .init(rawValue: key) else { return nil }
@@ -362,6 +364,8 @@ public extension HTMLElementAttribute.Extra {
362364
}
363365
}
364366

367+
public var htmlValueIsVoidable : Bool { false }
368+
365369
public enum Autocomplete : String, HTMLInitializable {
366370
case none, inline, list, both
367371
}
@@ -602,6 +606,8 @@ public extension HTMLElementAttribute.Extra {
602606
case .custom(let value): return "--" + value
603607
}
604608
}
609+
610+
public var htmlValueIsVoidable : Bool { false }
605611
}
606612

607613
// MARK: contenteditable
@@ -681,6 +687,13 @@ public extension HTMLElementAttribute.Extra {
681687
case .filename(let value): return value
682688
}
683689
}
690+
691+
public var htmlValueIsVoidable : Bool {
692+
switch self {
693+
case .empty: return true
694+
default: return false
695+
}
696+
}
684697
}
685698

686699
// MARK: enterkeyhint

Sources/HTMLKitUtilities/HTMLKitUtilities.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public extension String {
6464
}
6565
/// Escapes all occurrences of source-breaking HTML attribute characters
6666
mutating func escapeHTMLAttributes() {
67+
self.replace("\\\"", with: """)
6768
self.replace("\"", with: """)
6869
self.replace("'", with: "&#39")
6970
}
@@ -175,6 +176,8 @@ public extension HTMLElementAttribute {
175176
}
176177
}
177178

179+
public var htmlValueIsVoidable : Bool { false }
180+
178181
public var suffix : String {
179182
switch self {
180183
case .centimeters(_): return "cm"

Sources/HTMLKitUtilities/HTMX.swift

Lines changed: 23 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,7 @@ public extension HTMLElementAttribute {
6565
case "disinherit": self = .disinherit(string())
6666
case "encoding": self = .encoding(string())
6767
case "ext": self = .ext(string())
68-
case "headers": // TODO: fix
69-
//let dictionary = expression.dictionary!
70-
var js:Bool = false
71-
var headers:[String:String] = [:]
72-
self = .headers(js: js, headers)
73-
break
68+
case "headers": self = .headers(js: boolean() ?? false, arguments.last!.expression.dictionary_string_string(context: context, key: key))
7469
case "history": self = .history(enumeration())
7570
case "historyElt": self = .historyElt(boolean())
7671
case "include": self = .include(string())
@@ -118,46 +113,20 @@ public extension HTMLElementAttribute {
118113
}
119114
self = .request(js: javascript, timeout: timeout, credentials: credentials, noHeaders: noHeaders)
120115
break*/
121-
case "sync": // TODO: fix
122-
return nil
123-
/*
124-
let string:String = literal()
125-
let values:[Substring] = string.split(separator: ",")
126-
var key:Substring = values[0]
127-
key.removeLast() // "
128-
var strategy:SyncStrategy? = nil
129-
var strategy_string:Substring = values[1].split(separator: ":")[1]
130-
if !strategy_string.hasSuffix("nil") {
131-
while (strategy_string.first?.isWhitespace ?? false) || strategy_string.first == "." {
132-
strategy_string.removeFirst()
133-
}
134-
strategy = SyncStrategy(rawValue: String(strategy_string))
135-
}
136-
self = .sync(String(key), strategy: strategy)
137-
break*/
116+
case "sync":
117+
guard let s:String = string() else { return nil }
118+
self = .sync(s, strategy: arguments.last!.expression.enumeration(context: context, key: key, arguments: arguments))
138119
case "validate": self = .validate(enumeration())
139120

140121
case "get": self = .get(string())
141122
case "post": self = .post(string())
142-
case "on", "onevent": // TODO: fix
143-
return nil
144-
/*
145-
let string:String = literal()
146-
let values:[Substring] = string.split(separator: ",")
147-
let event_string:String = String(values[0])
148-
var value:String = String(string[values[1].startIndex...])
149-
while (value.first?.isWhitespace ?? false) || value.first == "\"" {
150-
value.removeFirst()
151-
}
152-
value.removeLast()
123+
case "on", "onevent":
124+
guard let s:String = arguments.last!.expression.string(context: context, key: key) else { return nil }
153125
if key == "on" {
154-
let event:Event = Event(rawValue: event_string)!
155-
self = .on(event, value)
126+
self = .on(enumeration(), s)
156127
} else {
157-
let event:HTMLElementAttribute.Extra.event = .init(rawValue: event_string)!
158-
self = .onevent(event, value)
128+
self = .onevent(enumeration(), s)
159129
}
160-
break*/
161130
case "pushURL": self = .pushURL(enumeration())
162131
case "select": self = .select(string())
163132
case "selectOOB": self = .selectOOB(string())
@@ -230,7 +199,7 @@ public extension HTMLElementAttribute {
230199
case .encoding(let value): return value
231200
case .ext(let value): return value
232201
case .headers(let js, let headers):
233-
return (js ? "js:" : "") + "{" + headers.map({ "\\\"" + $0.key + "\\\":\\\"" + $0.value + "\\\"" }).joined(separator: ",") + "}"
202+
return (js ? "js:" : "") + "{" + headers.map({ item in #"\"\#(item.key)\":\"\#(item.value)\""# }).joined(separator: ",") + "}"
234203
case .history(let value): return value?.rawValue
235204
case .historyElt(let value): return value ?? false ? "" : nil
236205
case .include(let value): return value
@@ -273,5 +242,19 @@ public extension HTMLElementAttribute {
273242
case .ws(let value): return value?.htmlValue
274243
}
275244
}
245+
246+
public var htmlValueIsVoidable : Bool {
247+
switch self {
248+
case .disable(_), .historyElt(_), .preserve(_):
249+
return true
250+
case .ws(let value):
251+
switch value {
252+
case .send(_): return true
253+
default: return false
254+
}
255+
default:
256+
return false
257+
}
258+
}
276259
}
277260
}

Sources/HTMLKitUtilities/HTMXAttributes.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ public extension HTMLElementAttribute.HTMX {
151151
case .list(let list): return list.joined(separator: ",")
152152
}
153153
}
154+
155+
public var htmlValueIsVoidable : Bool { false }
154156
}
155157

156158
// MARK: Swap
@@ -201,6 +203,8 @@ public extension HTMLElementAttribute.HTMX {
201203
case .queue(let queue): return (queue != nil ? "queue " + queue!.rawValue : nil)
202204
}
203205
}
206+
207+
public var htmlValueIsVoidable : Bool { false }
204208
}
205209

206210
// MARK: URL
@@ -232,6 +236,8 @@ public extension HTMLElementAttribute.HTMX {
232236
case .url(let url): return url.hasPrefix("http://") || url.hasPrefix("https://") ? url : (url.first == "/" ? "" : "/") + url
233237
}
234238
}
239+
240+
public var htmlValueIsVoidable : Bool { false }
235241
}
236242
}
237243

@@ -268,6 +274,8 @@ public extension HTMLElementAttribute.HTMX {
268274
return value
269275
}
270276
}
277+
278+
public var htmlValueIsVoidable : Bool { false }
271279
}
272280
}
273281

@@ -302,6 +310,13 @@ public extension HTMLElementAttribute.HTMX {
302310
}
303311
}
304312

313+
public var htmlValueIsVoidable : Bool {
314+
switch self {
315+
case .send(_): return true
316+
default: return false
317+
}
318+
}
319+
305320
public enum Event : String {
306321
case wsConnecting
307322
case wsOpen

Sources/HTMLKitUtilities/ParseData.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,9 @@ public extension HTMLKitUtilities {
106106
) -> CustomStringConvertible? {
107107
if let expansion:MacroExpansionExprSyntax = child.expression.macroExpansion {
108108
return "" // TODO: fix?
109-
} else if let string:HTMLElement = parse_element(context: context, expr: child.expression) {
110-
return string
111-
} else if var string:String = parse_literal_value(context: context, key: "", expression: child.expression, lookupFiles: lookupFiles)?.value(key: "") {
112-
string.escapeHTML(escapeAttributes: false)
109+
} else if let element:HTMLElement = parse_element(context: context, expr: child.expression) {
110+
return element
111+
} else if let string:String = parse_literal_value(context: context, key: "", expression: child.expression, lookupFiles: lookupFiles)?.value(key: "") {
113112
return string
114113
} else {
115114
unallowed_expression(context: context, node: child)
@@ -203,8 +202,8 @@ public extension HTMLKitUtilities {
203202
}
204203
break
205204
}
206-
return .interpolation("\(function)")
207205
}
206+
return .interpolation("\(function)")
208207
}
209208
if let member:MemberAccessExprSyntax = expression.memberAccess {
210209
return .interpolation("\(member)")
@@ -325,6 +324,7 @@ package extension SyntaxProtocol {
325324
var integerLiteral : IntegerLiteralExprSyntax? { self.as(IntegerLiteralExprSyntax.self) }
326325
var floatLiteral : FloatLiteralExprSyntax? { self.as(FloatLiteralExprSyntax.self) }
327326
var array : ArrayExprSyntax? { self.as(ArrayExprSyntax.self) }
327+
var dictionary : DictionaryExprSyntax? { self.as(DictionaryExprSyntax.self) }
328328
var memberAccess : MemberAccessExprSyntax? { self.as(MemberAccessExprSyntax.self) }
329329
var macroExpansion : MacroExpansionExprSyntax? { self.as(MacroExpansionExprSyntax.self) }
330330
var functionCall : FunctionCallExprSyntax? { self.as(FunctionCallExprSyntax.self) }
@@ -359,6 +359,17 @@ package extension ExprSyntax {
359359
func array_string(context: some MacroExpansionContext, key: String) -> [String] {
360360
array?.elements.compactMap({ $0.expression.string(context: context, key: key) }) ?? []
361361
}
362+
func dictionary_string_string(context: some MacroExpansionContext, key: String) -> [String:String] {
363+
var d:[String:String] = [:]
364+
if let elements:DictionaryElementListSyntax = dictionary?.content.as(DictionaryElementListSyntax.self) {
365+
for element in elements {
366+
if let key:String = element.key.string(context: context, key: key), let value:String = element.value.string(context: context, key: key) {
367+
d[key] = value
368+
}
369+
}
370+
}
371+
return d
372+
}
362373
func float(context: some MacroExpansionContext, key: String) -> Float? {
363374
guard let s:String = HTMLKitUtilities.parse_literal_value(context: context, key: key, expression: self, lookupFiles: [])?.value(key: key) else { return nil }
364375
return Float(s)

Sources/HTMLKitUtilityMacros/HTMLElements.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ enum HTMLElements : DeclarationMacro {
122122
var attributes_func:String = "func attributes() -> String {\n"
123123
attributes_func += (attributes.isEmpty ? "let" : "var") + " items:[String] = self.attributes.compactMap({\n"
124124
attributes_func += "guard let v:String = $0.htmlValue?.escapingHTML(escapeAttributes: true) else { return nil }\n"
125-
attributes_func += #"return "\($0.key)" + (v.isEmpty ? "" : "=\\\"\(v)\\\"")"#
125+
attributes_func += #"return "\($0.key)" + ($0.htmlValueIsVoidable && v.isEmpty ? "" : "=\\\"\(v)\\\"")"#
126126
attributes_func += "\n})"
127127
for (key, value_type, _) in attributes {
128128
var key_literal:String = key

Tests/HTMLKitTests/InterpolationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ extension InterpolationTests {
8585
#expect(static_string == "<div title=\"Mr. Crabs\"></div>")
8686
}
8787
@Test func third_party_func() {
88-
let string:String = #html(div(attributes: [.title(InterpolationTests.spongebobCharacter("patrick"))]))
88+
let string:String = #html(div(attributes: [.title(InterpolationTests.spongebobCharacter("patrick"))])) // TODO: fix | do not escape HTML for interpolation
8989
#expect(string == "<div title=\"Patrick Star\"></div>")
9090
}
9191
}

0 commit comments

Comments
 (0)