// // BaseComponents.swift // HRW // // Created by Isaac Paul on 10/15/24. // Non-commercial license, see LICENSE.MD in the project root for details // import System import Foundation public protocol IFlowContent : HTMLNode {} public protocol IGlobalContainer { var globalAttributes:Dictionary { get set } var globalEvents:Dictionary { get set } var dataAttributes:Dictionary { 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[.. = [:] public var globalEvents:Dictionary = [:] public var dataAttributes:Dictionary = [:] public init(globalAttributes: Dictionary, globalEvents: Dictionary, dataAttributes: Dictionary) { 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 protocol IHtmlNodeContainer { var rootNodeGeneric: HTMLNode { get } } public class IHtmlNodeContainerUtility { nonisolated(unsafe) public static let sharedInstance = IHtmlNodeContainerUtility() public var defaultBaseDir = "" public static func readHtmlFromFile(_ baseDir:String? = nil, _ fileName:String) throws -> [HTMLNode] { let dir = baseDir ?? sharedInstance.defaultBaseDir let strFp = "file://\(dir)/\(fileName)" guard let url = URL(string:strFp) else { throw AppError("String not valid url: \(strFp)") } let data = try Foundation.Data(contentsOf: url) guard let str = String(data: data, encoding: .utf8) else { throw AppError("Data not utf8") } /* let fp = FilePath("\(strFp)") guard let str = String(validating: fp) else { throw AppError("File not found at path: \(strFp)") } if str == strFp { throw AppError("File not found at path: \(str)") }*/ guard let xmlReader = XMLParser(str: str) else { throw AppError("Empty String") } let rootNodes = try xmlReader.readObjects() return rootNodes } } public class HTMLNode : XMLNode, IGlobalContainer { public var globalAttributes:Dictionary = [:] public var globalEvents:Dictionary = [:] 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[.., globalEvents:Dictionary, dataAttributes:Dictionary) { 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, 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 += "" } return (newDepth, result) } } public enum SpacingStrat { case tabs case spaces(num:Int) } public class GenericXMLNode : XMLNode { public var attributes:Dictionary = [:] 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 = [:] 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[.. 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[.. NHTMLRenderable? { if let result = super.findById(id) { return result } for eachChild in children { if let result = eachChild.findById(id) { return result } } return nil } }*/