Markdig для C# (.NET): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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>
}
MarkdownExtensions.UseAdvancedExtensions()

Сигнатура: 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.

Рекомендаций