C# 解析 Markdown Front Matter
0 | 2,294 |
前陣子試了在 ASP.NET Core 網站即時顯示 Markdown 文件,如此可用 Visual Studio 寫 Markdown,跟網站一併部署就直接轉網頁,不用依賴 Hugo 之類的第三方工具,對線上說明之類的網站附屬文件挺方便。順著這個概念繼續擴充,我還想自動產生文件清單,用來管理公告、FAQ 之類會持續成長的文件庫,感覺也不賴。
要實現這個想法,每份 Markdown 需要註明標題、發佈時間等額外資訊,最簡單的做法是在 Markdown 最上方加入一段稱為 Front Matter 的 YAML 語法,Front Matter 普遍應用在 Markdown,故很容易找到現成程式庫或工具。
Front Matter 範例:
---
title: "標題"
slug: "文件 URL 名稱"
description:
author:
date: 2019-08-22T15:20:28.000Z
lastmod: 2019-08-22T15:20:28.000Z
draft: true
tags: []
categories: []
---
這裡開始寫 Markdown 本文...
上回介紹過的 C# Markdown 程式庫首選 MarkDig 可識別 Front Matter,但解析 YAML 需另外想辦法。自己寫 Regex 是種解法,但要能完整涵蓋 YAML 完整語法得花點功夫,想了一下,借用現成程式庫比較省事可靠。YAML 程式庫的選擇不多,沒有選擇困難的問題,用 YamlDotNet。
研究了一下,MarkDig + YamlDotNet 處理內含 Front Matter Markdown 的寫法有點曲折,所以有了這篇筆記。
解析範例程式如下,詳細做法請參考註解。
using Markdig;
using Markdig.Extensions.Yaml;
using Markdig.Renderers;
using Markdig.Syntax;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
foreach (var file in Directory.GetFiles("Markdowns", "*.md"))
{
var p = ParseMardownWithFrontMatter(file);
Console.WriteLine($"File: {file}");
Console.WriteLine($" * Title = {p.Title}");
Console.WriteLine($" * Date = {p.Date:yyyy-MM-dd}");
Console.WriteLine($" * Html = {p.Html}");
}
PostEntry ParseMardownWithFrontMatter(string path)
{
// 建立能識別 YAML 的 Pipeline
var pipeline = new MarkdownPipelineBuilder()
.UseYamlFrontMatter().Build();
// 建立 TextWriter 及 HtmlRender
using var sw = new StringWriter();
var render = new HtmlRenderer(sw);
pipeline.Setup(render);
// 預設由檔案資訊決定標題跟時間
var postEntry = new PostEntry
{
Title = path,
Date = new FileInfo(path).LastWriteTime
};
try
{
var markdown = File.ReadAllText(path);
// 套用先前建立的 Pipeline
var doc = Markdown.Parse(markdown, pipeline);
// 取出 Markdown 第一個 YAML
var yamlBlock = doc.Descendants<YamlFrontMatterBlock>().FirstOrDefault();
// 若有包含 YAML
if (yamlBlock != null)
{
// 依 YAML 在 Markdown 內容的起始位置及長度取出 YAML 字串
var yaml = markdown.Substring(yamlBlock.Span.Start, yamlBlock.Span.Length);
using (var input = new StringReader(yaml))
{
// 使用 YAML Parser 開始解析
var yamlParser = new Parser(input);
yamlParser.Consume<StreamStart>();
yamlParser.Consume<DocumentStart>();
// 建立 YAML 反序列化工具
var yamlDes = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();
// 解析 YAML 內容,對映到資料型別的屬性上
var frontMatter = yamlDes.Deserialize<PostEntry>(yamlParser);
yamlParser.Consume<DocumentEnd>();
postEntry.Title = frontMatter.Title;
postEntry.Date = frontMatter.Date;
}
}
// 使用 HtmlRenderer 產生 HTML 內容
render.Render(doc);
sw.Flush();
postEntry.Html = sw.ToString();
}
catch (Exception ex) {
postEntry.Html = ex.ToString();
}
return postEntry;
}
public class PostEntry
{
public string Title { get; set; } = string.Empty;
public DateTime Date { get; set; }
public string Html { get; set; } = string.Empty;
}
我準備了四個測試檔:沒有 YAML、YAML 格式不對、只有標題 跟 資訊完整
實測成功!
範例專案已上傳 Github
【參考資料】
- Rendering Markdown to HTML and Parsing YAML Front Matter in C# by Mark Heath
- How to get YAML front matter?
Example of parsing Markdown document with front matter in C#.
Comments
Be the first to post a comment