Actually fix how whitespace is inserted between block elements

This commit is contained in:
Shadowfacts 2023-12-26 14:29:40 -05:00
parent 1ee7ab9405
commit b33be0f178
4 changed files with 53 additions and 43 deletions

View File

@ -27,6 +27,7 @@ public struct AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
private var actionStack: [ElementAction] = []
private var styleStack: [Style] = []
private var previouslyFinishedBlockElement = false
private var currentElementIsEmpty = true
// The current run of text w/o styles changing
private var currentRun: String = ""
@ -49,13 +50,19 @@ public struct AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
while let token = tokenizer.next() {
switch token {
case .character(let c):
currentElementIsEmpty = false
previouslyFinishedBlockElement = false
currentRun.unicodeScalars.append(c)
case .characterSequence(let s):
currentElementIsEmpty = false
previouslyFinishedBlockElement = false
currentRun.append(s)
case .comment:
// ignored
continue
case .startTag(let name, let selfClosing, let attributes):
currentElementIsEmpty = true
previouslyFinishedBlockElement = false
let action = Callbacks.elementAction(name: name, attributes: attributes)
actionStack.append(action)
handleStartTag(name, selfClosing: selfClosing, attributes: attributes)
@ -72,6 +79,9 @@ public struct AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
}
}
if previouslyFinishedBlockElement {
currentRun.removeLast(2)
}
finishRun()
return str
@ -111,21 +121,15 @@ public struct AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
finishRun()
styleStack.append(.monospace)
case "pre":
startBlockElement()
finishRun()
styleStack.append(.monospace)
case "blockquote":
startBlockElement()
finishRun()
styleStack.append(.blockquote)
case "p":
startBlockElement()
case "ol":
startBlockElement()
finishRun()
styleStack.append(.orderedList(nextElementOrdinal: 1))
case "ul":
startBlockElement()
finishRun()
styleStack.append(.unorderedList)
case "li":
@ -147,13 +151,6 @@ public struct AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
}
}
private mutating func startBlockElement() {
if str.length != 0 || !currentRun.isEmpty {
previouslyFinishedBlockElement = false
currentRun.append("\n\n")
}
}
private mutating func handleEndTag(_ name: String) {
switch name {
case "a":
@ -181,6 +178,8 @@ public struct AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
finishRun()
removeLastStyle(.blockquote)
finishBlockElement()
case "p":
finishBlockElement()
case "ol":
finishRun()
removeLastStyle(.orderedList)
@ -197,8 +196,9 @@ public struct AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
}
private mutating func finishBlockElement() {
if str.length != 0 {
if !currentElementIsEmpty {
previouslyFinishedBlockElement = true
currentRun.append("\n\n")
}
}
@ -251,11 +251,6 @@ public struct AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
currentRun = replacement
}
if previouslyFinishedBlockElement {
previouslyFinishedBlockElement = false
currentRun.insert(contentsOf: "\n\n", at: currentRun.startIndex)
}
var attributes = [NSAttributedString.Key: Any]()
var paragraphStyle = configuration.paragraphStyle
var currentFontTraits: FontTrait = []

View File

@ -16,6 +16,7 @@ public struct TextConverter<Callbacks: HTMLConversionCallbacks> {
private var actionStack: [ElementAction] = []
private var previouslyFinishedBlockElement = false
private var currentElementIsEmpty = true
private var currentRun = ""
public init(configuration: TextConverterConfiguration = .init()) where Callbacks == DefaultCallbacks {
@ -33,10 +34,16 @@ public struct TextConverter<Callbacks: HTMLConversionCallbacks> {
while let token = tokenizer.next() {
switch token {
case .character(let scalar):
currentElementIsEmpty = false
previouslyFinishedBlockElement = false
currentRun.unicodeScalars.append(scalar)
case .characterSequence(let string):
currentElementIsEmpty = false
previouslyFinishedBlockElement = false
currentRun.append(string)
case .startTag(let name, let selfClosing, let attributes):
currentElementIsEmpty = true
previouslyFinishedBlockElement = false
let action = Callbacks.elementAction(name: name, attributes: attributes)
actionStack.append(action)
handleStartTag(name, selfClosing: selfClosing, attributes: attributes)
@ -51,6 +58,13 @@ public struct TextConverter<Callbacks: HTMLConversionCallbacks> {
}
}
if previouslyFinishedBlockElement {
if configuration.insertNewlines {
currentRun.removeLast(2)
} else {
currentRun.removeLast(1)
}
}
finishRun()
return str
@ -64,25 +78,11 @@ public struct TextConverter<Callbacks: HTMLConversionCallbacks> {
} else {
currentRun.append(" ")
}
case "pre", "blockquote", "p", "ol", "ul":
startBlockElement()
finishRun()
default:
break
}
}
private mutating func startBlockElement() {
if !str.isEmpty {
previouslyFinishedBlockElement = false
if configuration.insertNewlines {
currentRun.append("\n\n")
} else {
currentRun.append(" ")
}
}
}
private mutating func handleEndTag(_ name: String) {
switch name {
case "pre", "blockquote", "p", "ol", "ul":
@ -94,8 +94,13 @@ public struct TextConverter<Callbacks: HTMLConversionCallbacks> {
}
private mutating func finishBlockElement() {
if !str.isEmpty {
if !currentElementIsEmpty {
previouslyFinishedBlockElement = true
if configuration.insertNewlines {
currentRun.append("\n\n")
} else {
currentRun.append(" ")
}
}
}
@ -111,15 +116,6 @@ public struct TextConverter<Callbacks: HTMLConversionCallbacks> {
currentRun = replacement
}
if previouslyFinishedBlockElement {
previouslyFinishedBlockElement = false
if configuration.insertNewlines {
currentRun.insert(contentsOf: "\n\n", at: currentRun.startIndex)
} else {
currentRun.insert(" ", at: currentRun.startIndex)
}
}
str.append(currentRun)
currentRun = ""
}

View File

@ -290,4 +290,19 @@ final class AttributedStringConverterTests: XCTestCase {
]))
}
func testEmptyBlockElements() {
let result = NSMutableAttributedString()
result.append(NSAttributedString(string: "inside\nquote", attributes: [
.font: italicFont,
.paragraphStyle: blockquoteParagraphStyle,
.foregroundColor: color,
]))
result.append(NSAttributedString(string: "\n\nafter", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
XCTAssertEqual(convert("<p></p><blockquote><span>inside<br>quote</span></blockquote><span>after</span><p></p>"), result)
}
}

View File

@ -63,4 +63,8 @@ final class TextConverterTests: XCTestCase {
XCTAssertEqual(replaced, "")
}
func testEmptyBlockElements() {
XCTAssertEqual(convert("<p></p><blockquote><span>inside<br>quote</span></blockquote><span>after</span><p></p>"), "inside\nquote\n\nafter")
}
}