import Foundation struct PortMappingXML: Equatable { var id: String var namespace: String var target: String var address: String var hostPort: String var servicePort: String var proto: String var description: String } enum PortMappingsXMLStore { private static func configURL() -> URL { // Determine PROLE_HOME similarly to PFScriptBridge let env = ProcessInfo.processInfo.environment if let home = env["PROLE_HOME"], !home.isEmpty { return URL(fileURLWithPath: home).appendingPathComponent("conf/port-mappings.properties") } let defaultHome = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("dev/prole") return defaultHome.appendingPathComponent("conf/port-mappings.properties") } static func load() -> [PortMappingXML] { let url = configURL() guard let data = try? Data(contentsOf: url) else { return [] } let parser = XMLParser(data: data) let delegate = ParserDelegate() parser.delegate = delegate if parser.parse() { return delegate.items } return [] } static func save(_ items: [PortMappingXML]) throws { let url = configURL() var xml = """ """ for m in items { xml += " \n" } xml += """ """ try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true) try xml.data(using: .utf8)?.write(to: url) } private static func escape(_ s: String) -> String { return s.replacingOccurrences(of: "&", with: "&") .replacingOccurrences(of: "\"", with: """) .replacingOccurrences(of: "<", with: "<") .replacingOccurrences(of: ">", with: ">") } private final class ParserDelegate: NSObject, XMLParserDelegate { var items: [PortMappingXML] = [] func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { if elementName == "mapping" { let m = PortMappingXML( id: attributeDict["id"] ?? "", namespace: attributeDict["namespace"] ?? "default", target: attributeDict["target"] ?? "", address: attributeDict["address"] ?? "127.0.0.1", hostPort: attributeDict["hostPort"] ?? "", servicePort: attributeDict["servicePort"] ?? "", proto: attributeDict["protocol"] ?? "TCP", description: attributeDict["description"] ?? "" ) items.append(m) } } } }