11package org .commonmark .ext .gfm .alerts .internal ;
22
3+ import java .util .List ;
34import java .util .Locale ;
45import java .util .Set ;
56import java .util .regex .Pattern ;
@@ -26,11 +27,11 @@ public class AlertBlockParser extends AbstractBlockParser {
2627 private static final Pattern ALERT_PATTERN_CUSTOM_TITLE = Pattern .compile ("^\\ [!([a-zA-Z]+)](.*)$" );
2728
2829 private final Alert block ;
29- private final String titleContent ;
30+ private final SourceLine titleLine ;
3031
31- private AlertBlockParser (String type , String titleContent ) {
32+ private AlertBlockParser (String type , SourceLine titleLine ) {
3233 this .block = new Alert (type );
33- this .titleContent = titleContent ;
34+ this .titleLine = titleLine ;
3435 }
3536
3637 @ Override
@@ -72,7 +73,7 @@ public BlockContinue tryContinue(ParserState state) {
7273
7374 @ Override
7475 public void parseInlines (InlineParser inlineParser ) {
75- if (titleContent . isEmpty () ) {
76+ if (titleLine == null || titleLine . getContent (). length () == 0 ) {
7677 return ;
7778 }
7879
@@ -84,7 +85,13 @@ public void parseInlines(InlineParser inlineParser) {
8485 * > But 3*3 = 9
8586 */
8687 var titleNode = new AlertTitle ();
87- inlineParser .parse (SourceLines .of (SourceLine .of (titleContent , null )), titleNode );
88+ inlineParser .parse (SourceLines .of (titleLine ), titleNode );
89+
90+ // Set source spans on the title node from the source line
91+ var sourceSpan = titleLine .getSourceSpan ();
92+ if (sourceSpan != null ) {
93+ titleNode .setSourceSpans (List .of (sourceSpan ));
94+ }
8895
8996 // Body blocks were attached as children during block parsing. Prepend the title.
9097 block .prependChild (titleNode );
@@ -181,13 +188,30 @@ private BlockStart tryStartFresh(CharSequence line, int nextNonSpace, ParserStat
181188 return BlockStart .none ();
182189 }
183190
184- var titleContent = "" ;
191+ SourceLine titleLine = null ;
185192 if (customTitlesAllowed ) {
186- titleContent = matcher .group (2 ).replaceFirst ("^[ \\ t]+" , "" ).stripTrailing ();
193+ var fullSourceLine = state .getLine ();
194+ var fullContent = fullSourceLine .getContent ();
195+
196+ var groupStart = matcher .start (2 );
197+ var groupEnd = matcher .end (2 );
198+ var absStart = afterGt + groupStart ;
199+ var absEnd = afterGt + groupEnd ;
200+
201+ // Trim leading spaces/tabs
202+ while (absStart < absEnd && Characters .isSpaceOrTab (fullContent , absStart )) {
203+ absStart ++;
204+ }
205+ // Trim trailing spaces/tabs
206+ while (absEnd > absStart && Characters .isSpaceOrTab (fullContent , absEnd - 1 )) {
207+ absEnd --;
208+ }
209+
210+ titleLine = fullSourceLine .substring (absStart , absEnd );
187211 }
188212
189213 // Consume the rest of the first line.
190- var start = BlockStart .of (new AlertBlockParser (type , titleContent )).atIndex (line .length ());
214+ var start = BlockStart .of (new AlertBlockParser (type , titleLine )).atIndex (line .length ());
191215
192216 // If we got here via the promotion path, replace the empty BlockQuote.
193217 var matched = state .getActiveBlockParser ().getBlock ();
0 commit comments