From f46c85bc4594d525d45379bb8c03847a4c7dc94e Mon Sep 17 00:00:00 2001 From: Chris Eidhof Date: Tue, 12 Oct 2021 11:23:03 +0200 Subject: [PATCH] Initial commit --- Sources/HTML/Tags.swift | 7 +++++++ Sources/Swim/Node.swift | 31 ++++++++++++++++++++++++++----- Sources/Swim/Visitor.swift | 4 ++-- Tests/HTMLTests/HTMLTests.swift | 4 ++-- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/Sources/HTML/Tags.swift b/Sources/HTML/Tags.swift index b4c51e8..a4f1d90 100644 --- a/Sources/HTML/Tags.swift +++ b/Sources/HTML/Tags.swift @@ -3,6 +3,13 @@ @_exported import Swim +extension Node { + static func element(_ name: String, _ attributes: [String:String], _ node: Node?) -> Node { + Node.element(name, Attributes(attributes: attributes.sorted(by: { $0.key < $1.key }).map(Attribute.init)), node) + } +} + + public struct ATag: Tag { public let elementName: String = "a" diff --git a/Sources/Swim/Node.swift b/Sources/Swim/Node.swift index 5d0947c..c63d506 100644 --- a/Sources/Swim/Node.swift +++ b/Sources/Swim/Node.swift @@ -1,8 +1,29 @@ import Foundation +public struct Attribute: Hashable { + public var key: String + public var value: String + + public init(_ pair: (String, String)) { + (self.key, self.value) = pair + } +} + +public struct Attributes: ExpressibleByDictionaryLiteral, Hashable { + public var attributes: [Attribute] + + public init(attributes: [Attribute]) { + self.attributes = attributes + } + + public init(dictionaryLiteral elements: (String, String)...) { + self.attributes = elements.map(Attribute.init) + } +} + public enum Node: Hashable { // The `Node`'s name, attribute and children. - indirect case element(String, [String: String], Node?) + indirect case element(String, Attributes, Node?) // The `Node`'s text contents. case text(String) @@ -49,14 +70,14 @@ extension Node: TextOutputStreamable { target.write("<") target.write(name) - for (key, value) in attributes.sorted(by: { $0 < $1 }) { + for attribute in attributes.attributes { target.write(" ") - target.write(key) + target.write(attribute.key) - guard value != "" else { continue } + guard attribute.value != "" else { continue } target.write("=\"") - target.write(value.replacingOccurrences(of: "\"", with: """)) + target.write(attribute.value.replacingOccurrences(of: "\"", with: """)) target.write("\"") } diff --git a/Sources/Swim/Visitor.swift b/Sources/Swim/Visitor.swift index e3cdcb7..1ba0339 100644 --- a/Sources/Swim/Visitor.swift +++ b/Sources/Swim/Visitor.swift @@ -3,7 +3,7 @@ import Foundation public protocol Visitor { associatedtype Result - func visitElement(name: String, attributes: [String: String], child: Node?) -> Result + func visitElement(name: String, attributes: Attributes, child: Node?) -> Result func visitText(text: String) -> Result @@ -42,7 +42,7 @@ extension Visitor { } public extension Visitor where Result == Node { - func visitElement(name: String, attributes: [String: String], child: Node?) -> Result { + func visitElement(name: String, attributes: Attributes, child: Node?) -> Result { .element(name, attributes, child.map(visitNode)) } diff --git a/Tests/HTMLTests/HTMLTests.swift b/Tests/HTMLTests/HTMLTests.swift index 674fff8..4240d4f 100644 --- a/Tests/HTMLTests/HTMLTests.swift +++ b/Tests/HTMLTests/HTMLTests.swift @@ -189,7 +189,7 @@ final class HTMLTests: XCTestCase { struct TextExtractionVisitor: Visitor { typealias Result = [String] - func visitElement(name: String, attributes: [String : String], child: Node?) -> [String] { + func visitElement(name: String, attributes: Attributes, child: Node?) -> [String] { child.map(visitNode) ?? [] } @@ -264,7 +264,7 @@ final class HTMLTests: XCTestCase { struct Sanitizer: Visitor { var denyList: [Tag] - func visitElement(name: String, attributes: [String : String], child: Node?) -> Node { + func visitElement(name: String, attributes: Attributes, child: Node?) -> Node { if denyList.contains(where: { $0.elementName == name }) { let original = Node.element(name, attributes, child)