feat: added parsing heading one from Markdown into a model
This commit is contained in:
parent
da16d3f28e
commit
55a4bbe676
88
src/MfGames.Nitride.Markdown/ParseMarkdownHeadingOne.cs
Normal file
88
src/MfGames.Nitride.Markdown/ParseMarkdownHeadingOne.cs
Normal file
|
@ -0,0 +1,88 @@
|
|||
using FluentValidation;
|
||||
using Markdig.Renderers.Roundtrip;
|
||||
using Markdig.Syntax;
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.Nitride.Generators;
|
||||
|
||||
namespace MfGames.Nitride.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// An operation that parses the Markdown and converts the first heading one
|
||||
/// into a model to include in the component. The rest of the Markdown is put
|
||||
/// back as the text content of the entity.
|
||||
/// </summary>
|
||||
[WithProperties]
|
||||
public partial class ParseMarkdownHeadingOne : IOperation
|
||||
{
|
||||
private readonly IValidator<ParseMarkdownHeadingOne> validator;
|
||||
|
||||
public ParseMarkdownHeadingOne(IValidator<ParseMarkdownHeadingOne> validator)
|
||||
{
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a callback for adding a heading to a given entity.
|
||||
/// </summary>
|
||||
public Func<Entity, string?, Entity>? AddModelCallback { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Entity> Run(
|
||||
IEnumerable<Entity> input,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
this.validator.ValidateAndThrow(this);
|
||||
|
||||
return input.SelectManyEntity<IsMarkdown>(x => x.Select(this.Parse));
|
||||
}
|
||||
|
||||
private string? GetText(MarkdownObject? block)
|
||||
{
|
||||
if (block == null)
|
||||
return null;
|
||||
|
||||
var writer = new StringWriter();
|
||||
var renderer = new RoundtripRenderer(writer);
|
||||
|
||||
renderer.Write(block);
|
||||
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
private Entity Parse(Entity entity)
|
||||
{
|
||||
// Get the text content of the file. No text, we don't do anything (but
|
||||
// there is going to be text since we filtered on IsMarkdown).
|
||||
string? oldText = entity.GetTextContentString();
|
||||
|
||||
if (oldText == null)
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
// Parse the result as Markdown and pull out the heading. If we can't
|
||||
// find one, then we just return the entity. We need to track trivia
|
||||
// because we are round-tripping back to Markdown.
|
||||
MarkdownDocument document = Markdig.Markdown.Parse(oldText, true);
|
||||
Block? block = document.FirstOrDefault(block => block is HeadingBlock);
|
||||
|
||||
if (block is not HeadingBlock { Level: 1 } heading)
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
string? headingText = this.GetText(heading.Inline);
|
||||
|
||||
// Convert the heading into the model.
|
||||
// Pull out the heading so we can write the rest back.
|
||||
document.Remove(heading);
|
||||
|
||||
string newText = this.GetText(document)!;
|
||||
|
||||
// Allow the extending class to add the model to the entity and then
|
||||
// set the text content to the new value before returning the results.
|
||||
return this.AddModelCallback!.Invoke(entity, headingText).SetTextContent(newText);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using FluentValidation;
|
||||
|
||||
namespace MfGames.Nitride.Markdown.Validators;
|
||||
|
||||
public class ParseMarkdownHeadingOneValidator : AbstractValidator<ParseMarkdownHeadingOne>
|
||||
{
|
||||
public ParseMarkdownHeadingOneValidator()
|
||||
{
|
||||
this.RuleFor(x => x.AddModelCallback).NotNull();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MfGames.Gallium;
|
||||
using MfGames.Nitride.Contents;
|
||||
using MfGames.TestSetup;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace MfGames.Nitride.Markdown.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests the functionality of the ParseMarkdownHeadingOne operation.
|
||||
/// </summary>
|
||||
public class ParseMarkdownHeadingOneTests : TestBase<MarkdownTestContext>
|
||||
{
|
||||
public ParseMarkdownHeadingOneTests(ITestOutputHelper output)
|
||||
: base(output) { }
|
||||
|
||||
[Fact]
|
||||
public void ParseComplexHeader()
|
||||
{
|
||||
using MarkdownTestContext context = this.CreateContext();
|
||||
|
||||
List<Entity> input =
|
||||
new()
|
||||
{
|
||||
new Entity()
|
||||
.Set(IsMarkdown.Instance)
|
||||
.SetTextContent("# Heading [One](/test)\n\nContent\nsecond\n\nline"),
|
||||
};
|
||||
|
||||
ParseMarkdownHeadingOne op = context
|
||||
.Resolve<ParseMarkdownHeadingOne>()
|
||||
.WithAddModelCallback((entity, heading) => entity.Set(heading));
|
||||
|
||||
IEnumerable<Entity> output = op.Run(input);
|
||||
Entity first = output.First();
|
||||
string content = first.GetTextContentString()!.Trim();
|
||||
|
||||
Assert.Equal("Content\nsecond\n\nline", content);
|
||||
Assert.Equal("Heading [One](/test)", first.Get<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEmptyHeader()
|
||||
{
|
||||
using MarkdownTestContext context = this.CreateContext();
|
||||
|
||||
List<Entity> input =
|
||||
new() { new Entity().Set(IsMarkdown.Instance).SetTextContent("#\nContent"), };
|
||||
|
||||
ParseMarkdownHeadingOne op = context
|
||||
.Resolve<ParseMarkdownHeadingOne>()
|
||||
.WithAddModelCallback((entity, heading) => entity.Set(heading));
|
||||
|
||||
IEnumerable<Entity> output = op.Run(input);
|
||||
Entity first = output.First();
|
||||
string content = first.GetTextContentString()!.Trim();
|
||||
|
||||
Assert.Equal("Content", content);
|
||||
Assert.Equal("", first.Get<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseHeaderTwo()
|
||||
{
|
||||
using MarkdownTestContext context = this.CreateContext();
|
||||
|
||||
List<Entity> input =
|
||||
new()
|
||||
{
|
||||
new Entity().Set(IsMarkdown.Instance).SetTextContent("## Heading Two\nContent"),
|
||||
};
|
||||
|
||||
ParseMarkdownHeadingOne op = context
|
||||
.Resolve<ParseMarkdownHeadingOne>()
|
||||
.WithAddModelCallback((entity, heading) => entity.Set(heading));
|
||||
|
||||
IEnumerable<Entity> output = op.Run(input);
|
||||
Entity first = output.First();
|
||||
string content = first.GetTextContentString()!.Trim();
|
||||
|
||||
Assert.Equal("## Heading Two\nContent", content);
|
||||
Assert.False(first.Has<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseMultipleHeaderOne()
|
||||
{
|
||||
using MarkdownTestContext context = this.CreateContext();
|
||||
|
||||
List<Entity> input =
|
||||
new()
|
||||
{
|
||||
new Entity()
|
||||
.Set(IsMarkdown.Instance)
|
||||
.SetTextContent("# Heading One\n\n# Heading Two\n Content"),
|
||||
};
|
||||
|
||||
ParseMarkdownHeadingOne op = context
|
||||
.Resolve<ParseMarkdownHeadingOne>()
|
||||
.WithAddModelCallback((entity, heading) => entity.Set(heading));
|
||||
|
||||
IEnumerable<Entity> output = op.Run(input);
|
||||
Entity first = output.First();
|
||||
string content = first.GetTextContentString()!.Trim();
|
||||
|
||||
Assert.Equal("# Heading Two\n Content", content);
|
||||
Assert.Equal("Heading One", first.Get<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseNoHeader()
|
||||
{
|
||||
using MarkdownTestContext context = this.CreateContext();
|
||||
|
||||
List<Entity> input =
|
||||
new() { new Entity().Set(IsMarkdown.Instance).SetTextContent("Content"), };
|
||||
|
||||
ParseMarkdownHeadingOne op = context
|
||||
.Resolve<ParseMarkdownHeadingOne>()
|
||||
.WithAddModelCallback((entity, heading) => entity.Set(heading));
|
||||
|
||||
IEnumerable<Entity> output = op.Run(input);
|
||||
Entity first = output.First();
|
||||
string content = first.GetTextContentString()!.Trim();
|
||||
|
||||
Assert.Equal("Content", content);
|
||||
Assert.False(first.Has<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseSimpleHeader()
|
||||
{
|
||||
using MarkdownTestContext context = this.CreateContext();
|
||||
|
||||
List<Entity> input =
|
||||
new()
|
||||
{
|
||||
new Entity().Set(IsMarkdown.Instance).SetTextContent("# Heading One\n\nContent"),
|
||||
};
|
||||
|
||||
ParseMarkdownHeadingOne op = context
|
||||
.Resolve<ParseMarkdownHeadingOne>()
|
||||
.WithAddModelCallback((entity, heading) => entity.Set(heading));
|
||||
|
||||
IEnumerable<Entity> output = op.Run(input);
|
||||
Entity first = output.First();
|
||||
string content = first.GetTextContentString()!.Trim();
|
||||
|
||||
Assert.Equal("Content", content);
|
||||
Assert.Equal("Heading One", first.Get<string>());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue