2018-08-24 18:42:07 +02:00
|
|
|
/**
|
|
|
|
* Splash
|
|
|
|
* Copyright (c) John Sundell 2018
|
|
|
|
* MIT license - see LICENSE.md
|
|
|
|
*/
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
|
|
/// Output format to use to generate an HTML string with a semantic
|
|
|
|
/// representation of the highlighted code. Each token will be wrapped
|
|
|
|
/// in a `span` element with a CSS class matching the token's type.
|
|
|
|
/// Optionally, a `classPrefix` can be set to prefix each CSS class with
|
|
|
|
/// a given string.
|
|
|
|
public struct HTMLOutputFormat: OutputFormat {
|
|
|
|
public var classPrefix: String
|
|
|
|
|
|
|
|
public init(classPrefix: String = "") {
|
|
|
|
self.classPrefix = classPrefix
|
|
|
|
}
|
|
|
|
|
|
|
|
public func makeBuilder() -> Builder {
|
|
|
|
return Builder(classPrefix: classPrefix)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public extension HTMLOutputFormat {
|
|
|
|
struct Builder: OutputBuilder {
|
|
|
|
private let classPrefix: String
|
|
|
|
private var html = ""
|
2019-03-10 13:56:13 +01:00
|
|
|
private var pendingToken: (string: String, type: TokenType)?
|
|
|
|
private var pendingWhitespace: String?
|
2018-08-24 18:42:07 +02:00
|
|
|
|
|
|
|
fileprivate init(classPrefix: String) {
|
|
|
|
self.classPrefix = classPrefix
|
|
|
|
}
|
|
|
|
|
|
|
|
public mutating func addToken(_ token: String, ofType type: TokenType) {
|
2019-03-10 13:56:13 +01:00
|
|
|
if var pending = pendingToken {
|
|
|
|
guard pending.type != type else {
|
|
|
|
pendingWhitespace.map { pending.string += $0 }
|
|
|
|
pendingWhitespace = nil
|
|
|
|
pending.string += token
|
|
|
|
pendingToken = pending
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
appendPending()
|
|
|
|
pendingToken = (token, type)
|
2018-08-24 18:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public mutating func addPlainText(_ text: String) {
|
2019-03-10 13:56:13 +01:00
|
|
|
appendPending()
|
2018-08-27 00:14:48 +02:00
|
|
|
html.append(text.escaped)
|
2018-08-24 18:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public mutating func addWhitespace(_ whitespace: String) {
|
2019-03-10 13:56:13 +01:00
|
|
|
if pendingToken != nil {
|
|
|
|
pendingWhitespace = (pendingWhitespace ?? "") + whitespace
|
|
|
|
} else {
|
|
|
|
html.append(whitespace)
|
|
|
|
}
|
2018-08-24 18:42:07 +02:00
|
|
|
}
|
|
|
|
|
2019-03-10 13:56:13 +01:00
|
|
|
public mutating func build() -> String {
|
|
|
|
appendPending()
|
2018-08-24 18:42:07 +02:00
|
|
|
return html
|
|
|
|
}
|
2019-03-10 13:56:13 +01:00
|
|
|
|
|
|
|
private mutating func appendPending() {
|
|
|
|
if let pending = pendingToken {
|
|
|
|
html.append("""
|
|
|
|
<span class="\(classPrefix)\(pending.type.string)">\(pending.string.escaped)</span>
|
|
|
|
""")
|
|
|
|
|
|
|
|
pendingToken = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if let whitespace = pendingWhitespace {
|
|
|
|
html.append(whitespace)
|
|
|
|
pendingWhitespace = nil
|
|
|
|
}
|
|
|
|
}
|
2018-08-24 18:42:07 +02:00
|
|
|
}
|
|
|
|
}
|
2018-08-27 00:14:48 +02:00
|
|
|
|
|
|
|
private extension String {
|
|
|
|
var escaped: String {
|
|
|
|
return replacingOccurrences(of: "<", with: "<")
|
|
|
|
.replacingOccurrences(of: ">", with: ">")
|
|
|
|
}
|
|
|
|
}
|