Convert lists to attributed strings
This commit is contained in:
parent
38d57b3f79
commit
97ce18d056
|
@ -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))."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
]))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue