396 lines
11 KiB
Swift
396 lines
11 KiB
Swift
//
|
|
// BaseComponents.swift
|
|
// HRW
|
|
//
|
|
// Created by Isaac Paul on 10/15/24.
|
|
// Non-commercial license, see LICENSE.MD in the project root for details
|
|
//
|
|
|
|
public protocol IFlowContent : HTMLNode {}
|
|
|
|
public protocol IGlobalContainer {
|
|
var globalAttributes:Dictionary<GlobalAttributeKey, String> { get set }
|
|
var globalEvents:Dictionary<GlobalEventKey, String> { get set }
|
|
var dataAttributes:Dictionary<String, String> { get set }
|
|
}
|
|
|
|
extension IGlobalContainer {
|
|
mutating func trySetGlobalAttribute(_ key:String, _ value:String) -> Bool {
|
|
if let attr = GlobalAttributeKey(rawValue: key) {
|
|
globalAttributes.updateValue(value, forKey: attr)
|
|
return true
|
|
}
|
|
if let attr = GlobalEventKey(rawValue: key.asSubstring()) {
|
|
globalEvents[attr] = value
|
|
return true
|
|
}
|
|
if key[..<key.index(key.startIndex, offsetBy: 5)] == "data-" {
|
|
dataAttributes[key] = value
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
public class NParent : NRenderable {
|
|
public var children:[NRenderable] = []
|
|
|
|
public override init() {
|
|
super.init()
|
|
children = []
|
|
}
|
|
}
|
|
|
|
public class NRenderable {
|
|
public var parent:NParent? = nil
|
|
}
|
|
|
|
public class NNone: NRenderable { }
|
|
|
|
public struct GlobalAttributesBuilder : IGlobalContainer{
|
|
public var globalAttributes:Dictionary<GlobalAttributeKey, String> = [:]
|
|
public var globalEvents:Dictionary<GlobalEventKey, String> = [:]
|
|
public var dataAttributes:Dictionary<String, String> = [:]
|
|
|
|
public init(globalAttributes: Dictionary<GlobalAttributeKey, String>, globalEvents: Dictionary<GlobalEventKey, String>, dataAttributes: Dictionary<String, String>) {
|
|
self.globalAttributes = globalAttributes
|
|
self.globalEvents = globalEvents
|
|
self.dataAttributes = dataAttributes
|
|
}
|
|
|
|
public init() {
|
|
self.globalAttributes = [:]
|
|
self.globalEvents = [:]
|
|
self.dataAttributes = [:]
|
|
}
|
|
|
|
public init(_ attributes: [String: String]) throws {
|
|
self.globalAttributes = [:]
|
|
self.globalEvents = [:]
|
|
self.dataAttributes = [:]
|
|
for (key, value) in attributes {
|
|
if self.trySetGlobalAttribute(key, value) {
|
|
continue
|
|
}
|
|
throw AppError("Unexpected attribute: \(key)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// The problem is pausing and resuming so this is a n-squared search, but is it faster than make allocations? do we even need this?
|
|
/*
|
|
public struct HtmlIterator : IteratorProtocol {
|
|
public init(src: NHTMLRenderable) {
|
|
self._src = src
|
|
}
|
|
|
|
public typealias Element = NHTMLRenderable
|
|
|
|
private let _src:NHTMLRenderable
|
|
private var _index:Int = 0
|
|
|
|
public mutating func next() -> NHTMLRenderable? {
|
|
let (index, result) = _src.renderableAtIndex(_index)
|
|
_index += 1
|
|
return result
|
|
}
|
|
}*/
|
|
|
|
public class HTMLNode : XMLNode, IGlobalContainer {
|
|
|
|
public var globalAttributes:Dictionary<GlobalAttributeKey, String> = [:]
|
|
public var globalEvents:Dictionary<GlobalEventKey, String> = [:]
|
|
|
|
public var children:[HTMLNode] = []
|
|
|
|
public init(expectedAttributes:[String:String]) throws {
|
|
super.init()
|
|
for (key, value) in expectedAttributes {
|
|
if let attr = GlobalAttributeKey(rawValue: key) {
|
|
globalAttributes.updateValue(value, forKey: attr)
|
|
continue
|
|
}
|
|
if let attr = GlobalEventKey(rawValue: key.asSubstring()) {
|
|
globalEvents[attr] = value
|
|
continue
|
|
}
|
|
if key[..<key.index(key.startIndex, offsetBy: 5)] == "data-" {
|
|
dataAttributes[key] = value
|
|
continue
|
|
}
|
|
throw AppError("Unexpected attribute: \(key)")
|
|
}
|
|
}
|
|
|
|
public init(globalAttributes:Dictionary<GlobalAttributeKey, String>, globalEvents:Dictionary<GlobalEventKey, String>, dataAttributes:Dictionary<String, String>) {
|
|
self.globalAttributes = globalAttributes
|
|
self.globalEvents = globalEvents
|
|
super.init(dataAttributes: dataAttributes)
|
|
}
|
|
|
|
public init(_ builder:GlobalAttributesBuilder, _ children:[HTMLNode] = []) {
|
|
self.globalAttributes = builder.globalAttributes
|
|
self.globalEvents = builder.globalEvents
|
|
self.children = children
|
|
super.init(dataAttributes: builder.dataAttributes)
|
|
}
|
|
|
|
public func findById(_ id:String) -> HTMLNode? {
|
|
if (globalAttributes[.id] == id) {
|
|
return self
|
|
}
|
|
for eachChild in children {
|
|
if let result = eachChild.findById(id) {
|
|
return result
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func iterate(_ index:Int, _ skipTextNodes:Bool, _ cb:(HTMLNode, Int)->()) -> Int {
|
|
if (skipTextNodes && self is HTMLText) {
|
|
return index
|
|
}
|
|
cb(self, index)
|
|
var newIndex = index + 1
|
|
for eachChild in children {
|
|
newIndex = eachChild.iterate(newIndex, skipTextNodes, cb)
|
|
}
|
|
return newIndex
|
|
}
|
|
|
|
public func flatten() -> [HTMLNode] {
|
|
let count = countElements()
|
|
let result:[HTMLNode] = Array(unsafeUninitializedCapacity: count+1) { buffer, initializedCount in
|
|
initializedCount = self.addTo(array: &buffer, index: 0)
|
|
}
|
|
return result
|
|
}
|
|
|
|
public func countElements() -> Int {
|
|
var result = 1
|
|
for eachChild in children {
|
|
result += eachChild.countElements()
|
|
}
|
|
return result
|
|
}
|
|
|
|
private func addTo(array:inout UnsafeMutableBufferPointer<HTMLNode>, index:Int) -> Int {
|
|
array.initializeElement(at: index, to: self)
|
|
var newIndex = index + 1
|
|
for eachChild in children {
|
|
newIndex = eachChild.addTo(array: &array, index: newIndex)
|
|
}
|
|
return newIndex
|
|
}
|
|
|
|
public override func renderAttributes() -> String {
|
|
var result = super.renderAttributes()
|
|
var first = result.count == 0
|
|
for eachAttr in globalAttributes {
|
|
if (!first) {
|
|
result += " "
|
|
}
|
|
first = false
|
|
if (eachAttr.value.count > 0) {
|
|
result += "\(eachAttr.key)='\(eachAttr.value)'"
|
|
} else {
|
|
result += "\(eachAttr.key)"
|
|
}
|
|
}
|
|
|
|
for eachAttr in globalEvents {
|
|
if (!first) {
|
|
result += " "
|
|
}
|
|
first = false
|
|
if (eachAttr.value.count > 0) {
|
|
result += "\(eachAttr.key) = \(eachAttr.value)"
|
|
} else {
|
|
result += "\(eachAttr.key)"
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
override var nodeName: String {
|
|
return "HTML"
|
|
}
|
|
|
|
override var isVoidElement: Bool {
|
|
return false
|
|
}
|
|
|
|
override public func toString(_ depth:Int = 0, spacingStrat:SpacingStrat = .tabs) -> (Int, String) {
|
|
var newDepth = depth
|
|
var result = renderTag()
|
|
if (!isVoidElement) {
|
|
for eachChild in children {
|
|
let (nextDepth, renderedChild) = eachChild.toString(newDepth, spacingStrat: spacingStrat)
|
|
newDepth = nextDepth
|
|
result += renderedChild
|
|
}
|
|
result += "<\(nodeName)/>"
|
|
}
|
|
return (newDepth, result)
|
|
}
|
|
}
|
|
|
|
public enum SpacingStrat {
|
|
case tabs
|
|
case spaces(num:Int)
|
|
}
|
|
|
|
public class GenericXMLNode : XMLNode {
|
|
|
|
public var attributes:Dictionary<String, String> = [:]
|
|
public var children:[XMLNode] = []
|
|
public var name:String
|
|
|
|
public init(_ name:String, _ attributes:[String:String], _ children:[XMLNode] = []) {
|
|
self.attributes = attributes
|
|
self.children = children
|
|
self.name = name
|
|
super.init(attributes)
|
|
}
|
|
|
|
public init(_ name:String, _ attributes:[String:String], _ parser:XMLParser? = nil) throws {
|
|
self.attributes = attributes
|
|
self.name = name
|
|
super.init(attributes)
|
|
|
|
|
|
var allItems:[XMLNode] = []
|
|
while let obj = try parser?.readObjectXml(endTag: "a") {
|
|
allItems.append(obj)
|
|
}
|
|
children = allItems
|
|
}
|
|
}
|
|
|
|
public class XMLNode { //Not sure if should be a parent class or just protocol
|
|
|
|
public var dataAttributes:Dictionary<String, String> = [:]
|
|
public var parent:XMLNode? = nil
|
|
var nodeName: String {
|
|
return ""
|
|
}
|
|
var isVoidElement: Bool {
|
|
return true
|
|
}
|
|
/*
|
|
public func it() -> HtmlIterator {
|
|
return HtmlIterator(src: self)
|
|
}*/
|
|
|
|
public init() {
|
|
|
|
}
|
|
|
|
public init(_ attributes:[String:String]) {
|
|
for (key, value) in attributes {
|
|
if key[..<key.index(key.startIndex, offsetBy: 5)] == "data-" {
|
|
dataAttributes[key] = value
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
public init(dataAttributes:[String:String]) {
|
|
self.dataAttributes = dataAttributes
|
|
}
|
|
|
|
public func renderAttributes() -> String {
|
|
var result = ""
|
|
var first = true
|
|
for eachAttr in dataAttributes {
|
|
if (!first) {
|
|
result += " "
|
|
}
|
|
first = false
|
|
if (eachAttr.value.count > 0) {
|
|
result += "\(eachAttr.key) = \(eachAttr.value)"
|
|
} else {
|
|
result += "\(eachAttr.key)"
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
/*
|
|
public func renderableAtIndex(_ index:Int) -> (Int, NHTMLRenderable?) {
|
|
if (index == 0) {
|
|
return (-1, self)
|
|
}
|
|
var nextIndex = index - 1
|
|
for eachChild in children {
|
|
let (index, result) = eachChild.renderableAtIndex(nextIndex)
|
|
if let result = result {
|
|
return (-1, result)
|
|
}
|
|
nextIndex = index
|
|
}
|
|
return (nextIndex, nil)
|
|
}*/
|
|
|
|
public func toString(_ depth:Int = 0, spacingStrat:SpacingStrat = .tabs) -> (Int, String) {
|
|
var newDepth = depth
|
|
var result = "<\(nodeName) \(renderAttributes())"
|
|
if (isVoidElement) {
|
|
result += "/>"
|
|
} else {
|
|
result += ">"
|
|
}
|
|
return (newDepth, result)
|
|
}
|
|
|
|
public func renderTag() -> String {
|
|
let closing = isVoidElement ? "/" : ""
|
|
let attributes = renderAttributes()
|
|
if (attributes.isEmpty) {
|
|
let result = "<\(nodeName)\(closing)>"
|
|
return result
|
|
} else {
|
|
let result = "<\(nodeName) \(renderAttributes()) \(closing)>"
|
|
return result
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func isGlobalHTMLAttribute(_ key:String) -> Bool {
|
|
if let _ = GlobalAttributeKey(rawValue: key) {
|
|
return true
|
|
}
|
|
if let _ = GlobalEventKey(rawValue: key.asSubstring()) {
|
|
return true
|
|
}
|
|
if key[..<key.index(key.startIndex, offsetBy: 5)] == "data-" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
public protocol IHTMLParent : HTMLNode {
|
|
var childrenAny:[HTMLNode] { get set }
|
|
}
|
|
|
|
/*
|
|
public class NHTMLParent : NHTMLRenderable {
|
|
public var children:[NHTMLRenderable] { get {} set {} }
|
|
|
|
override open func findById(_ id:String) -> NHTMLRenderable? {
|
|
if let result = super.findById(id) {
|
|
return result
|
|
}
|
|
for eachChild in children {
|
|
if let result = eachChild.findById(id) {
|
|
return result
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}*/
|
|
|