1818
1919namespace Elastic . Markdown . Myst . Renderers . LlmMarkdown ;
2020
21- /// <summary>
22- /// Helper methods for common rendering patterns in LLM renderers
23- /// </summary>
2421public static class LlmRenderingHelpers
2522{
26- /// <summary>
27- /// Renders a block with fixed indentation applied to each line
28- /// </summary>
29- /// <param name="renderer">The target renderer to write to</param>
30- /// <param name="block">The block to render</param>
31- /// <param name="indentation">The indentation string to apply to each line (default: 2 spaces)</param>
3223 public static void RenderBlockWithIndentation ( LlmMarkdownRenderer renderer , MarkdownObject block , string indentation = " " )
3324 {
3425 using var tempWriter = new StringWriter ( ) ;
@@ -37,17 +28,14 @@ public static void RenderBlockWithIndentation(LlmMarkdownRenderer renderer, Mark
3728 BuildContext = renderer . BuildContext // Copy BuildContext for URL transformation
3829 } ;
3930 _ = tempRenderer . Render ( block ) ;
40-
41- // Get the rendered content and add indentation to each line
4231 var content = tempWriter . ToString ( ) . TrimEnd ( ) ;
43- if ( ! string . IsNullOrEmpty ( content ) )
32+ if ( string . IsNullOrEmpty ( content ) )
33+ return ;
34+ var lines = content . Split ( [ '\n ' , '\r ' ] , StringSplitOptions . RemoveEmptyEntries ) ;
35+ foreach ( var line in lines )
4436 {
45- var lines = content . Split ( [ '\n ' , '\r ' ] , StringSplitOptions . RemoveEmptyEntries ) ;
46- foreach ( var line in lines )
47- {
48- renderer . Write ( indentation ) ;
49- renderer . WriteLine ( line ) ;
50- }
37+ renderer . Write ( indentation ) ;
38+ renderer . WriteLine ( line ) ;
5139 }
5240 }
5341
@@ -56,30 +44,22 @@ public static void RenderBlockWithIndentation(LlmMarkdownRenderer renderer, Mark
5644 /// </summary>
5745 public static string ? MakeAbsoluteUrl ( LlmMarkdownRenderer renderer , string ? url )
5846 {
59- if ( string . IsNullOrEmpty ( url ) || renderer . BuildContext . CanonicalBaseUrl == null )
60- return url ;
61-
62- // If URL is already absolute, return as-is
63- if ( Uri . IsWellFormedUriString ( url , UriKind . Absolute ) )
47+ if (
48+ string . IsNullOrEmpty ( url )
49+ || renderer . BuildContext . CanonicalBaseUrl == null
50+ || Uri . IsWellFormedUriString ( url , UriKind . Absolute )
51+ || ! Uri . IsWellFormedUriString ( url , UriKind . Relative ) )
6452 return url ;
65-
66- // If URL is relative, prepend canonical base URL
67- if ( Uri . IsWellFormedUriString ( url , UriKind . Relative ) )
53+ try
6854 {
69- try
70- {
71- var baseUri = renderer . BuildContext . CanonicalBaseUrl ;
72- var absoluteUri = new Uri ( baseUri , url ) ;
73- return absoluteUri . ToString ( ) ;
74- }
75- catch
76- {
77- // If URI construction fails, return original URL
78- return url ;
79- }
55+ var baseUri = renderer . BuildContext . CanonicalBaseUrl ;
56+ var absoluteUri = new Uri ( baseUri , url ) ;
57+ return absoluteUri . ToString ( ) ;
58+ }
59+ catch
60+ {
61+ return url ;
8062 }
81-
82- return url ;
8363 }
8464}
8565
@@ -91,90 +71,70 @@ public class LlmYamlFrontMatterRenderer : MarkdownObjectRenderer<LlmMarkdownRend
9171 protected override void Write ( LlmMarkdownRenderer renderer , YamlFrontMatterBlock obj )
9272 {
9373 // Intentionally skip rendering YAML frontmatter - it should not appear in LLM output
94- // The frontmatter content is already processed and available through context.SourceFile.YamlFrontMatter
74+ // The frontmatter content is currently handled in LlmMarkdownExporter.cs
75+ // TODO: Handle YAML frontmatter in LLM output here
9576 }
9677}
9778
98- /// <summary>
99- /// Renders headings as clean CommonMark headings with improved spacing for readability
100- /// </summary>
10179public class LlmHeadingRenderer : MarkdownObjectRenderer < LlmMarkdownRenderer , HeadingBlock >
10280{
10381 protected override void Write ( LlmMarkdownRenderer renderer , HeadingBlock obj )
10482 {
105- // Add extra spacing before headings - always add multiple newlines
106- renderer . Writer . WriteLine ( ) ;
107- renderer . Writer . WriteLine ( ) ;
83+ renderer . EnsureBlockSpacing ( ) ;
84+ renderer . WriteLine ( ) ;
10885
109- // Extract just the text content for clean headings
11086 var headingText = ExtractHeadingText ( obj ) ;
11187
112- // Output as standard markdown heading
113- renderer . Writer . Write ( new string ( '#' , obj . Level ) ) ;
114- renderer . Writer . Write ( ' ' ) ;
88+ renderer . Write ( new string ( '#' , obj . Level ) ) ;
89+ renderer . Write ( " " ) ;
11590 renderer . WriteLine ( headingText ) ;
11691 }
11792
11893 private static string ExtractHeadingText ( HeadingBlock heading )
11994 {
12095 if ( heading . Inline == null )
12196 return string . Empty ;
122-
123- // Extract plain text from inline elements
12497 return heading . Inline . Descendants ( )
12598 . OfType < Markdig . Syntax . Inlines . LiteralInline > ( )
12699 . Select ( l => l . Content . ToString ( ) )
127100 . Aggregate ( string . Empty , ( current , text ) => current + text ) ;
128101 }
129102}
130103
131- /// <summary>
132- /// Renders paragraphs with proper spacing for LLM readability
133- /// </summary>
134104public class LlmParagraphRenderer : MarkdownObjectRenderer < LlmMarkdownRenderer , ParagraphBlock >
135105{
136106 protected override void Write ( LlmMarkdownRenderer renderer , ParagraphBlock obj )
137107 {
108+ // Only add newline if the paragraph is not within an element
138109 if ( obj . Parent is MarkdownDocument )
139110 renderer . EnsureBlockSpacing ( ) ;
140111 renderer . WriteLeafInline ( obj ) ;
141112 renderer . EnsureLine ( ) ;
142113 }
143114}
144115
145- /// <summary>
146- /// Renders enhanced code blocks (your custom extension) as standard code blocks with optional metadata
147- /// and improved spacing for readability
148- /// </summary>
149- public partial class LlmEnhancedCodeBlockRenderer : MarkdownObjectRenderer < LlmMarkdownRenderer , EnhancedCodeBlock >
116+ public class LlmEnhancedCodeBlockRenderer : MarkdownObjectRenderer < LlmMarkdownRenderer , EnhancedCodeBlock >
150117{
151118 protected override void Write ( LlmMarkdownRenderer renderer , EnhancedCodeBlock obj )
152119 {
153- // Ensure single empty line before code block
154120 renderer . EnsureBlockSpacing ( ) ;
155-
156- // Add caption as comment if present
157121 if ( ! string . IsNullOrEmpty ( obj . Caption ) )
158122 {
159123 renderer . Write ( "<!-- Caption: " ) ;
160124 renderer . Write ( obj . Caption ) ;
161125 renderer . WriteLine ( " -->" ) ;
162126 }
163-
164127 renderer . Write ( "```" ) ;
165128 if ( ! string . IsNullOrEmpty ( obj . Language ) )
166129 renderer . Write ( obj . Language ) ;
167130 renderer . WriteLine ( ) ;
168-
169131 var lastNonEmptyIndex = GetLastNonEmptyLineIndex ( obj ) ;
170132 for ( var i = 0 ; i <= lastNonEmptyIndex ; i ++ )
171133 {
172134 var line = obj . Lines . Lines [ i ] ;
173135 renderer . Write ( line . ToString ( ) ) ;
174136 renderer . WriteLine ( ) ;
175137 }
176-
177- // Close code block
178138 renderer . WriteLine ( "```" ) ;
179139 }
180140
@@ -226,15 +186,9 @@ protected override void Write(LlmMarkdownRenderer renderer, ListBlock listBlock)
226186 }
227187 }
228188
229- /// <summary>
230- /// Gets the continuation indent for multi-line list items
231- /// </summary>
232189 private static string GetContinuationIndent ( string baseIndent , bool isOrdered ) =>
233190 baseIndent + new string ( ' ' , isOrdered ? 3 : 2 ) ;
234191
235- /// <summary>
236- /// Renders any block type with proper list continuation indentation
237- /// </summary>
238192 private static void RenderBlockWithIndentation ( LlmMarkdownRenderer renderer , Block block , string baseIndent , bool isOrdered )
239193 {
240194 using var tempWriter = new StringWriter ( ) ;
@@ -304,13 +258,11 @@ protected override void Write(LlmMarkdownRenderer renderer, QuoteBlock obj)
304258 }
305259}
306260
307- /// <summary>
308- /// Renders thematic breaks as standard markdown horizontal rules
309- /// </summary>
310261public class LlmThematicBreakRenderer : MarkdownObjectRenderer < LlmMarkdownRenderer , ThematicBreakBlock >
311262{
312263 protected override void Write ( LlmMarkdownRenderer renderer , ThematicBreakBlock obj )
313264 {
265+ renderer . EnsureBlockSpacing ( ) ;
314266 renderer . Writer . WriteLine ( "---" ) ;
315267 renderer . EnsureLine ( ) ;
316268 }
@@ -447,9 +399,6 @@ private static int[] CalculateColumnWidths(LlmMarkdownRenderer renderer, Table t
447399 }
448400}
449401
450- /// <summary>
451- /// Renders MyST directives as structured comments for LLM understanding with improved visual separation
452- /// </summary>
453402public class LlmDirectiveRenderer : MarkdownObjectRenderer < LlmMarkdownRenderer , DirectiveBlock >
454403{
455404 protected override void Write ( LlmMarkdownRenderer renderer , DirectiveBlock obj )
@@ -459,7 +408,6 @@ protected override void Write(LlmMarkdownRenderer renderer, DirectiveBlock obj)
459408 case ImageBlock imageBlock :
460409 WriteImageBlock ( renderer , imageBlock ) ;
461410 return ;
462- // Special handling for include directives
463411 case IncludeBlock includeBlock :
464412 WriteIncludeBlock ( renderer , includeBlock ) ;
465413 return ;
@@ -509,18 +457,11 @@ private static void WriteImageBlock(LlmMarkdownRenderer renderer, ImageBlock ima
509457 renderer . EnsureLine ( ) ;
510458 }
511459
512- /// <summary>
513- /// Renders definition lists as structured XML for better LLM comprehension
514- /// </summary>
515- /// <summary>
516- /// Renders include directives by fetching and rendering the included content
517- /// </summary>
518460 private void WriteIncludeBlock ( LlmMarkdownRenderer renderer , IncludeBlock block )
519461 {
520- // If the include wasn't found or path is null, just write a comment
521462 if ( ! block . Found || block . IncludePath is null )
522463 {
523- renderer . Writer . WriteLine ( $ "<!-- INCLUDE ERROR: File not found or invalid path --> ") ;
464+ renderer . BuildContext . Collector . EmitError ( block . IncludePath ?? string . Empty , " File not found or invalid path") ;
524465 return ;
525466 }
526467
@@ -530,7 +471,7 @@ private void WriteIncludeBlock(LlmMarkdownRenderer renderer, IncludeBlock block)
530471 var snippet = block . Build . ReadFileSystem . FileInfo . New ( block . IncludePath ) ;
531472 if ( ! snippet . Exists )
532473 {
533- renderer . Writer . WriteLine ( $ "<!-- INCLUDE ERROR: File does not exist: { block . IncludePath } --> ") ;
474+ renderer . BuildContext . Collector . EmitError ( block . IncludePath ?? string . Empty , " File not found or invalid path ") ;
534475 return ;
535476 }
536477
@@ -540,62 +481,40 @@ private void WriteIncludeBlock(LlmMarkdownRenderer renderer, IncludeBlock block)
540481 // For literal includes, output the content as a code block
541482 // Read the file content
542483 var content = block . Build . ReadFileSystem . File . ReadAllText ( block . IncludePath ) ;
543-
544- // Add language if specified
545- renderer . Writer . Write ( "```" ) ;
484+ renderer . Write ( "```" ) ;
546485 if ( ! string . IsNullOrEmpty ( block . Language ) )
547- {
548- renderer . Writer . Write ( block . Language ) ;
549- }
550-
486+ renderer . Write ( block . Language ) ;
551487 renderer . WriteLine ( ) ;
552- // Add an extra newline after the opening code block marker
553- renderer . Writer . WriteLine ( ) ;
554-
555- // Write the content
556- renderer . Writer . Write ( content ) ;
557-
558- // Close the code block
559- renderer . Writer . WriteLine ( ) ;
560- renderer . Writer . WriteLine ( "```" ) ;
561-
562- // Add multiple line breaks after code blocks for better readability
488+ renderer . Write ( content ) ;
489+ renderer . WriteLine ( ) ;
490+ renderer . WriteLine ( "```" ) ;
563491 }
564492 else
565493 {
566- // For regular includes, parse as markdown and render
567494 try
568495 {
569496 var parentPath = block . Context . MarkdownParentPath ?? block . Context . MarkdownSourcePath ;
570- var document = MarkdownParser . ParseSnippetAsync ( block . Build , block . Context , snippet , parentPath , block . Context . YamlFrontMatter , default )
497+ var document = MarkdownParser . ParseSnippetAsync ( block . Build , block . Context , snippet , parentPath , block . Context . YamlFrontMatter , Cancel . None )
571498 . GetAwaiter ( ) . GetResult ( ) ;
572-
573- // Use the same renderer to render the included content
574499 _ = renderer . Render ( document ) ;
575500 }
576501 catch ( Exception ex )
577502 {
578- renderer . Writer . WriteLine ( $ "<!-- INCLUDE ERROR: Failed to parse included content: { ex . Message } -->" ) ;
503+ renderer . BuildContext . Collector . EmitError ( block . IncludePath ?? string . Empty , " Failed to parse included content" , ex ) ;
579504 }
580505 }
581506
582507 renderer . EnsureLine ( ) ;
583508 }
584509
585- /// <summary>
586- /// Writes children with the specified indentation applied to each line
587- /// </summary>
588510 private static void WriteChildrenWithIndentation ( LlmMarkdownRenderer renderer , Block container , string indent )
589511 {
590- // Simple approach: capture output and manually add indentation
512+ // Capture output and manually add indentation
591513 using var sw = new StringWriter ( ) ;
592514 var originalWriter = renderer . Writer ;
593515 renderer . Writer = sw ;
594-
595516 try
596517 {
597- // Render children to our temporary writer
598-
599518 switch ( container )
600519 {
601520 case ContainerBlock containerBlock :
@@ -605,30 +524,15 @@ private static void WriteChildrenWithIndentation(LlmMarkdownRenderer renderer, B
605524 renderer . WriteLeafInline ( leafBlock ) ;
606525 break ;
607526 }
608-
609-
610- // Get the output and add indentation to each non-empty line
611527 var content = sw . ToString ( ) ;
612528 if ( string . IsNullOrEmpty ( content ) )
613529 return ;
614530 var reader = new StringReader ( content ) ;
615531 while ( reader . ReadLine ( ) is { } line )
616- {
617- if ( string . IsNullOrWhiteSpace ( line ) )
618- {
619- // Empty line - write as-is
620- originalWriter . WriteLine ( ) ;
621- }
622- else
623- {
624- // Non-empty line - add indentation
625- originalWriter . WriteLine ( indent + line ) ;
626- }
627- }
532+ originalWriter . WriteLine ( string . IsNullOrWhiteSpace ( line ) ? string . Empty : indent + line ) ;
628533 }
629534 finally
630535 {
631- // Restore original writer
632536 renderer . Writer = originalWriter ;
633537 }
634538 }
0 commit comments