Compare commits

..

No commits in common. "3c9a5eb0caa15764b3cd7f4876c454bccfe60a69" and "9c8b127f152c9a7a43019703a5fc2eaa9e18f941" have entirely different histories.

5 changed files with 27 additions and 90 deletions

View File

@ -1,5 +1,6 @@
digraph blockstate { digraph blockstate {
/* rankdir=LR; */ /* rankdir=LR; */
node [shape = doublecircle, fontsize = 18]; end;
node [shape = circle, fontsize = 18]; node [shape = circle, fontsize = 18];
edge [fontsize = 18]; edge [fontsize = 18];
init [label = "", shape=none, height = .0, width = .0]; init [label = "", shape=none, height = .0, width = .0];

View File

@ -24,19 +24,7 @@ public class AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
private var tokenizer: Tokenizer<String.UnicodeScalarView.Iterator>! private var tokenizer: Tokenizer<String.UnicodeScalarView.Iterator>!
private var str: NSMutableAttributedString! private var str: NSMutableAttributedString!
private var actionStack: [ElementAction] = [] { private var actionStack: [ElementAction] = []
didSet {
hasSkipOrReplaceElementAction = actionStack.contains(where: {
switch $0 {
case .skip, .replace(_):
true
default:
false
}
})
}
}
private var hasSkipOrReplaceElementAction = false
private var styleStack: [Style] = [] private var styleStack: [Style] = []
private var blockStateMachine = BlockStateMachine(blockBreak: "", lineBreak: "", listIndentForContentOutsideItem: "", append: { _ in }, removeChar: {}) private var blockStateMachine = BlockStateMachine(blockBreak: "", lineBreak: "", listIndentForContentOutsideItem: "", append: { _ in }, removeChar: {})
private var currentElementIsEmpty = true private var currentElementIsEmpty = true
@ -71,15 +59,13 @@ public class AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
switch token { switch token {
case .character(let c): case .character(let c):
currentElementIsEmpty = false currentElementIsEmpty = false
if blockStateMachine.continueBlock(char: c), if blockStateMachine.continueBlock(char: c) {
!hasSkipOrReplaceElementAction {
currentRun.unicodeScalars.append(c) currentRun.unicodeScalars.append(c)
} }
case .characterSequence(let s): case .characterSequence(let s):
currentElementIsEmpty = false currentElementIsEmpty = false
for c in s.unicodeScalars { for c in s.unicodeScalars {
if blockStateMachine.continueBlock(char: c), if blockStateMachine.continueBlock(char: c) {
!hasSkipOrReplaceElementAction {
currentRun.unicodeScalars.append(c) currentRun.unicodeScalars.append(c)
} }
} }
@ -295,10 +281,13 @@ public class AttributedStringConverter<Callbacks: HTMLConversionCallbacks> {
}() }()
private func finishRun() { private func finishRun() {
if case .append(let s) = actionStack.last { if actionStack.contains(.skip) {
currentRun = ""
return
} else if case .append(let s) = actionStack.last {
currentRun.append(s) currentRun.append(s)
} else if case .replace(let replacement) = actionStack.last { } else if case .replace(let replacement) = actionStack.first(where: \.isReplace) {
currentRun.append(replacement) currentRun = replacement
} }
guard !currentRun.isEmpty else { guard !currentRun.isEmpty else {

View File

@ -17,6 +17,14 @@ public enum ElementAction: Equatable {
case skip case skip
case replace(String) case replace(String)
case append(String) case append(String)
var isReplace: Bool {
if case .replace(_) = self {
true
} else {
false
}
}
} }
public extension HTMLConversionCallbacks { public extension HTMLConversionCallbacks {

View File

@ -13,19 +13,7 @@ public class TextConverter<Callbacks: HTMLConversionCallbacks> {
private var tokenizer: Tokenizer<String.UnicodeScalarView.Iterator>! private var tokenizer: Tokenizer<String.UnicodeScalarView.Iterator>!
private var str: String! private var str: String!
private var actionStack: [ElementAction] = [] { private var actionStack: [ElementAction] = []
didSet {
hasSkipOrReplaceElementAction = actionStack.contains(where: {
switch $0 {
case .skip, .replace(_):
true
default:
false
}
})
}
}
private var hasSkipOrReplaceElementAction = false
var blockStateMachine = BlockStateMachine(blockBreak: "", lineBreak: "", listIndentForContentOutsideItem: "", append: { _ in }, removeChar: {}) var blockStateMachine = BlockStateMachine(blockBreak: "", lineBreak: "", listIndentForContentOutsideItem: "", append: { _ in }, removeChar: {})
private var currentElementIsEmpty = true private var currentElementIsEmpty = true
private var currentRun = "" private var currentRun = ""
@ -58,15 +46,13 @@ public class TextConverter<Callbacks: HTMLConversionCallbacks> {
switch token { switch token {
case .character(let scalar): case .character(let scalar):
currentElementIsEmpty = false currentElementIsEmpty = false
if blockStateMachine.continueBlock(char: scalar), if blockStateMachine.continueBlock(char: scalar) {
!hasSkipOrReplaceElementAction {
currentRun.unicodeScalars.append(scalar) currentRun.unicodeScalars.append(scalar)
} }
case .characterSequence(let string): case .characterSequence(let string):
currentElementIsEmpty = false currentElementIsEmpty = false
for c in string.unicodeScalars { for c in string.unicodeScalars {
if blockStateMachine.continueBlock(char: c), if blockStateMachine.continueBlock(char: c) {
!hasSkipOrReplaceElementAction {
currentRun.unicodeScalars.append(c) currentRun.unicodeScalars.append(c)
} }
} }
@ -151,10 +137,13 @@ public class TextConverter<Callbacks: HTMLConversionCallbacks> {
} }
private func finishRun() { private func finishRun() {
if case .append(let s) = actionStack.last { if actionStack.contains(.skip) {
currentRun = ""
return
} else if case .append(let s) = actionStack.last {
currentRun.append(s) currentRun.append(s)
} else if case .replace(let replacement) = actionStack.last { } else if case .replace(let replacement) = actionStack.first(where: \.isReplace) {
currentRun.append(replacement) currentRun = replacement
} }
guard !currentRun.isEmpty else { guard !currentRun.isEmpty else {

View File

@ -264,12 +264,6 @@ final class AttributedStringConverterTests: XCTestCase {
.paragraphStyle: NSParagraphStyle.default, .paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color, .foregroundColor: color,
])) ]))
let replaceNested = convert("<span class='replace'><b>a</b></span>", callbacks: Callbacks.self)
XCTAssertEqual(replaceNested, NSAttributedString(string: "", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
let appended = convert("<span class='append'>test</span>", callbacks: Callbacks.self) let appended = convert("<span class='append'>test</span>", callbacks: Callbacks.self)
XCTAssertEqual(appended, NSAttributedString(string: "test…", attributes: [ XCTAssertEqual(appended, NSAttributedString(string: "test…", attributes: [
.font: font, .font: font,
@ -618,48 +612,4 @@ final class AttributedStringConverterTests: XCTestCase {
XCTAssertEqual(convert("<ol><li><pre>a</pre></li></ol>"), result) XCTAssertEqual(convert("<ol><li><pre>a</pre></li></ol>"), result)
} }
func testInvisibleAtBeginningOfParagraphDoesNotPreventParagraphBreak() {
struct Invisible: HTMLConversionCallbacks {
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
if attributes.attributeValue(for: "class") == "invisible" {
.skip
} else {
.default
}
}
}
let result = NSAttributedString(string: "a\n\nc", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
])
let html = """
<p>a</p><p><span class="invisible">b</span><span class="ellipsis">c</span></p>
"""
XCTAssertEqual(convert(html, callbacks: Invisible.self), result)
}
func testReplaceAtBeginningOfParagraphDoesNotPreventParagraphBreak() {
struct Replace: HTMLConversionCallbacks {
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
if attributes.attributeValue(for: "class") == "replace" {
.replace("c")
} else {
.default
}
}
}
let result = NSAttributedString(string: "a\n\nc", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
])
let html = """
<p>a</p><p><span class="replace">b</span></p>
"""
XCTAssertEqual(convert(html, callbacks: Replace.self), result)
}
} }