Skip to content

Repository files navigation

Markdown.FileConverter

Converts DOCX, PDF, XLSX, XLS, CSV and images into a single CommonMark .md file — without truncation, at any input size.

Two properties define the library:

  1. Extension-aware strategy selection. The extension picks the converter, validated against the file's magic bytes and container, so a DOCX named report.pdf is either handled correctly or rejected loudly — never garbled.
  2. Nothing is lost. The pipeline streams end to end (IAsyncEnumerable<DocumentBlock> from converter to writer), so a 250,000-row CSV or a 5 MB PDF converts in bounded memory with every row, page and cell present in the output.

Images and scanned PDF pages are read with OCR.

Full design: docs/PRODUCT-SPEC.md.

Install

dotnet add package Markdown.FileConverter

Use

using Markdown.FileConverter.Abstractions;
using Markdown.FileConverter.Core;

var service = ConversionService.CreateDefault();

// File in, file out.
ConversionReport report = await service.ConvertFileAsync("statement.pdf", "statement.md");

// Or stream in, stream out.
using var source = await SourceDocument.FromStreamAsync(upload, "upload.docx");
await service.ConvertAsync(source, outputStream);

With dependency injection:

services.AddMarkdownFileConverter();          // registers every built-in converter
services.AddSingleton<IOcrEngine, MyOcr>();   // or swap a piece

Adding a format takes one IDocumentConverter, a SourceFormat member, and a registration — no change to the pipeline:

public sealed class RtfConverter : IDocumentConverter
{
    public IReadOnlySet<SourceFormat> SupportedFormats { get; } = new HashSet<SourceFormat> { SourceFormat.Rtf };

    public bool CanConvert(SourceDocument source) => true;

    public async IAsyncEnumerable<DocumentBlock> ReadAsync(
        SourceDocument source, ConversionOptions options, [EnumeratorCancellation] CancellationToken ct = default)
    {
        yield return new HeadingBlock(1, [new InlineRun("Title")]);
        // ... yield blocks lazily; never buffer the document
    }
}

Options that matter

Option Default Effect
Flavor CommonMark Strict CommonMark. GitHub adds pipe tables, alerts and footnotes.
DetectionPolicy Strict Sniff converts by content on mismatch, Trust believes the extension.
Ocr Auto OCR images, and PDF pages with almost no extractable text. Always / Never override.
CellValues Display Spreadsheet cells as shown in Excel. Raw emits stored values.
ImageMode Reference Sidecar <name>.assets/ folder. Embed inlines data URIs; Omit keeps alt text only.
IncludeFrontMatter true YAML provenance block (filename, SHA-256, format, size).
MaxTableWidth 50 Wider tables render as labelled records — a shape change, never a data loss.

Output is deterministic: same input plus same options gives byte-identical markdown (the front-matter timestamp is opt-in for exactly this reason).

CommonMark and tables

CommonMark has no table, alert or footnote syntax — those are GitHub extensions. In the default flavor, tabular data is written as hard-broken rows (every cell intact, each row on its own line), notes become blockquotes, and footnotes become a numbered ## Notes section. Set Flavor = MarkdownFlavor.GitHub when the target renderer is GitHub and you want real pipe tables.

OCR

The default engine is Tesseract, which needs language data. Point at it with ConversionOptions.TessDataPath, the TESSDATA_PREFIX environment variable, or a tessdata folder beside the application. When OCR is unavailable, conversion still succeeds: the image is referenced and a warning is written into the document rather than a silent blank.

Tesseract reads print, not handwriting. On the OCR fixtures it recovers every printed label of a scanned form (62% mean confidence) but garbles the handwritten entries, and drops to 18-32% on fully handwritten pages. The converter always states the confidence rather than passing weak text off as content. For handwriting, implement IOcrEngine against a cloud reader (Azure AI Vision Read, Google Vision, AWS Textract) and pass it to ConversionService.CreateDefault(engine) — no other code changes.

Image decoding and preprocessing (EXIF orientation, greyscale, deskew, contrast stretch, upscale toward 300 DPI) run on Magick.NET, which is Apache-2.0.

Tests

dotnet test

125 tests, including conversion of the production-compliant documents in tests/Markdown.FileConverter.Tests/TestCase-Files. Each of those is verified against an independent extraction of the same file (OpenXML for DOCX/XLSX, PdfPig for PDF): every character of the source must appear in the markdown, in order. Generated fixtures cover 250,000-row CSVs and 100,000-row workbooks with exact row-count assertions and a memory budget.

Licensing

This package is MIT. Its dependencies are not all the same licence, which matters if you redistribute:

Dependency Purpose Licence
DocumentFormat.OpenXml DOCX, XLSX MIT
UglyToad.PdfPig PDF text and positions Apache-2.0
CsvHelper CSV parsing MS-PL / Apache-2.0
Magick.NET-Q8-AnyCPU image decode and preprocessing Apache-2.0
Tesseract OCR Apache-2.0
PDFtoImage / PDFium PDF page rasterisation MIT / BSD-3-Clause
NPOI legacy .xls Apache-2.0 with an OSMF licence acceptance prompt

NPOI asks consuming projects to accept its OSMF licence. That prompt does not flow through this package, but review it if .xls support matters to you.

Known limits

  • .xls (BIFF8) has no managed streaming reader, so it is read in full; large legacy workbooks are reported with a warning. Convert to .xlsx for streaming.
  • PDF reading order for heavily designed layouts is best-effort; low-confidence regions are kept as text so content survives even when structure does not.
  • Charts, SmartArt and pivot tables are noted, not rendered.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages