import Foundation import Splash @_cdecl("highlight_swift") public func highlight(codePtr: UnsafePointer, codeLen: UInt64, htmlLenPtr: UnsafeMutablePointer) -> UnsafeMutablePointer { let buf = UnsafeBufferPointer(start: codePtr, count: Int(codeLen)) let data = Data(buffer: buf) let code = String(data: data, encoding: .utf8)! let highligher = SyntaxHighlighter(format: MyOutputFormat()) var html = highligher.highlight(code) let outPtr = UnsafeMutableBufferPointer.allocate(capacity: html.utf8.count) _ = html.withUTF8 { buf in buf.copyBytes(to: outPtr, count: buf.count) } htmlLenPtr.pointee = UInt64(outPtr.count) return outPtr.baseAddress! } struct MyOutputFormat: OutputFormat { func makeBuilder() -> Builder { return Builder() } struct Builder: OutputBuilder { typealias Output = String private var html = "" private var pendingToken: (string: String, type: TokenType)? private var pendingWhitespace: String? mutating func addToken(_ token: String, ofType type: TokenType) { if var pendingToken = pendingToken { guard pendingToken.type != type else { pendingWhitespace.map { pendingToken.string += $0 } pendingWhitespace = nil pendingToken.string += token self.pendingToken = pendingToken return } } appendPending() pendingToken = (token, type) } mutating func addPlainText(_ text: String) { appendPending() html.append(contentsOf: text.escapingHTMLEntities()) } mutating func addWhitespace(_ whitespace: String) { if pendingToken != nil { pendingWhitespace = (pendingWhitespace ?? "") + whitespace } else { html.append(whitespace) } } mutating func build() -> String { appendPending() return html } private mutating func appendPending() { if let pendingToken = pendingToken { let cls: String switch pendingToken.type { case .keyword: cls = "hl-kw" case .string: cls = "hl-str" case .type: cls = "hl-type" case .call: cls = "hl-fn" case .number: cls = "hl-num" case .comment: cls = "hl-cmt" case .property: cls = "hl-var" case .dotAccess: cls = "hl-prop" case .preprocessing: cls = "" case .custom(_): cls = "" } html.append(""" \(pendingToken.string.escapingHTMLEntities()) """) self.pendingToken = nil } if let pendingWhitespace = pendingWhitespace { html.append(pendingWhitespace) self.pendingWhitespace = nil } } } } // copied from Splash, where it's internal extension StringProtocol { func escapingHTMLEntities() -> String { return String(flatMap { character -> String in switch character { case "&": return "&" case "<": return "<" case ">": return ">" default: return String(character) } }) } }