Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/_snippets/sample-data-markdown.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Name,Notes,Links
Alpha,"**Bold** text, _italic_ text, and `code`.","[Google](https://www.google.com)"
Bravo,"Inline role: {kbd}`Ctrl+C`.","[Elastic docs](https://www.elastic.co/docs)"
Charlie,"Mixed **bold** and _italic_, plus `inline code`.","[Search](https://www.elastic.co/docs/solutions/search)"
Delta,"Feature {applies_to}`stack: ga 9.1` available now.","[Elastic](https://www.elastic.co)"
Echo,"This is {preview}`9.1` functionality.","[Preview docs](https://www.elastic.co/docs)"
28 changes: 28 additions & 0 deletions docs/syntax/csv-include.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,34 @@ The directive includes built-in performance limits to handle large files efficie
- **Column limit**: Maximum of 10 columns will be displayed
- **File size limit**: Maximum file size of 10MB

### Markdown rendering in cells

Cells are parsed as Markdown, so they can render inline formatting and links. For example, a cell containing `**Bold**` becomes bold text, and `[Text](https://www.google.com)` becomes a link.

Here is a complete example that uses multiple Markdown formats:

:::::{tab-set}

::::{tab-item} Output

:::{csv-include} ../_snippets/sample-data-markdown.csv
:caption: Sample data with Markdown formatting
:::

::::

::::{tab-item} Markdown

```markdown
:::{csv-include} _snippets/sample-data-markdown.csv
:caption: Sample data with Markdown formatting
:::
```

::::

:::::

## Performance considerations

The CSV directive is optimized for large files:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System.IO.Abstractions;
using Elastic.Markdown.Diagnostics;

namespace Elastic.Markdown.Myst.Directives.CsvInclude;
Expand All @@ -10,6 +11,10 @@ public class CsvIncludeBlock(DirectiveBlockParser parser, ParserContext context)
{
public override string Directive => "csv-include";

public ParserContext Context { get; } = context;

public IFileInfo IncludeFrom { get; } = context.MarkdownSourcePath;

public string? CsvFilePath { get; private set; }
public string? CsvFilePathRelativeToSource { get; private set; }
public bool Found { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<tr>
@for (var i = 0; i < csvRows[0].Length; i++)
{
<th>@csvRows[0][i]</th>
<th>@Model.RenderCell(csvRows[0][i])</th>
}
</tr>
</thead>
Expand All @@ -37,7 +37,7 @@
<tr>
@for (var i = 0; i < csvRows[rowIndex].Length; i++)
{
<td>@csvRows[rowIndex][i]</td>
<td>@Model.RenderCell(csvRows[rowIndex][i])</td>
}
</tr>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
// See the LICENSE file in the project root for more information

using Elastic.Markdown.Diagnostics;
using Microsoft.AspNetCore.Html;

namespace Elastic.Markdown.Myst.Directives.CsvInclude;

public class CsvIncludeViewModel : DirectiveViewModel
{
public required Func<string, HtmlString> RenderMarkdown { get; init; }

public IEnumerable<string[]> GetCsvRows()
{
if (DirectiveBlock is not CsvIncludeBlock csvBlock || !csvBlock.Found || string.IsNullOrEmpty(csvBlock.CsvFilePath))
Expand Down Expand Up @@ -48,9 +51,18 @@ public IEnumerable<string[]> GetCsvRows()
});
}

public static CsvIncludeViewModel Create(CsvIncludeBlock csvBlock) =>
public HtmlString RenderCell(string? value)
{
if (string.IsNullOrEmpty(value))
return HtmlString.Empty;

return RenderMarkdown(value);
}

public static CsvIncludeViewModel Create(CsvIncludeBlock csvBlock, Func<string, HtmlString> renderMarkdown) =>
new()
{
DirectiveBlock = csvBlock
DirectiveBlock = csvBlock,
RenderMarkdown = renderMarkdown
};
}
38 changes: 37 additions & 1 deletion src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System;
using System.Diagnostics.CodeAnalysis;
using Elastic.Documentation.AppliesTo;
using Elastic.Markdown.Diagnostics;
Expand Down Expand Up @@ -29,6 +30,7 @@
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using Microsoft.AspNetCore.Html;
using RazorSlices;
using YamlDotNet.Core;

Expand Down Expand Up @@ -517,11 +519,45 @@ void Render(Block o)

private static void WriteCsvIncludeBlock(HtmlRenderer renderer, CsvIncludeBlock block)
{
var viewModel = CsvIncludeViewModel.Create(block);
var viewModel = CsvIncludeViewModel.Create(block, value => RenderCsvCellMarkdown(block, value));
var slice = CsvIncludeView.Create(viewModel);
RenderRazorSlice(slice, renderer);
}

private static HtmlString RenderCsvCellMarkdown(CsvIncludeBlock block, string value)
{
if (string.IsNullOrWhiteSpace(value))
return HtmlString.Empty;

var document = MarkdownParser.ParseMarkdownStringAsync(
block.Build,
block.Context,
value,
block.IncludeFrom,
block.Context.YamlFrontMatter,
MarkdownParser.Pipeline);

if (document.Count == 1 && document.FirstOrDefault() is ParagraphBlock paragraph && paragraph.Inline != null)
return RenderInlineMarkdown(paragraph);

var html = document.ToHtml(MarkdownParser.Pipeline);
return new HtmlString(html.EnsureTrimmed());
}

private static HtmlString RenderInlineMarkdown(ParagraphBlock paragraph)
{
if (paragraph.Inline is null)
return HtmlString.Empty;

var subscription = DocumentationObjectPoolProvider.HtmlRendererPool.Get();
subscription.HtmlRenderer.WriteChildren(paragraph.Inline);

var result = subscription.RentedStringBuilder?.ToString();
DocumentationObjectPoolProvider.HtmlRendererPool.Return(subscription);

return result == null ? HtmlString.Empty : new HtmlString(result.EnsureTrimmed());
}

private static void WriteChangelogBlock(HtmlRenderer renderer, ChangelogBlock block)
{
if (!block.Found || block.BundlesFolderPath is null)
Expand Down
15 changes: 15 additions & 0 deletions tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,21 @@ public void HandlesEscapedQuotes()
}
}

public class CsvIncludeRenderLinksTests(ITestOutputHelper output) : DirectiveTest(output,
"""
::::{csv-include} test-data.csv
::::
""")
{
protected override void AddToFileSystem(MockFileSystem fileSystem) =>
fileSystem.AddFile("docs/test-data.csv", new MockFileData(
@"Name,Link
Search,[Text](https://www.google.com)"));

[Fact]
public void RendersMarkdownLinkAsLink() => Html.Should().Contain(">Text</a>");
}

public class CsvIncludeNotFoundTests(ITestOutputHelper output) : DirectiveTest<CsvIncludeBlock>(output,
"""
:::{csv-include} missing-file.csv
Expand Down
Loading