import Foundation /// A parsed `NAME;PARAM=value:VALUE` content line. /// /// vCard and iCalendar share this grammar, so both sanitizers parse through /// this type. struct PropertyLine { /// Apple exports group related properties as `item1.EMAIL` / `item1.X-ABLabel`. /// The group is parsed off and never re-emitted — click-wheel parsers do not /// understand grouping, and the labels it points at are `X-` properties that /// get dropped anyway. var name: String var parameters: [String] var value: String init?(_ line: String) { guard let colon = line.firstIndex(of: ":") else { return nil } let head = String(line[line.startIndex.. String? { let prefix = name.uppercased() + "=" for parameter in parameters where parameter.uppercased().hasPrefix(prefix) { return String(parameter.dropFirst(prefix.count)) } return nil } mutating func removeParameters(named names: [String]) { let prefixes = names.map { $0.uppercased() + "=" } parameters.removeAll { parameter in prefixes.contains { parameter.uppercased().hasPrefix($0) } } } var rendered: String { let head = ([name] + parameters).joined(separator: ";") return "\(head):\(value)" } } extension PropertyLine { /// Unescapes a TEXT value: `\n` to a newline, `\;` `\,` `\\` to their literals. static func unescape(_ value: String) -> String { var output = "" var escaped = false for character in value { if escaped { switch character { case "n", "N": output.append("\n") default: output.append(character) } escaped = false } else if character == "\\" { escaped = true } else { output.append(character) } } return output } }