Compare commits
No commits in common. "3c9a5eb0caa15764b3cd7f4876c454bccfe60a69" and "9c8b127f152c9a7a43019703a5fc2eaa9e18f941" have entirely different histories.
3c9a5eb0ca
...
9c8b127f15
|
@ -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];
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue