Convert lists to attributed strings

This commit is contained in:
Shadowfacts 2023-11-25 09:44:53 -05:00
parent 38d57b3f79
commit 97ce18d056
2 changed files with 84 additions and 2 deletions

View File

@ -99,6 +99,28 @@ struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
styleStack.append(.blockquote)
case "p":
startBlockElement()
case "ol":
startBlockElement()
finishRun()
styleStack.append(.orderedList(nextElementOrdinal: 1))
case "ul":
startBlockElement()
finishRun()
styleStack.append(.unorderedList)
case "li":
if str.length != 0 || !currentRun.isEmpty {
currentRun.append("\n")
}
let marker: String
if case .orderedList(let nextElementOrdinal) = styleStack.last {
marker = orderedTextList.marker(forItemNumber: nextElementOrdinal)
styleStack[styleStack.count - 1] = .orderedList(nextElementOrdinal: nextElementOrdinal + 1)
} else if case .unorderedList = styleStack.last {
marker = unorderedTextList.marker(forItemNumber: 0)
} else {
break
}
currentRun.append("\t\(marker)\t")
default:
break
}
@ -135,6 +157,14 @@ struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
case "blockquote":
finishRun()
removeLastStyle(.blockquote)
case "ol":
finishRun()
removeLastStyle(.orderedList)
case "ul":
finishRun()
removeLastStyle(.unorderedList)
case "li":
finishRun()
default:
break
}
@ -159,6 +189,19 @@ struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
return style
}()
private lazy var listParagraphStyle: NSParagraphStyle = {
let style = configuration.paragraphStyle.mutableCopy() as! NSMutableParagraphStyle
// I don't like that I can't just use paragraphStyle.textLists, because it makes the list markers
// not use the monospace digit font (it seems to just use whatever font attribute is set for the whole thing),
// and it doesn't right align the list markers.
// Unfortunately, doing it manually means the list markers are incldued in the selectable text.
style.headIndent = 32
style.firstLineHeadIndent = 0
// Use 2 tab stops, one for the list marker, the second for the content.
style.tabStops = [NSTextTab(textAlignment: .right, location: 28), NSTextTab(textAlignment: .natural, location: 32)]
return style
}()
private mutating func finishRun() {
guard !currentRun.isEmpty else {
return
@ -190,6 +233,8 @@ struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
case .blockquote:
attributes[.paragraphStyle] = blockquoteParagraphStyle
currentFontTraits.insert(.italic)
case .orderedList, .unorderedList:
attributes[.paragraphStyle] = listParagraphStyle
}
}
@ -290,6 +335,8 @@ private enum Style {
case link(URL?)
case strikethrough
case blockquote
case orderedList(nextElementOrdinal: Int)
case unorderedList
var type: StyleType {
switch self {
@ -305,11 +352,22 @@ private enum Style {
return .strikethrough
case .blockquote:
return .blockquote
case .orderedList(nextElementOrdinal: _):
return .orderedList
case .unorderedList:
return .unorderedList
}
}
enum StyleType {
case bold, italic, monospace, link, strikethrough, blockquote
enum StyleType: Equatable {
case bold
case italic
case monospace
case link
case strikethrough
case blockquote
case orderedList
case unorderedList
}
}
@ -318,3 +376,12 @@ extension Collection where Element == Attribute {
first(where: { $0.name == name })?.value
}
}
private let orderedTextList = OrderedNumberTextList(markerFormat: .decimal, options: 0)
private let unorderedTextList = NSTextList(markerFormat: .disc, options: 0)
private class OrderedNumberTextList: NSTextList {
override func marker(forItemNumber itemNumber: Int) -> String {
"\(super.marker(forItemNumber: itemNumber))."
}
}

View File

@ -26,6 +26,13 @@ final class AttributedStringConverterTests: XCTestCase {
style.firstLineHeadIndent = 32
return style
}()
private let listParagraphStyle: NSParagraphStyle = {
let style = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
style.headIndent = 32
style.firstLineHeadIndent = 0
style.tabStops = [NSTextTab(textAlignment: .right, location: 28), NSTextTab(textAlignment: .natural, location: 32)]
return style
}()
private func convert(_ html: String) -> NSAttributedString {
convert(html, callbacks: DefaultCallbacks.self)
@ -192,4 +199,12 @@ final class AttributedStringConverterTests: XCTestCase {
]))
}
func testOrderedList() {
let result = convert("<ol><li>a</li><li>b</li></ol>")
XCTAssertEqual(result, NSAttributedString(string: "\t1.\ta\n\t2.\tb", attributes: [
.font: font,
.paragraphStyle: listParagraphStyle,
]))
}
}