diff --git a/Sources/HTMLStreamer/AttributedStringConverter.swift b/Sources/HTMLStreamer/AttributedStringConverter.swift
index 8459f3d..0fe7609 100644
--- a/Sources/HTMLStreamer/AttributedStringConverter.swift
+++ b/Sources/HTMLStreamer/AttributedStringConverter.swift
@@ -99,6 +99,28 @@ struct AttributedStringConverter {
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 {
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 {
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 {
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))."
+ }
+}
diff --git a/Tests/HTMLStreamerTests/AttributedStringConverterTests.swift b/Tests/HTMLStreamerTests/AttributedStringConverterTests.swift
index 73dbd89..3620b51 100644
--- a/Tests/HTMLStreamerTests/AttributedStringConverterTests.swift
+++ b/Tests/HTMLStreamerTests/AttributedStringConverterTests.swift
@@ -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("- a
- b
")
+ XCTAssertEqual(result, NSAttributedString(string: "\t1.\ta\n\t2.\tb", attributes: [
+ .font: font,
+ .paragraphStyle: listParagraphStyle,
+ ]))
+ }
+
}