diff --git a/Sources/Elementary/Core/AttributeStorage.swift b/Sources/Elementary/Core/AttributeStorage.swift index 04c48d3..443e0e6 100644 --- a/Sources/Elementary/Core/AttributeStorage.swift +++ b/Sources/Elementary/Core/AttributeStorage.swift @@ -100,6 +100,10 @@ public struct _MergedAttributes: Sequence, Sendable { Iterator(storage) } + public var isEmpty: Bool { + storage.isEmpty + } + public struct Iterator: IteratorProtocol { enum State { case empty diff --git a/Sources/Elementary/Core/Html+Elements.swift b/Sources/Elementary/Core/Html+Elements.swift index 6a70cbb..069faf6 100644 --- a/Sources/Elementary/Core/Html+Elements.swift +++ b/Sources/Elementary/Core/Html+Elements.swift @@ -166,7 +166,7 @@ extension HTMLComment: Sendable {} extension HTMLRaw: Sendable {} extension HTMLTagDefinition { - @usableFromInline + @usableFromInline @inline(__always) static var renderingType: _HTMLRenderToken.RenderingType { _rendersInline ? .inline : .block } diff --git a/Sources/Elementary/Rendering/RenderingUtils.swift b/Sources/Elementary/Rendering/RenderingUtils.swift index bfecac5..e6336d8 100644 --- a/Sources/Elementary/Rendering/RenderingUtils.swift +++ b/Sources/Elementary/Rendering/RenderingUtils.swift @@ -23,61 +23,99 @@ extension _RenderingContext { // I do not know why this function does not work in embedded, but currently it crashes the compiler #if !hasFeature(Embedded) extension [UInt8] { + @inline(__always) mutating func appendToken(_ token: consuming _HTMLRenderToken) { // avoid strings and append each component directly switch token { case let .startTag(tagName, attributes: attributes, isUnpaired: _, type: _): append(60) // < - append(contentsOf: tagName.utf8) - for attribute in attributes { - append(32) // space - append(contentsOf: attribute.name.utf8) - if let value = attribute.value { - append(contentsOf: [61, 34]) // =" - appendEscapedAttributeValue(value) - append(34) // " + appendString(tagName) + if !attributes.isEmpty { + for attribute in attributes { + append(32) // space + appendString(attribute.name) + if let value = attribute.value { + append(contentsOf: [61, 34]) // =" + appendEscapedAttributeValue(value) + append(34) // " + } } } append(62) // > case let .endTag(tagName, _): append(contentsOf: [60, 47]) // case let .text(text): appendEscapedText(text) case let .raw(raw): - append(contentsOf: raw.utf8) + appendString(raw) case let .comment(comment): - append(contentsOf: "".utf8) + appendString("-->") + } + } + + @inline(__always) + mutating func appendString(_ string: consuming String) { + string.withUTF8 { utf8 in + append(contentsOf: utf8) } } + @inline(__always) mutating func appendEscapedAttributeValue(_ value: consuming String) { - for byte in value.utf8 { - switch byte { - case 38: // & - append(contentsOf: "&".utf8) - case 34: // " - append(contentsOf: """.utf8) - default: - append(byte) + value.withUTF8 { utf8 in + var start = utf8.startIndex + + for current in utf8.indices { + switch utf8[current] { + case 38: // & + append(contentsOf: utf8[start ..< current]) + appendString("&") + start = utf8.index(after: current) + case 34: // " + append(contentsOf: utf8[start ..< current]) + appendString(""") + start = utf8.index(after: current) + default: + () + } + } + + if start < utf8.endIndex { + append(contentsOf: utf8[start ..< utf8.endIndex]) } } } + @inline(__always) mutating func appendEscapedText(_ value: consuming String) { - for byte in value.utf8 { - switch byte { - case 38: // & - append(contentsOf: "&".utf8) - case 60: // < - append(contentsOf: "<".utf8) - case 62: // > - append(contentsOf: ">".utf8) - default: - append(byte) + value.withUTF8 { utf8 in + var start = utf8.startIndex + + for current in utf8.indices { + switch utf8[current] { + case 38: // & + append(contentsOf: utf8[start ..< current]) + appendString("&") + start = utf8.index(after: current) + case 60: // < + append(contentsOf: utf8[start ..< current]) + appendString("<") + start = utf8.index(after: current) + case 62: // > + append(contentsOf: utf8[start ..< current]) + appendString(">") + start = utf8.index(after: current) + default: + () + } + } + + if start < utf8.endIndex { + append(contentsOf: utf8[start ..< utf8.endIndex]) } } } diff --git a/Tests/ElementaryTests/Utilities.swift b/Tests/ElementaryTests/Utilities.swift index 7942b7c..506679b 100644 --- a/Tests/ElementaryTests/Utilities.swift +++ b/Tests/ElementaryTests/Utilities.swift @@ -15,6 +15,3 @@ func HTMLAssertEqualAsyncOnly(_ html: some HTML, _ expected: String, file: Stati func HTMLFormattedAssertEqual(_ html: some HTML, _ expected: String, file: StaticString = #filePath, line: UInt = #line) { XCTAssertEqual(expected, html.renderFormatted(), file: file, line: line) } - -@inline(never) -func blackHole(_: T) {}