Markdig для C# (.NET): Полное руководство
Markdig — это высокопроизводительный, полностью управляемый парсер Markdown для платформы .NET, написанный на C#. Он предоставляет разработчикам мощный и гибкий инструмент для преобразования Markdown-текста в HTML, XAML и другие форматы. В отличие от многих других библиотек, Markdig спроектирован с акцентом на производительность, расширяемость и строгое соответствие спецификациям CommonMark. Это делает его идеальным выбором для создания систем управления контентом, блогов, документационных сайтов, чат-приложений и любых других проектов, где требуется обработка Markdown.
Зачем нужна эта библиотека? В современной веб-разработке Markdown стал стандартом де-факто для написания документации, статей и сообщений. Однако, чтобы отобразить его в браузере, необходимо преобразовать его в HTML. Markdig решает эту задачу быстро и эффективно. Кроме того, он поддерживает множество расширений, таких как таблицы, сноски, зачёркнутый текст, авто-ссылки, эмодзи (через дополнительные пакеты) и многое другое, что выходит за рамки базового CommonMark. Вы можете легко настроить конвейер обработки, подключая только те расширения, которые вам нужны, что позволяет контролировать как функциональность, так и производительность.
Markdig также известен своей надёжностью. Он активно используется в таких крупных проектах, как статический генератор сайтов Statiq, платформа для документации DocFX и многие другие. Благодаря модульной архитектуре, вы можете не только использовать встроенные расширения, но и создавать свои собственные, что открывает практически безграничные возможности для кастомизации. В этой статье мы подробно рассмотрим установку, основные методы, приведём практические примеры и дадим советы по эффективному использованию Markdig.
Установка
Установка Markdig в проект .NET выполняется через менеджер пакетов NuGet. Вы можете использовать консоль диспетчера пакетов, интерфейс Visual Studio или .NET CLI. Ниже приведены все три способа.
Через .NET CLI (рекомендуется):
dotnet add package Markdig
Через консоль диспетчера пакетов (Package Manager Console):
Install-Package Markdig
Через интерфейс Visual Studio:
Откройте "Управление пакетами NuGet" (ПКМ по проекту -> Управление пакетами NuGet), найдите "Markdig" и нажмите "Установить".
После установки в ваш проект будет добавлена ссылка на сборку Markdig.dll, и вы сможете начать использовать библиотеку.
Быстрый старт
Давайте начнём с самого простого примера: преобразуем строку Markdown в HTML.
using Markdig;
string markdownContent = "# Привет, мир!\n\nЭто **жирный** текст и *курсив*.";
string htmlContent = Markdown.ToHtml(markdownContent);
Console.WriteLine(htmlContent);
// Вывод:
// <h1>Привет, мир!</h1>
// <p>Это <strong>жирный</strong> текст и <em>курсив</em>.</p>
Как видите, всё очень просто. Статический метод Markdown.ToHtml() делает всю работу за вас. Однако, это лишь вершина айсберга. Далее мы рассмотрим более продвинутые возможности.
Основные методы и классы
Для более тонкой настройки обработки Markdown, Markdig предоставляет набор классов и методов. Рассмотрим 8 ключевых из них.
1. Markdown.ToHtml(string markdown)
Сигнатура: public static string ToHtml(string markdown)
Что делает: Преобразует строку Markdown в строку HTML, используя стандартный конвейер (Pipeline) с расширениями CommonMark по умолчанию.
string html = Markdown.ToHtml("**Hello** *World*");
Console.WriteLine(html); // <p><strong>Hello</strong> <em>World</em></p>
2. Markdown.ToHtml(string markdown, MarkdownPipeline pipeline)
Сигнатура: public static string ToHtml(string markdown, MarkdownPipeline pipeline)
Что делает: Преобразует Markdown в HTML, используя предоставленный вами конвейер обработки. Это позволяет подключать или отключать конкретные расширения.
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions() // Включает ВСЕ расширения
.Build();
string html = Markdown.ToHtml("~~Зачёркнутый текст~~", pipeline);
Console.WriteLine(html); // <p><del>Зачёркнутый текст</del></p>
3. Markdown.ToPlainText(string markdown, MarkdownPipeline pipeline)
Сигнатура: public static string ToPlainText(string markdown, MarkdownPipeline pipeline)
Что делает: Извлекает из Markdown чистый текст, удаляя всю разметку. Полезно для создания кратких описаний (excerpts) или мета-тегов.
var pipeline = new MarkdownPipelineBuilder().Build();
string plainText = Markdown.ToPlainText("# Заголовок\n\nКакой-то **текст**.", pipeline);
Console.WriteLine(plainText); // Заголовок Какой-то текст.
4. MarkdownPipelineBuilder
Сигнатура: Класс. Основные методы: UseXXXX(), Build().
Что делает: Это строитель (Builder) для конфигурации конвейера обработки. Позволяет по цепочке добавлять расширения, а затем вызвать Build() для создания финального MarkdownPipeline.
var pipeline = new MarkdownPipelineBuilder()
.UseEmphasisExtras() // Дополнительные способы выделения (например, ~~)
.UseTables() // Поддержка таблиц
.UseAutoLinks() // Автоматическое преобразование URL в ссылки
.Build();
5. MarkdownPipeline
Сигнатура: Класс. Свойства: Extensions.
Что делает: Представляет собой сконфигурированный конвейер, который управляет процессом парсинга и рендеринга. Экземпляр этого класса передаётся в методы ToHtml и ToPlainText. Рекомендуется создавать один экземпляр и кешировать его для многократного использования.
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
// pipeline теперь можно использовать многократно.
6. MarkdownDocument
Сигнатура: public MarkdownDocument Parse(string markdown, MarkdownPipeline pipeline)
Что делает: Парсит Markdown и возвращает абстрактное синтаксическое дерево (AST) в виде объекта MarkdownDocument. Это позволяет программно анализировать и модифицировать структуру документа перед рендерингом.
var pipeline = new MarkdownPipelineBuilder().Build();
var document = Markdown.Parse("## Заголовок\n\nАбзац.", pipeline);
// Обходим все блоки верхнего уровня
foreach (var block in document)
{
Console.WriteLine($"Тип блока: {block.GetType().Name}");
}
7. HtmlRenderer
Сигнатура: public HtmlRenderer(TextWriter writer)
Что делает: Класс для рендеринга AST (MarkdownDocument) в HTML. Позволяет более гибко контролировать процесс вывода, например, писать результат в StringBuilder или файловый поток.
var pipeline = new MarkdownPipelineBuilder().Build();
var document = Markdown.Parse("**Текст**", pipeline);
using (var writer = new StringWriter())
{
var renderer = new HtmlRenderer(writer);
pipeline.Setup(renderer); // Настройка рендерера для конвейера
renderer.Render(document); // Рендеринг документа
writer.Flush();
Console.WriteLine(writer.ToString()); // <p><strong>Текст</strong></p>
}
Сигнатура: public static MarkdownPipelineBuilder UseAdvancedExtensions(this MarkdownPipelineBuilder pipeline)
Что делает: Метод расширения, который одним вызовом включает почти все доступные расширения Markdig, за исключением нескольких экспериментальных. Это самый быстрый способ получить максимальную функциональность.
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
// Теперь доступны: таблицы, сноски, зачёркивание, авто-ссылки, эмодзи (через доп.пакет) и т.д.
Реальный практический пример: Генератор HTML-документации
Представьте, что вам нужно создать простой генератор статической документации. Он будет читать Markdown-файлы из папки, преобразовывать их в HTML и сохранять в другую папку. Для этого мы напишем консольное приложение.
using Markdig;
using System.Text;
class Program
{
static void Main(string[] args)
{
string inputDir = "./docs/markdown";
string outputDir = "./docs/html";
string templateFile = "./template.html";
// 1. Создаём и кешируем конвейер
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
// 2. Читаем HTML-шаблон
string htmlTemplate = File.ReadAllText(templateFile);
// 3. Получаем все .md файлы из входной директории
var markdownFiles = Directory.GetFiles(inputDir, "*.md", SearchOption.AllDirectories);
foreach (var mdFile in markdownFiles)
{
// 4. Читаем содержимое Markdown
string markdownContent = File.ReadAllText(mdFile);
// 5. Преобразуем Markdown в HTML
string bodyHtml = Markdown.ToHtml(markdownContent, pipeline);
// 6. Извлекаем заголовок первого уровня для тега <title>
string title = ExtractTitle(markdownContent);
// 7. Вставляем сгенерированный HTML и заголовок в шаблон
string finalHtml = htmlTemplate
.Replace("{{TITLE}}", title)
.Replace("{{CONTENT}}", bodyHtml);
// 8. Формируем путь для выходного файла
string relativePath = Path.GetRelativePath(inputDir, mdFile);
string outputFilePath = Path.Combine(outputDir, Path.ChangeExtension(relativePath, ".html"));
// 9. Создаём директорию, если её нет
Directory.CreateDirectory(Path.GetDirectoryName(outputFilePath));
// 10. Сохраняем результат
File.WriteAllText(outputFilePath, finalHtml, Encoding.UTF8);
Console.WriteLine($"Сгенерирован: {outputFilePath}");
}
Console.WriteLine("Генерация завершена.");
}
/// <summary>
/// Извлекает текст первого заголовка H1 (#) из Markdown.
/// </summary>
static string ExtractTitle(string markdown)
{
var lines = markdown.Split('\n');
foreach (var line in lines)
{
var trimmed = line.TrimStart();
if (trimmed.StartsWith("# ") && !trimmed.StartsWith("## "))
{
return trimmed.Substring(2).Trim();
}
}
return "Документация";
}
}
Шаблон (template.html):
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{TITLE}}</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<article>
{{CONTENT}}
</article>
</body>
</html>
Этот пример демонстрирует: работу с файловой системой, использование кешированного конвейера, базовую обработку Markdown, интеграцию с HTML-шаблоном и пакетную обработку файлов. Вы можете легко расширить его, добавив, например, генерацию оглавления или поддержку мета-данных (YAML front matter).
Советы по производительности и типичные ошибки
Советы по производительности
- Кешируйте конвейер (Pipeline): Создание
MarkdownPipeline— относительно дорогая операция. Создайте его один раз и используйте повторно для всех вызововToHtml. Никогда не создавайте новый конвейер для каждого вызова. - Используйте только нужные расширения: Не подключайте
UseAdvancedExtensions(), если вам нужна только базовая поддержка CommonMark. Каждое расширение добавляет накладные расходы. Собирайте конвейер только из необходимых компонентов. - Для больших объёмов используйте
HtmlRendererиTextWriter: Если вы обрабатываете тысячи документов, работа черезHtmlRendererс буферизированнымTextWriter(например,StreamWriter) может быть эффективнее, чем работа с большими строками. - Избегайте повторного парсинга: Если вам нужно и преобразовать в HTML, и извлечь plain text, распарсите документ один раз через
Markdown.Parse(), а затем используйте два разных рендерера (HtmlRendererиPlainTextRenderer) для одного и того жеMarkdownDocument.