From 925e61e40c1b0a4e3ee7ad0a2aadbce78852a767 Mon Sep 17 00:00:00 2001
From: Stefan Ceriu
"# + let pattern = #"(p|ul|ol)>(\n+)(?=
])"#
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
return result
@@ -63,11 +64,12 @@ nonisolated extension String {
for match in matches.reversed() {
guard let range = Range(match.range, in: self),
- let innerMatchRange = Range(match.range(at: 1), in: self) else {
+ let tagRange = Range(match.range(at: 1), in: self),
+ let innerMatchRange = Range(match.range(at: 2), in: self) else {
continue
}
let numberOfBreaks = (self[innerMatchRange].components(separatedBy: "\n").count - 1)
- let replacement = "
" + String(repeating: "
", count: numberOfBreaks)
+ let replacement = "\(self[tagRange])>" + String(repeating: "
", count: numberOfBreaks)
result.replaceSubrange(range, with: replacement)
}
diff --git a/UnitTests/Sources/AttributedStringBuilderTests.swift b/UnitTests/Sources/AttributedStringBuilderTests.swift
index 67dd16c23..c2a71cd0a 100644
--- a/UnitTests/Sources/AttributedStringBuilderTests.swift
+++ b/UnitTests/Sources/AttributedStringBuilderTests.swift
@@ -684,6 +684,41 @@ struct AttributedStringBuilderTests {
}
}
+ /// A blank line the user typed after a list must survive, just like one between two paragraphs.
+ @Test
+ func newLineAfterList() throws {
+ let htmlString = "
Line 1
\nLine 4
\n" + + let attributedString = try #require(attributedStringBuilder.fromHTML(htmlString), "Could not build the attributed string") + + #expect(String(attributedString.characters) == "Line 1\n • Line 2\n\nLine 4") + } + + /// Without a blank line the trailing text is a lazy continuation of the item, so it stays on its line. + @Test + func noNewLineAfterList() throws { + let htmlString = "Line 2
" + + let attributedString = try #require(attributedStringBuilder.fromHTML(htmlString), "Could not build the attributed string") + + #expect(String(attributedString.characters) == " • Line 1\nLine 2") + } + + /// A blank line is preserved wherever it can be told apart from mere block separation. + @Test + func newLinesBetweenBlocks() throws { + let expectations = ["a
\nb
": "a\n\nb", + "a
\n\nb
": "a\n\n\nb", + "b
": " 2. a\n\nb", + // A list can interrupt a paragraph, so this newline is block separation. + "a
\n" let input4 = "
a
\nb
" let input5 = "empty" + let input6 = "b
" + let input7 = "\na
b
" + let expectedOutput2 = "
" + let expectedOutput3 = "
" + let expectedOutput4 = "
a
b
" let expectedOutput5 = input5 + let expectedOutput6 = "b
" + // A list can interrupt a paragraph, so this newline isn't a blank line the user typed. + let expectedOutput7 = input7 #expect(input0.replacingHtmlBreaksOccurrences() == expectedOutput0) #expect(input1.replacingHtmlBreaksOccurrences() == expectedOutput1) @@ -87,5 +92,7 @@ struct StringTests { #expect(input3.replacingHtmlBreaksOccurrences() == expectedOutput3) #expect(input4.replacingHtmlBreaksOccurrences() == expectedOutput4) #expect(input5.replacingHtmlBreaksOccurrences() == expectedOutput5) + #expect(input6.replacingHtmlBreaksOccurrences() == expectedOutput6) + #expect(input7.replacingHtmlBreaksOccurrences() == expectedOutput7) } }