diff --git a/Sources/HTMLStreamer/AttributedStringConverter.swift b/Sources/HTMLStreamer/AttributedStringConverter.swift
index a287c4d..9846804 100644
--- a/Sources/HTMLStreamer/AttributedStringConverter.swift
+++ b/Sources/HTMLStreamer/AttributedStringConverter.swift
@@ -27,6 +27,7 @@ public struct AttributedStringConverter {
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 {
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 {
}
}
+ if previouslyFinishedBlockElement {
+ currentRun.removeLast(2)
+ }
finishRun()
return str
@@ -111,21 +121,15 @@ public struct AttributedStringConverter {
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 {
}
}
- 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 {
finishRun()
removeLastStyle(.blockquote)
finishBlockElement()
+ case "p":
+ finishBlockElement()
case "ol":
finishRun()
removeLastStyle(.orderedList)
@@ -197,8 +196,9 @@ public struct AttributedStringConverter {
}
private mutating func finishBlockElement() {
- if str.length != 0 {
+ if !currentElementIsEmpty {
previouslyFinishedBlockElement = true
+ currentRun.append("\n\n")
}
}
@@ -251,11 +251,6 @@ public struct AttributedStringConverter {
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 = []
diff --git a/Sources/HTMLStreamer/TextConverter.swift b/Sources/HTMLStreamer/TextConverter.swift
index 3f89151..1237625 100644
--- a/Sources/HTMLStreamer/TextConverter.swift
+++ b/Sources/HTMLStreamer/TextConverter.swift
@@ -16,6 +16,7 @@ public struct TextConverter {
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 {
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 {
}
}
+ if previouslyFinishedBlockElement {
+ if configuration.insertNewlines {
+ currentRun.removeLast(2)
+ } else {
+ currentRun.removeLast(1)
+ }
+ }
finishRun()
return str
@@ -64,25 +78,11 @@ public struct TextConverter {
} 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 {
}
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 {
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 = ""
}
diff --git a/Tests/HTMLStreamerTests/AttributedStringConverterTests.swift b/Tests/HTMLStreamerTests/AttributedStringConverterTests.swift
index 3cbc26b..041ae59 100644
--- a/Tests/HTMLStreamerTests/AttributedStringConverterTests.swift
+++ b/Tests/HTMLStreamerTests/AttributedStringConverterTests.swift
@@ -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("inside
quote
after"), result)
+ }
+
}
diff --git a/Tests/HTMLStreamerTests/TextConverterTests.swift b/Tests/HTMLStreamerTests/TextConverterTests.swift
index 485aa60..78faee4 100644
--- a/Tests/HTMLStreamerTests/TextConverterTests.swift
+++ b/Tests/HTMLStreamerTests/TextConverterTests.swift
@@ -63,4 +63,8 @@ final class TextConverterTests: XCTestCase {
XCTAssertEqual(replaced, "…")
}
+ func testEmptyBlockElements() {
+ XCTAssertEqual(convert("inside
quote
after"), "inside\nquote\n\nafter")
+ }
+
}