HTMLOutputFormat: Merge same-type tokens along with whitespace
This change makes Splash merge tokens of the same type (along with any whitespace in between them) when generating HTML. The result is much smaller HTML, since less tags have to be used to produce the same result. This was most obvious with comment highlighting, for example, this comment: ``` // Hello I’m a comment ``` Would generate 5 different <span class="comment"></span> elements. Now it’s just one!
This commit is contained in:
parent
59161f2724
commit
6dff287cd2
@ -27,26 +27,60 @@ public extension HTMLOutputFormat {
|
|||||||
struct Builder: OutputBuilder {
|
struct Builder: OutputBuilder {
|
||||||
private let classPrefix: String
|
private let classPrefix: String
|
||||||
private var html = ""
|
private var html = ""
|
||||||
|
private var pendingToken: (string: String, type: TokenType)?
|
||||||
|
private var pendingWhitespace: String?
|
||||||
|
|
||||||
fileprivate init(classPrefix: String) {
|
fileprivate init(classPrefix: String) {
|
||||||
self.classPrefix = classPrefix
|
self.classPrefix = classPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
public mutating func addToken(_ token: String, ofType type: TokenType) {
|
public mutating func addToken(_ token: String, ofType type: TokenType) {
|
||||||
html.append("<span class=\"\(classPrefix)\(type.string)\">\(token.escaped)</span>")
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
public mutating func addPlainText(_ text: String) {
|
public mutating func addPlainText(_ text: String) {
|
||||||
|
appendPending()
|
||||||
html.append(text.escaped)
|
html.append(text.escaped)
|
||||||
}
|
}
|
||||||
|
|
||||||
public mutating func addWhitespace(_ whitespace: String) {
|
public mutating func addWhitespace(_ whitespace: String) {
|
||||||
|
if pendingToken != nil {
|
||||||
|
pendingWhitespace = (pendingWhitespace ?? "") + whitespace
|
||||||
|
} else {
|
||||||
html.append(whitespace)
|
html.append(whitespace)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func build() -> String {
|
public mutating func build() -> String {
|
||||||
|
appendPending()
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,5 +21,5 @@ public protocol OutputBuilder {
|
|||||||
/// Add some whitespace to the builder
|
/// Add some whitespace to the builder
|
||||||
mutating func addWhitespace(_ whitespace: String)
|
mutating func addWhitespace(_ whitespace: String)
|
||||||
/// Build the final output based on the builder's current state
|
/// Build the final output based on the builder's current state
|
||||||
func build() -> Output
|
mutating func build() -> Output
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,20 @@ final class HTMLOutputFormatTests: SplashTestCase {
|
|||||||
highlighter = SyntaxHighlighter(format: HTMLOutputFormat())
|
highlighter = SyntaxHighlighter(format: HTMLOutputFormat())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBasicGeneration() {
|
||||||
|
let html = highlighter.highlight("""
|
||||||
|
public struct Test: SomeProtocol {
|
||||||
|
func hello() -> Int { return 7 }
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
XCTAssertEqual(html, """
|
||||||
|
<span class="keyword">public struct</span> Test: <span class="type">SomeProtocol</span> {
|
||||||
|
<span class="keyword">func</span> hello() -> <span class="type">Int</span> { <span class="keyword">return</span> <span class="number">7</span> }
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
func testStrippingGreaterAndLessThanCharactersFromOutput() {
|
func testStrippingGreaterAndLessThanCharactersFromOutput() {
|
||||||
let html = highlighter.highlight("Array<String>")
|
let html = highlighter.highlight("Array<String>")
|
||||||
|
|
||||||
@ -18,6 +32,14 @@ final class HTMLOutputFormatTests: SplashTestCase {
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCommentMerging() {
|
||||||
|
let html = highlighter.highlight("// Hey I'm a comment!")
|
||||||
|
|
||||||
|
XCTAssertEqual(html, """
|
||||||
|
<span class="comment">// Hey I'm a comment!</span>
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
func testAllTestsRunOnLinux() {
|
func testAllTestsRunOnLinux() {
|
||||||
XCTAssertTrue(TestCaseVerifier.verifyLinuxTests((type(of: self)).allTests))
|
XCTAssertTrue(TestCaseVerifier.verifyLinuxTests((type(of: self)).allTests))
|
||||||
}
|
}
|
||||||
@ -26,7 +48,9 @@ final class HTMLOutputFormatTests: SplashTestCase {
|
|||||||
extension HTMLOutputFormatTests {
|
extension HTMLOutputFormatTests {
|
||||||
static var allTests: [(String, TestClosure<HTMLOutputFormatTests>)] {
|
static var allTests: [(String, TestClosure<HTMLOutputFormatTests>)] {
|
||||||
return [
|
return [
|
||||||
("testStrippingGreaterAndLessThanCharactersFromOutput", testStrippingGreaterAndLessThanCharactersFromOutput)
|
("testBasicGeneration", testBasicGeneration),
|
||||||
|
("testStrippingGreaterAndLessThanCharactersFromOutput", testStrippingGreaterAndLessThanCharactersFromOutput),
|
||||||
|
("testCommentMerging", testCommentMerging)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user