@@ -43,26 +43,148 @@ export function normalizeContent(input: unknown): string {
4343 return "" ;
4444}
4545
46- export function normalizeMathDelimiters ( source : string ) : string {
47- if ( ! source || ( ! source . includes ( "\\(" ) && ! source . includes ( "\\[" ) ) ) {
48- return source ;
49- }
46+ const MARKDOWN_LITERAL_FRAGMENT_RE = / ( ` ` ` [ \s \S ] * ? ` ` ` | ~ ~ ~ [ \s \S ] * ? ~ ~ ~ | ` [ ^ ` \n ] * ` ) / g ;
47+ const INLINE_DOLLAR_MATH_RE = / ( ^ | [ ^ \\ $ ] ) \$ ( [ ^ $ \n ] { 1 , 800 } ) \$ / g ;
48+ const ESCAPED_INLINE_DOLLAR_MATH_RE = / \\ \$ ( [ ^ $ \n ] { 1 , 400 } ) \\ \$ / g ;
49+ const DISPLAY_DOLLAR_MATH_RE = / ( \$ { 2 , } ) ( [ \s \S ] * ? ) ( \1 ) / g ;
5050
51- const fragments = source . split ( / ( ` ` ` [ \s \S ] * ?` ` ` | ` [ ^ ` \n ] * ` ) / g) ;
51+ function isMarkdownLiteralFragment ( fragment : string ) : boolean {
52+ return fragment . startsWith ( "```" ) || fragment . startsWith ( "~~~" ) || fragment . startsWith ( "`" ) ;
53+ }
5254
53- return fragments
55+ function mapMarkdownTextFragments ( source : string , transform : ( fragment : string ) => string ) : string {
56+ return source
57+ . split ( MARKDOWN_LITERAL_FRAGMENT_RE )
5458 . map ( ( fragment ) => {
55- if ( ! fragment || fragment . startsWith ( "```" ) || fragment . startsWith ( "`" ) ) {
59+ if ( ! fragment || isMarkdownLiteralFragment ( fragment ) ) {
5660 return fragment ;
5761 }
58-
59- return fragment
60- . replace ( / \\ \[ \s * \n ? ( [ \s \S ] * ?) \n ? \s * \\ \] / g, ( _ , mathContent : string ) => `$$\n${ mathContent . trim ( ) } \n$$` )
61- . replace ( / \\ \( ( [ \s \S ] * ?) \\ \) / g, ( _ , mathContent : string ) => `$${ mathContent . trim ( ) } $` ) ;
62+ return transform ( fragment ) ;
6263 } )
6364 . join ( "" ) ;
6465}
6566
67+ function looksLikeLatexMathContent ( value : string ) : boolean {
68+ const trimmedValue = value . trim ( ) ;
69+ if ( ! trimmedValue || / ^ \d + (?: [ . , ] \d + ) ? $ / . test ( trimmedValue ) ) {
70+ return false ;
71+ }
72+
73+ return (
74+ / \\ [ A - Z a - z ] + / . test ( trimmedValue ) ||
75+ / [ \^ _ { } = < > + \- * / ] / . test ( trimmedValue ) ||
76+ ( trimmedValue . includes ( "|" ) && / [ A - Z a - z \\ Α - ω ] | [ \^ _ { } = < > + \- * / ] / . test ( trimmedValue ) ) ||
77+ / ^ [ A - Z a - z ] $ / . test ( trimmedValue ) ||
78+ / [ Α - ω ] / . test ( trimmedValue )
79+ ) ;
80+ }
81+
82+ function normalizeLatexPipes ( mathContent : string ) : string {
83+ return mathContent . replace ( / ( ^ | [ ^ \\ ] ) \| / g, "$1\\vert{}" ) ;
84+ }
85+
86+ function isEscapedCharacter ( source : string , index : number ) : boolean {
87+ let slashCount = 0 ;
88+ for ( let cursor = index - 1 ; cursor >= 0 && source [ cursor ] === "\\" ; cursor -= 1 ) {
89+ slashCount += 1 ;
90+ }
91+ return slashCount % 2 === 1 ;
92+ }
93+
94+ function getDollarMathDelimiterLength ( source : string , index : number ) : number {
95+ if ( source [ index ] !== "$" || isEscapedCharacter ( source , index ) || source [ index - 1 ] === "$" ) {
96+ return 0 ;
97+ }
98+
99+ if ( source [ index + 1 ] === "$" && source [ index + 2 ] !== "$" ) {
100+ return 2 ;
101+ }
102+
103+ return source [ index + 1 ] === "$" ? 0 : 1 ;
104+ }
105+
106+ function normalizeDollarMathContent ( mathContent : string , inline : boolean ) : string {
107+ const normalizedContent = inline ? mathContent . replace ( / \s * \n \s * / g, " " ) : mathContent ;
108+ return normalizeLatexPipes ( normalizedContent ) ;
109+ }
110+
111+ function normalizeDollarMathSegments ( source : string ) : string {
112+ if ( ! source . includes ( "$" ) ) {
113+ return source ;
114+ }
115+
116+ let normalizedSource = "" ;
117+ let consumedUntil = 0 ;
118+
119+ for ( let index = 0 ; index < source . length ; index += 1 ) {
120+ const delimiterLength = getDollarMathDelimiterLength ( source , index ) ;
121+ if ( ! delimiterLength ) {
122+ continue ;
123+ }
124+
125+ const openingDelimiterIndex = index ;
126+ let closingDelimiterIndex = - 1 ;
127+ for ( let cursor = openingDelimiterIndex + delimiterLength ; cursor < source . length ; cursor += 1 ) {
128+ if ( getDollarMathDelimiterLength ( source , cursor ) === delimiterLength ) {
129+ closingDelimiterIndex = cursor ;
130+ break ;
131+ }
132+ }
133+
134+ if ( closingDelimiterIndex < 0 ) {
135+ break ;
136+ }
137+
138+ const mathContent = source . slice ( openingDelimiterIndex + delimiterLength , closingDelimiterIndex ) ;
139+ const inline = delimiterLength === 1 ;
140+ const shouldNormalize =
141+ ( mathContent . includes ( "|" ) || ( inline && mathContent . includes ( "\n" ) ) ) &&
142+ looksLikeLatexMathContent ( mathContent ) ;
143+
144+ if ( shouldNormalize ) {
145+ normalizedSource += source . slice ( consumedUntil , openingDelimiterIndex + delimiterLength ) ;
146+ normalizedSource += normalizeDollarMathContent ( mathContent , inline ) ;
147+ normalizedSource += source . slice ( closingDelimiterIndex , closingDelimiterIndex + delimiterLength ) ;
148+ consumedUntil = closingDelimiterIndex + delimiterLength ;
149+ }
150+
151+ index = closingDelimiterIndex + delimiterLength - 1 ;
152+ }
153+
154+ if ( ! consumedUntil ) {
155+ return source ;
156+ }
157+
158+ return normalizedSource + source . slice ( consumedUntil ) ;
159+ }
160+
161+ function normalizeLatexDelimitersInText ( source : string ) : string {
162+ return source
163+ . replace ( / \\ \[ \s * \n ? ( [ \s \S ] * ?) \n ? \s * \\ \] / g, ( _ , mathContent : string ) => `$$\n${ mathContent . trim ( ) } \n$$` )
164+ . replace ( / \\ \( ( [ \s \S ] * ?) \\ \) / g, ( _ , mathContent : string ) => `$${ mathContent . trim ( ) } $` )
165+ . replace ( ESCAPED_INLINE_DOLLAR_MATH_RE , ( match : string , mathContent : string ) => {
166+ const trimmedMathContent = mathContent . trim ( ) ;
167+ return looksLikeLatexMathContent ( trimmedMathContent ) ? `$${ trimmedMathContent } $` : match ;
168+ } ) ;
169+ }
170+
171+ export function normalizeMathDelimiters ( source : string ) : string {
172+ if ( ! source ) {
173+ return source ;
174+ }
175+
176+ const shouldNormalizeDelimiters = source . includes ( "\\(" ) || source . includes ( "\\[" ) || source . includes ( "\\$" ) ;
177+ const hasDollarMath = source . includes ( "$" ) ;
178+ if ( ! shouldNormalizeDelimiters && ! hasDollarMath ) {
179+ return source ;
180+ }
181+
182+ return mapMarkdownTextFragments ( source , ( fragment ) => {
183+ const normalizedFragment = shouldNormalizeDelimiters ? normalizeLatexDelimitersInText ( fragment ) : fragment ;
184+ return normalizedFragment . includes ( "$" ) ? normalizeDollarMathSegments ( normalizedFragment ) : normalizedFragment ;
185+ } ) ;
186+ }
187+
66188const LATEX_UNICODE_SYMBOLS : Array < [ RegExp , string ] > = [
67189 [ / → / g, " \\to " ] ,
68190 [ / ← / g, " \\leftarrow " ] ,
@@ -72,7 +194,6 @@ const LATEX_UNICODE_SYMBOLS: Array<[RegExp, string]> = [
72194 [ / ⇔ / g, " \\Leftrightarrow " ] ,
73195] ;
74196
75- const INLINE_CODE_OR_FENCE_RE = / ( ` ` ` [ \s \S ] * ?` ` ` | ~ ~ ~ [ \s \S ] * ?~ ~ ~ | ` [ ^ ` \n ] * ` ) / g;
76197const THINKING_LIKE_HTML_TAG_RE = / < \/ ? \s * t h i n k [ \w - ] * \b [ ^ > ] * > / gi;
77198
78199function escapeHtmlTag ( value : string ) : string {
@@ -87,16 +208,7 @@ function escapeThinkingLikeHtmlTags(source: string): string {
87208 return source ;
88209 }
89210
90- return source
91- . split ( INLINE_CODE_OR_FENCE_RE )
92- . map ( ( fragment ) => {
93- if ( ! fragment || fragment . startsWith ( "```" ) || fragment . startsWith ( "~~~" ) || fragment . startsWith ( "`" ) ) {
94- return fragment ;
95- }
96-
97- return fragment . replace ( THINKING_LIKE_HTML_TAG_RE , escapeHtmlTag ) ;
98- } )
99- . join ( "" ) ;
211+ return mapMarkdownTextFragments ( source , ( fragment ) => fragment . replace ( THINKING_LIKE_HTML_TAG_RE , escapeHtmlTag ) ) ;
100212}
101213
102214function normalizeLatexSymbols ( mathContent : string ) : string {
@@ -111,23 +223,23 @@ export function normalizeLatexUnicodeSymbols(source: string): string {
111223 return source ;
112224 }
113225
114- const fragments = source . split ( / ( ` ` ` [ \s \S ] * ?` ` ` | ` [ ^ ` \n ] * ` ) / g) ;
115-
116- return fragments
117- . map ( ( fragment ) => {
118- if ( ! fragment || fragment . startsWith ( "```" ) || fragment . startsWith ( "`" ) ) {
119- return fragment ;
120- }
121-
122- return fragment . replace ( / ( \$ { 2 , } ) ( [ \s \S ] * ?) ( \1) / g, ( match , openingDelimiter : string , mathContent : string , closingDelimiter : string ) => {
226+ return mapMarkdownTextFragments ( source , ( fragment ) =>
227+ fragment
228+ . replace ( DISPLAY_DOLLAR_MATH_RE , ( match , openingDelimiter : string , mathContent : string , closingDelimiter : string ) => {
123229 if ( ! mathContent ) {
124230 return match ;
125231 }
126232
127233 return `${ openingDelimiter } ${ normalizeLatexSymbols ( mathContent ) } ${ closingDelimiter } ` ;
128- } ) ;
129- } )
130- . join ( "" ) ;
234+ } )
235+ . replace ( INLINE_DOLLAR_MATH_RE , ( match : string , prefix : string , mathContent : string ) => {
236+ if ( ! mathContent ) {
237+ return match ;
238+ }
239+
240+ return `${ prefix } $${ normalizeLatexSymbols ( mathContent ) } $` ;
241+ } ) ,
242+ ) ;
131243}
132244
133245export function normalizeMermaidBlocks ( source : string ) : string {
0 commit comments