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 {
private let classPrefix: String
private var html = ""
private var pendingToken: (string: String, type: TokenType)?
private var pendingWhitespace: String?
fileprivate init(classPrefix: String) {
self.classPrefix = classPrefix
}
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) {
appendPending()
html.append(text.escaped)
}
public mutating func addWhitespace(_ whitespace: String) {
html.append(whitespace)
if pendingToken != nil {
pendingWhitespace = (pendingWhitespace ?? "") + whitespace
} else {
html.append(whitespace)
}
}
public func build() -> String {
public mutating func build() -> String {
appendPending()
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
mutating func addWhitespace(_ whitespace: String)
/// 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())
}
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() {
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() {
XCTAssertTrue(TestCaseVerifier.verifyLinuxTests((type(of: self)).allTests))
}
@ -26,7 +48,9 @@ final class HTMLOutputFormatTests: SplashTestCase {
extension HTMLOutputFormatTests {
static var allTests: [(String, TestClosure<HTMLOutputFormatTests>)] {
return [
("testStrippingGreaterAndLessThanCharactersFromOutput", testStrippingGreaterAndLessThanCharactersFromOutput)
("testBasicGeneration", testBasicGeneration),
("testStrippingGreaterAndLessThanCharactersFromOutput", testStrippingGreaterAndLessThanCharactersFromOutput),
("testCommentMerging", testCommentMerging)
]
}
}