💡 Полный рабочий пример доступен на GitHub:
parse-docx-to-markdown-chunks-dotnet

Старый способ был болезненным

Разделение Markdown по заголовкам — это путь подготовки извлечения в .NET, который преобразует файлы Office и PDF в текст размером с раздел перед встраиванием. Без этого команды часто загружают весь файл Word в один вектор. Вводный абзац и пункт обязательств делят одно встраивание, поэтому поиск по косинусу ранжирует неправильный раздел. Я столкнулся с этим в трехстраничном внутреннем руководстве: каждый запрос сначала вытягивал блок заголовка, потому что индекс никогда не видел структуру # / ##, которую уже написали авторы.

Самостоятельная реализация конвертера DOCX-to-Markdown подразумевает обход стилей Open XML, сопоставление уровней списков и всё равно приводит к сбоям, когда следующая загрузка — PDF, путь форматированного Markdown которого возвращает null. Окна фиксированного размера токенов игнорируют эти заголовки. За ночь корпус данных смешивает пустые оболочки с огромными руководствами, потому что никто не проверял количество страниц или размер заранее.

Настоящая цена встраиваний целых файлов: ответы из неправильных разделов, потраченные впустую ресурсы на извлечение из мусорных загрузок и отсутствие артефактов chunk-01.md, которые операторы могли бы открыть при плохой партии.

Есть лучший способ

GroupDocs.Parser for .NET заменяет эту путаницу четырьмя целевыми вызовами: GetDocumentInfo, GetFormattedText(FormattedTextMode.Markdown), GetText когда Markdown возвращает null, и небольшой обход заголовков, который записывает chunk-NN.md. Тот же тип Parser покрывает разбор Word DOCX и резервный текст PDF, поэтому вы сохраняете один путь службы C#.

Вам понадобится:

  • .NET 8 SDK
  • GroupDocs.Parser 26.10.0 (temporary license)
  • Word DOCX (пример состоит из 3 страниц / 14820 байт) и, при желании, PDF
dotnet add package GroupDocs.Parser --version 26.10.0

Новый способ: проверка, извлечение, откат, разделение

Вот тот же процесс подготовки извлечения после переключения. Консольный пример в parse-docx-to-markdown-chunks-dotnet выводит информацию, Markdown, текст PDF и четыре фрагмента заголовков для поставляемого DOCX.

Шаг 1 — проверка с GetDocumentInfo

Запросите тип файла, количество страниц и размер перед тем, как платить за полное извлечение.

using var parser = new Parser(sourcePath);
var info = parser.GetDocumentInfo();

var sb = new StringBuilder();
sb.AppendLine($"Source: {Path.GetFileName(sourcePath)}");
sb.AppendLine($"File type: {info?.FileType}");
sb.AppendLine($"Pages: {info?.PageCount}");
sb.AppendLine($"Size: {info?.Size} bytes");
File.WriteAllText(outputPath, sb.ToString());

Запишите эти факты рядом с идентификатором задания. В примере DOCX вы должны увидеть Microsoft Word Open XML Document, 3 страницы и 14820 байт.

Шаг 2 — предпочтительно использовать Markdown из GetFormattedText

Для Word и форматов, поддерживающих форматированное извлечение, сохраняйте ATX‑заголовки для разделителя.

using var parser = new Parser(sourcePath);
using TextReader? reader = parser.GetFormattedText(
    new FormattedTextOptions(FormattedTextMode.Markdown));

if (reader == null)
{
    return null;
}

string markdown = reader.ReadToEnd();
File.WriteAllText(outputPath, markdown, Encoding.UTF8);
return markdown;

null — это сигнал переключить путь, а не сбой. Записывайте в UTF-8, чтобы инструменты фрагментов и diff‑ы git оставались читаемыми.

Шаг 3 — откат с GetText для PDF

Когда режим Markdown возвращает null, сохраняйте файл в конвейере с уровнем простого текста.

using var parser = new Parser(sourcePath);
using TextReader? reader = parser.GetText();
if (reader == null)
{
    return null;
}

string text = reader.ReadToEnd();
File.WriteAllText(outputPath, text, Encoding.UTF8);
return text;

Ожидайте .txt, а не придуманные заголовки #. Пример хоста записывает Result/sample-pdf.txt для ветки PDF.

Шаг 4 — разделить Markdown по # и

Обходите строки; сбрасывайте, когда появляется новый ATX # или ##; записывайте chunk-NN.md.

var chunks = new List<string>();
var current = new StringBuilder();
using (var reader = new StringReader(markdown))
{
    string? line;
    while ((line = reader.ReadLine()) != null)
    {
        bool isHeading = line.StartsWith("# ", StringComparison.Ordinal)
            || line.StartsWith("## ", StringComparison.Ordinal);
        if (isHeading && current.Length > 0)
        {
            chunks.Add(current.ToString().TrimEnd());
            current.Clear();
        }
        current.AppendLine(line);
    }
}

Сначала удалите устаревшие файлы chunk-*.md. Более глубокие строки ### остаются внутри текущего раздела. Пример DOCX создает четыре файла в Result/chunks/.

Когда следует заменить встраивание целых файлов этим путем?

Заменяйте встраивание целых файлов, когда входные данные смешивают Word DOCX и PDF, а индексу нужен Markdown размером с раздел. Сначала вызывайте GetDocumentInfo, пробуйте Markdown для файлов Office, вызывайте GetText, когда форматированный читатель возвращает null, и разделяйте только при наличии маркеров # / ##. Оставляйте неструктурированный текст PDF нетронутым, пока не добавите отдельную политику фрагментов. Такой порядок соответствует примеру консольного приложения и делает режимы ошибок явными для операторов.

Что изменилось для операторов

До После
Одно встраивание на файл Одно встраивание на блок заголовка
Пользовательский Open XML Markdown GetFormattedText режим Markdown
PDF отбрасывается при null у форматированного читателя GetText записывает текстовый артефакт
Отсутствие дешевого предварительного контроля Работают проверки количества страниц и размера

Освобождайте каждый Parser и TextReader. Примените лицензию или временная лицензия перед ночным объёмом. Рассматривайте загрузки как недоверенный ввод и парсите их в изолированном воркере.

Попробуйте на своих файлах

Клонируйте parse-docx-to-markdown-chunks-dotnet, восстановите GroupDocs.Parser 26.10.0 и запустите против documents/sample.docx. Затем примените те же четыре метода к одному из ваших руководств и откройте сгенерированные файлы chunk-*.md.