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:
John Sundell 2019-03-10 13:56:13 +01:00
parent 59161f2724
commit 6dff287cd2
3 changed files with 63 additions and 5 deletions

View File

@ -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
}
}
} }
} }

View File

@ -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
} }

View File

@ -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() -&gt; <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)
] ]
} }
} }