Skip to content

Commit bccc860

Browse files
fixes; added custom struct conforming to HTMLElement and...
- now parses arrays
1 parent 86042f6 commit bccc860

File tree

7 files changed

+121
-138
lines changed

7 files changed

+121
-138
lines changed

Sources/HTMLKit/HTMLKit.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,4 @@ public macro htmlData<T: ExpressibleByStringLiteral>(lookupFiles: [StaticString]
5151
#endif
5252

5353
@freestanding(expression)
54-
public macro htmlByteBuffer<T: ExpressibleByStringLiteral>(lookupFiles: [StaticString] = [], attributes: [HTMLElementAttribute] = [], xmlns: T? = nil, _ innerHTML: T...) -> ByteBuffer = #externalMacro(module: "HTMLKitMacros", type: "HTMLElementMacro")
55-
56-
// MARK: Elements
57-
58-
@freestanding(expression)
59-
public macro custom<T: ExpressibleByStringLiteral>(tag: String, isVoid: Bool, attributes: [HTMLElementAttribute] = [], _ innerHTML: T...) -> T = #externalMacro(module: "HTMLKitMacros", type: "HTMLElementMacro")
54+
public macro htmlByteBuffer<T: ExpressibleByStringLiteral>(lookupFiles: [StaticString] = [], attributes: [HTMLElementAttribute] = [], xmlns: T? = nil, _ innerHTML: T...) -> ByteBuffer = #externalMacro(module: "HTMLKitMacros", type: "HTMLElementMacro")

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 4 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -71,35 +71,9 @@ private extension HTMLElementMacro {
7171
return parse_inner_html(context: context, elementType: elementType, child: child, lookupFiles: [])
7272
}).joined()
7373
}
74-
let tag:String, isVoid:Bool
75-
var children:Slice<SyntaxChildren>
76-
if elementType == .custom {
77-
tag = childs.first(where: { $0.labeled?.label?.text == "tag" })!.labeled!.expression.stringLiteral!.string
78-
isVoid = childs.first(where: { $0.labeled?.label?.text == "isVoid" })!.labeled!.expression.booleanLiteral!.literal.text == "true"
79-
children = childs.dropFirst() // tag
80-
children.removeFirst() // isVoid
81-
} else {
82-
tag = elementType.rawValue.starts(with: "html") ? "html" : elementType.rawValue
83-
isVoid = elementType.isVoid
84-
children = childs.prefix(childs.count)
85-
}
74+
let children:Slice<SyntaxChildren> = childs.prefix(childs.count)
8675
let (attributes, innerHTML, trailingSlash):(String, String, Bool) = parse_arguments(context: context, elementType: elementType, children: children)
8776
return innerHTML
88-
89-
var string:String = (tag == "html" ? "<!DOCTYPE html>" : "") + "<" + tag + attributes
90-
if trailingSlash {
91-
if isVoid {
92-
string += " /"
93-
} else {
94-
context.diagnose(Diagnostic(node: macro, message: DiagnosticMsg(id: "trailingSlashGlobalAttributeMisused", message: "Trailing Slash global attribute can only be applied to void elements.")))
95-
return ""
96-
}
97-
}
98-
string += ">" + innerHTML
99-
if !isVoid {
100-
string += "</" + tag + ">"
101-
}
102-
return string
10377
}
10478
// MARK: Parse Arguments
10579
static func parse_arguments(
@@ -261,13 +235,7 @@ private extension HTMLElementMacro {
261235
child: LabeledExprSyntax,
262236
lookupFiles: Set<String>
263237
) -> String? {
264-
if let macro:MacroExpansionExprSyntax = child.expression.macroExpansion {
265-
var string:String = expand_macro(context: context, macro: macro)
266-
if elementType == .escapeHTML {
267-
string.escapeHTML(escapeAttributes: false)
268-
}
269-
return string
270-
} else if let string:String = parse_element(expr: child.expression) {
238+
if let string:String = parse_element(expr: child.expression) {
271239
return string
272240
} else if var string:String = parse_literal_value(context: context, elementType: elementType, key: "", expression: child.expression, lookupFiles: lookupFiles)?.value {
273241
string.escapeHTML(escapeAttributes: false)
@@ -288,22 +256,6 @@ private extension HTMLElementMacro {
288256
]))
289257
}
290258

291-
static func enumName(elementType: HTMLElementType, key: String) -> String {
292-
switch elementType.rawValue + key {
293-
case "buttontype": return "buttontype"
294-
case "formenctype": return "formenctype"
295-
case "inputtype": return "inputtype"
296-
case "metahttp-equiv": return "httpequiv"
297-
case "oltype": return "numberingtype"
298-
case "scripttype": return "scripttype"
299-
default:
300-
if key.hasPrefix("aria-") {
301-
return "ariaattribute"
302-
}
303-
return key
304-
}
305-
}
306-
307259
// MARK: Parse Attribute
308260
static func parse_attribute(
309261
context: some MacroExpansionContext,
@@ -326,10 +278,6 @@ private extension HTMLElementMacro {
326278
return key + "=\\\"" + string + "\\\""
327279
}
328280
}
329-
if let function:FunctionCallExprSyntax = expression.functionCall {
330-
let string:String = "\(function)"
331-
return HTMLElementAttribute.Extra.htmlValue(enumName: enumName(elementType: elementType, key: key), for: String(string[string.index(after: string.startIndex)...]))
332-
}
333281
return nil
334282
}
335283
// MARK: Parse element
@@ -394,10 +342,7 @@ private extension HTMLElementMacro {
394342
if let function:FunctionCallExprSyntax = expression.functionCall {
395343
let enums:Set<String> = ["command", "download", "height", "width"]
396344
if enums.contains(key) || key.hasPrefix("aria-") {
397-
var value:String = "\(function)"
398-
value = String(value[value.index(after: value.startIndex)...])
399-
value = HTMLElementAttribute.Extra.htmlValue(enumName: enumName(elementType: elementType, key: key), for: value)
400-
return (value, .enumCase)
345+
return ("", .enumCase)
401346
} else {
402347
if let decl:String = function.calledExpression.as(DeclReferenceExprSyntax.self)?.baseName.text {
403348
switch decl {
@@ -420,10 +365,7 @@ private extension HTMLElementMacro {
420365
}
421366
}
422367
if let member:MemberAccessExprSyntax = expression.memberAccess {
423-
if let _:ExprSyntax = member.base {
424-
return ("\(member)", .interpolation)
425-
}
426-
return (HTMLElementAttribute.Extra.htmlValue(enumName: enumName(elementType: elementType, key: key), for: member.declName.baseName.text), .enumCase)
368+
return ("\(member)", .interpolation)
427369
}
428370
if let array:ArrayExprSyntax = expression.array {
429371
let separator:Character, separator_string:String
@@ -451,9 +393,6 @@ private extension HTMLElementMacro {
451393
if let string:String = element.expression.integerLiteral?.literal.text ?? element.expression.floatLiteral?.literal.text {
452394
result += string + separator_string
453395
}
454-
if let string:String = element.expression.memberAccess?.declName.baseName.text {
455-
result += HTMLElementAttribute.Extra.htmlValue(enumName: enumName(elementType: elementType, key: key), for: string) + separator_string
456-
}
457396
}
458397
if !result.isEmpty {
459398
result.removeLast()
@@ -558,28 +497,6 @@ extension StringLiteralExprSyntax {
558497
}
559498

560499
extension HTMLElementAttribute.Extra {
561-
static func htmlValue(enumName: String, for enumCase: String) -> String { // only need to check the ones where the htmlValue is different from the rawValue
562-
switch enumName {
563-
case "ariaattribute": return ariaattribute(rawValue: enumCase)?.htmlValue ?? "???"
564-
case "command": return command(rawValue: enumCase)!.htmlValue!
565-
case "contenteditable": return contenteditable(rawValue: enumCase)!.htmlValue!
566-
case "crossorigin": return crossorigin(rawValue: enumCase)!.htmlValue!
567-
case "download": return download(rawValue: enumCase)!.htmlValue!
568-
case "formenctype": return formenctype(rawValue: enumCase)!.htmlValue!
569-
case "hidden": return hidden(rawValue: enumCase)!.htmlValue!
570-
case "httpequiv": return httpequiv(rawValue: enumCase)!.htmlValue!
571-
case "inputtype": return inputtype(rawValue: enumCase)!.htmlValue!
572-
case "numberingtype": return numberingtype(rawValue: enumCase)!.htmlValue!
573-
case "referrerpolicy": return referrerpolicy(rawValue: enumCase)!.htmlValue!
574-
case "rel": return rel(rawValue: enumCase)!.htmlValue!
575-
case "sandbox": return sandbox(rawValue: enumCase)!.htmlValue!
576-
case "height", "width":
577-
let values:[Substring] = enumCase.split(separator: "("), key:String = String(values[0]), value:String = String(values[1])
578-
return value[value.startIndex..<value.index(before: value.endIndex)] + CSSUnitType(rawValue: key)!.suffix
579-
default: return enumCase
580-
}
581-
}
582-
583500
enum CSSUnitType : String {
584501
case centimeters
585502
case millimeters

Sources/HTMLKitUtilities/HTMLKitUtilities.swift

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ public extension HTMLElementAttribute.CSSUnit { // https://www.w3schools.com/css
8989
// MARK: HTMLElementType
9090
package enum HTMLElementType : String, CaseIterable {
9191
case escapeHTML
92-
case custom
9392

9493
case html, htmlUTF8Bytes, htmlUTF16Bytes, htmlUTF8CString
9594

@@ -274,9 +273,12 @@ package indirect enum HTMLElementValueType {
274273
}
275274
guard !range.isEmpty else { return "" }
276275
range.removeFirst() // "
277-
guard let index:Substring.Index = range.firstIndex(of: "\"") else { return nil }
278-
let string:String = String(range[range.startIndex..<index])
276+
guard let index:Substring.Index = range.firstIndex(of: ",") ?? range.firstIndex(of: ")") else { return nil }
277+
var string:String = String(range[range.startIndex..<index])
279278
range.removeFirst(string.count + 1)
279+
if string.last == "\"" {
280+
string.removeLast()
281+
}
280282
cleanup(&range)
281283
return string
282284
}
@@ -334,20 +336,35 @@ package indirect enum HTMLElementValueType {
334336
cleanup(&range)
335337
return value
336338
}
337-
static func cArrayString(key: String, _ range: inout Substring) -> [String] {
338-
guard consumable(key: key, range: &range) else { return [] }
339-
var values:[String] = []
340-
if range.first == "[" {
341-
if let ends:Substring.Index = range.firstIndex(of: "]") {
342-
let string:Substring = range[range.startIndex..<ends]
343-
range = range[range.index(after: ends)...]
344-
cleanup(&range)
339+
static func cArray<T>(key: String, _ range: inout Substring, parse: (String) -> T?) -> [T] {
340+
guard consumable(key: key, range: &range), range.first == "[" else { return [] }
341+
range.removeFirst()
342+
var string:String = "", depth:Int = 1
343+
while let char:Character = range.first {
344+
if char == "[" {
345+
depth += 1
346+
} else if char == "]" {
347+
depth -= 1
348+
if depth == 0 {
349+
range.removeFirst()
350+
break
351+
}
345352
}
353+
string.append(range.removeFirst())
346354
}
355+
var values:[T] = []
356+
for var ss in string.split(separator: ",") {
357+
while ss.first?.isWhitespace ?? false {
358+
ss.removeFirst()
359+
}
360+
if let value:T = parse(String(ss)) {
361+
values.append(value)
362+
}
363+
}
364+
cleanup(&range)
347365
return values
348366
}
349367
static func cInnerHTML(_ range: inout Substring) -> String {
350-
//print("HTMLKitUtilities;cInnerHTML;range=\(range)")
351368
cleanup(&range)
352369
var values:[String] = []
353370
while let char:Character = range.first {
@@ -360,7 +377,7 @@ package indirect enum HTMLElementValueType {
360377
}
361378
} else if let parenth:Substring.Index = range.firstIndex(of: "(") {
362379
let key:String = String(range[range.startIndex..<parenth])
363-
if let element_type:HTMLElementType = HTMLElementType(rawValue: key) {
380+
if HTMLElementType(rawValue: key) != nil || key == "custom" {
364381
var depth:Int = 0
365382
var string:String = ""
366383
while let character:Character = range.first {
@@ -501,9 +518,11 @@ package indirect enum HTMLElementValueType {
501518
case "track": return track(rawValue: rawValue)
502519
case "u": return u(rawValue: rawValue)
503520
case "ul": return ul(rawValue: rawValue)
504-
//case "var": return var(rawValue: rawValue)
521+
//case "var": return `var`(rawValue: rawValue)
505522
case "video": return video(rawValue: rawValue)
506523
case "wbr": return wbr(rawValue: rawValue)
524+
525+
case "custom": return custom(rawValue: rawValue)
507526
default: return nil
508527
}
509528
}

Sources/HTMLKitUtilities/LiteralElements.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,36 @@ public protocol HTMLElement : CustomStringConvertible {
137137
var innerHTML : [CustomStringConvertible] { get }
138138
}
139139

140+
/// A custom HTML element.
141+
public struct custom : HTMLElement {
142+
public var tag:String
143+
public var isVoid:Bool
144+
public var attributes:[HTMLElementAttribute]
145+
public var innerHTML:[CustomStringConvertible]
146+
147+
public init?(rawValue: String) {
148+
tag = ""
149+
isVoid = false
150+
attributes = []
151+
innerHTML = []
152+
}
153+
public init(
154+
tag: String,
155+
isVoid: Bool,
156+
attributes: [HTMLElementAttribute] = [],
157+
_ innerHTML: CustomStringConvertible...
158+
) {
159+
self.tag = tag
160+
self.isVoid = isVoid
161+
self.attributes = attributes
162+
self.innerHTML = innerHTML
163+
}
164+
165+
public var description : String {
166+
return "<" + tag + ">" + (isVoid ? "" : "</" + tag + ">")
167+
}
168+
}
169+
140170
#HTMLElements([
141171
// MARK: A
142172
.a : [

0 commit comments

Comments
 (0)