Skip to content

Commit df2f738

Browse files
committed
fix: regex issues with mat context
1 parent 7961a6e commit df2f738

2 files changed

Lines changed: 41 additions & 8 deletions

File tree

Sources/MarkdownParser/MarkdownParser/MarkdownParser+MathContext.swift

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import Foundation
99

1010
private let mathPattern: NSRegularExpression? = {
1111
let patterns = [
12-
###"\$\$([\s\S]*?)\$\$"###, // 块级公式 $$ ... $$
13-
###"\\\\\[([\s\S]*?)\\\\\]"###, // 带转义的块级公式 \\[ ... \\]
14-
###"\\\\\(([\s\S]*?)\\\\\)"###, // 带转义的行内公式 \\( ... \\)
15-
###"\\\[ ([\s\S]*?) \\\]"###, // 单个反斜杠的块级公式 \[ ... \],前后需要空格
16-
###"\\\( ([^`\n]*?) \\\)"###, // 单个反斜杠的块级公式 \( ... \),前后需要空格,中间不能有 ` 和 换行
12+
###"\$\$([\s\S]*?)\$\$"###, // block formula $$ ... $$
13+
###"\\\\\[([\s\S]*?)\\\\\]"###, // escaped block formula \\[ ... \\]
14+
###"\\\\\(([\s\S]*?)\\\\\)"###, // escaped inline formula \\( ... \\)
15+
###"\\\[ ([\s\S]*?) \\\]"###, // single-backslash block formula \[ ... \], spaces required
16+
###"\\\( ([^`\n]*?) \\\)"###, // single-backslash inline formula \( ... \), spaces required, no backticks or newlines inside
1717
]
1818
let pattern = patterns.joined(separator: "|")
1919
guard let regex = try? NSRegularExpression(
@@ -90,9 +90,16 @@ public extension MarkdownParser {
9090
}
9191

9292
private let mathPatternWithinBlock: NSRegularExpression? = {
93+
// NOTE: `.allowCommentsAndWhitespace` strips unescaped whitespace from the
94+
// pattern, so do NOT rely on literal spaces inside these regexes. The
95+
// inline `$...$` pattern uses pandoc-style lookarounds to avoid matching
96+
// currency amounts like `$73,092.00 ... $73,370.00`:
97+
// • opening `$` must be followed by a non-whitespace character
98+
// • closing `$` must be preceded by a non-whitespace character
99+
// • closing `$` must not be immediately followed by a digit (anti-currency)
93100
let patterns = [
94-
###"\\\( ([^\r\n]+?) \\\)"###, // 行内公式 \(...\)
95-
###"\$ ([^\r\n]+?) \$"###, // 行内公式 $ ... $
101+
###"\\\(([^\r\n]+?)\\\)"###, // inline formula \(...\)
102+
###"\$(?!\s)([^\r\n$]+?)(?<!\s)\$(?!\d)"###, // inline formula $...$, avoids matching currency amounts
96103
]
97104
let pattern = patterns.joined(separator: "|")
98105
guard let regex = try? NSRegularExpression(
@@ -214,7 +221,7 @@ extension MarkdownParser {
214221
let fullMatchRange = match.range(at: 0)
215222
guard fullMatchRange.location != NSNotFound else { continue }
216223

217-
// 找到第一个有效的捕获组(数学内容)
224+
// find the first valid capture group (the math content)
218225
var mathContent: String?
219226
for groupIndex in 1 ..< match.numberOfRanges {
220227
let captureRange = match.range(at: groupIndex)

Tests/MarkdownViewTests/ParserTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,32 @@ final class ParserTests: XCTestCase {
348348
XCTAssertTrue(result.mathContext.isEmpty)
349349
}
350350

351+
func testCurrencyAmountsNotTreatedAsMath() {
352+
// Regression: `$XX.XX ... $YY.YY` (sentences with multiple currency
353+
// amounts) used to be matched as inline LaTeX because the regex was
354+
// compiled with `.allowCommentsAndWhitespace`, which silently stripped
355+
// the intended spaces from `\$ ... \$`, collapsing it to `\$...\$`.
356+
let md = "As of April 11, 2026, the price of Bitcoin is $73,092.00 USD. Its 24-hour high was $73,370.00 and the 24-hour low was $72,626.00."
357+
let result = parser.parse(md)
358+
XCTAssertTrue(
359+
result.mathContext.isEmpty,
360+
"Currency amounts must not be detected as math, got: \(result.mathContext)"
361+
)
362+
}
363+
364+
func testInlineDollarMathStillDetectedAroundCurrency() {
365+
// A real inline formula must still be detected even when currency
366+
// amounts appear in the same paragraph.
367+
let md = "The fee is $5.00 but the identity $E=mc^2$ still holds."
368+
let result = parser.parse(md)
369+
XCTAssertEqual(
370+
result.mathContext.count,
371+
1,
372+
"Expected exactly one inline math node, got: \(result.mathContext)"
373+
)
374+
XCTAssertEqual(result.mathContext.values.first, "E=mc^2")
375+
}
376+
351377
// MARK: - Image Parsing
352378

353379
func testParseImage() {

0 commit comments

Comments
 (0)