// // RDF-Extensions.swift // Diomede // // Created by Gregory Todd Williams on 5/23/20. // import Foundation #if os(macOS) import Compression #endif import CryptoSwift import SPARQLSyntax import Diomede extension Data { #if os(macOS) func uncompressed(size outputDataSize: Int) throws -> Data { let compressedSize = self.count let destinationBuffer = UnsafeMutablePointer.allocate(capacity: outputDataSize) var sourceBuffer = Array(repeating: 0, count: compressedSize) self.copyBytes(to: &sourceBuffer, count: compressedSize) let decodedSize = compression_decode_buffer(destinationBuffer, outputDataSize, &sourceBuffer, compressedSize, nil, COMPRESSION_ZLIB) guard decodedSize == outputDataSize else { destinationBuffer.deallocate() throw DiomedeError.encodingError } return Data(bytesNoCopy: destinationBuffer, count: outputDataSize, deallocator: .free) } func compressed60() -> Data? { guard self.count > 100 else { return nil } let inputDataSize = self.count let byteSize = MemoryLayout.stride let bufferSize = inputDataSize / byteSize let destinationBuffer = UnsafeMutablePointer.allocate(capacity: bufferSize) var sourceBuffer = Array(repeating: 0, count: bufferSize) self.copyBytes(to: &sourceBuffer, count: inputDataSize) let compressedSize = compression_encode_buffer(destinationBuffer, inputDataSize, &sourceBuffer, inputDataSize, nil, COMPRESSION_ZLIB) let ratio = Double(compressedSize) / Double(inputDataSize) if ratio < 0.60 { // print("compression: \(compressedSize) / \(inputDataSize) (\(100.0 * ratio)%)") return Data(bytesNoCopy: destinationBuffer, count: compressedSize, deallocator: .free) } return nil } #endif func uuid() throws -> UUID { guard self.count == 16 else { throw DiomedeError.encodingError } var uu : uuid_t = UUID().uuid Swift.withUnsafeMutableBytes(of: &uu) { (ptr) -> () in self.copyBytes(to: ptr, count: 16) } return UUID(uuid: uu) } } extension Term: DataEncodable { public func sha256() throws -> Data { let d = try self.asData() let term_key = d.sha256() return term_key } public func asData() throws -> Data { let s: String switch self.type { case .blank: if let u = UUID(uuidString: self.value), self.value.count == 36 { var uu = u.uuid let du = Data(bytes: &uu, count: 16) let d = try "u".asData() return d + du } s = "B\"" + self.value case .iri: if self.value.count == 45 && self.value.hasPrefix("urn:uuid:") { let suffix = String(self.value.dropFirst(9)) if let u = UUID(uuidString: suffix) { var uu = u.uuid let du = Data(bytes: &uu, count: 16) let d = try "U".asData() return d + du } } s = "I\"" + self.value case .language(let lang): s = "L\(lang)\"" + self.value case .datatype(let dt): switch dt { case .string: // the Compression framework is not cross-platform, so this isn't supported currently // if let inputData = self.value.data(using: .utf8), let cd = inputData.compressed60() { // let d = "Z".data(using: .utf8)! // let s = inputData.count.asData() // return d + s + cd // } s = "S\"" + self.value case .integer: s = "i\"" + self.value default: s = "D\(dt.value)\"" + self.value } } return try s.asData() } public static func fromData(_ data: Data) throws -> Term { guard data.count >= 2 else { throw DiomedeError.encodingError } let c = String(UnicodeScalar(data[0])) switch c { // these are encodings which do not have a DOUBLE QUOTE followed by a string value case "U": let bytes = Data(data.dropFirst()) let uu = try bytes.uuid() return Term(iri: "urn:uuid:\(uu.uuidString.lowercased())") case "u": let bytes = Data(data.dropFirst()) let uu = try bytes.uuid() return Term(value: uu.uuidString.uppercased(), type: .blank) // the Compression framework is not cross-platform, so this isn't supported currently // case "Z": // let bytes = Data(data.dropFirst()) // let size = Int.fromData(bytes) // let buffer = bytes.dropFirst(8) // let data = try buffer.uncompressed(size: size) // guard let string = String(data: data, encoding: .utf8) else { // throw DiomedeError.encodingError // } // return Term(string: string) default: break } let s = try String.fromData(data) guard let i = s.firstIndex(of: "\"") else { throw DiomedeError.encodingError } let value = String(s.suffix(from: s.index(after: i))) switch c { case "B": return Term(value: value, type: .blank) case "I": return Term(value: value, type: .iri) case "L": let lang = String(s.dropFirst().prefix(while: { $0 != "\"" })) return Term(value: value, type: .language(lang)) case "S": return Term(value: value, type: .datatype(.string)) case "i": return Term(value: value, type: .datatype(.integer)) case "D": let dtvalue = String(s.dropFirst(1).prefix(while: { $0 != "\"" })) let dt = TermDataType(stringLiteral: dtvalue) return Term(value: value, type: .datatype(dt)) default: throw DiomedeError.unknownError } } }