Convert Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, and PDF documents into clean GitHub-Flavored Markdown.
Elixir bindings for the anydoc Rust crate by Firecrawl. Every format parses into one shared document model and renders through a single Markdown serializer, so headings, tables, lists, and footnotes come out the same no matter which format goes in. Conversion runs as a NIF on dirty CPU schedulers, so it never blocks the BEAM's normal schedulers.
Usage
# From a file path:
{:ok, markdown} = Anydoc.to_markdown("report.docx")
# From bytes, with the format detected from the content:
{:ok, markdown} = Anydoc.to_markdown_bytes(bytes)
# Or name it, which signature-less formats (CSV) need:
{:ok, markdown} = Anydoc.to_markdown_bytes(bytes, :csv)
# Or stop at the document model, which also carries embedded assets:
{:ok, %Anydoc.Document{} = document} = Anydoc.to_document(bytes)Each function has a bang variant (to_markdown!/1, to_markdown_bytes!/2,
to_document!/2) that returns the value directly and raises
Anydoc.Error on failure.
Supported formats
| Format | Atom | Extensions |
|---|---|---|
| Word | :doc, :docx | .doc, .docx, .docm |
| PowerPoint | :ppt, :pptx | .ppt, .pps, .pot, .pptx, .pptm, .ppsx, .ppsm |
| Excel | :excel | .xls, .xlsx, .xlsm, .xlsb |
| OpenDocument | :odt, :ods, :odp | .odt, .ods, .odp |
| Rich Text Format | :rtf | .rtf |
| EPUB | :epub | .epub |
| CSV | :csv | .csv |
:pdf | .pdf |
Container variants that share a parser map onto one atom: .docm is
:docx, .pptm/.ppsx/.ppsm are :pptx, and every Excel container
is :excel.
Format detection
The format is read from the file content, using the marker its
specification designates: the PDF header, the RTF open group, OLE stream
names, the ZIP package mimetype and content types. CSV has no such
marker, so detection returns nil for it and the extension, or an
explicit format, names it instead.
iex> Anydoc.format_from_bytes("%PDF-1.7 ...")
:pdf
iex> Anydoc.format_from_bytes("just some text")
nil
iex> Anydoc.format_from_extension("pptm")
:pptx
iex> Anydoc.format_from_path("report.odt")
:odtErrors
A conversion fails only when no meaningful Markdown could come out of the
input. The error's :code names what went wrong — see Anydoc.Error for
the full table.
iex> {:error, %Anydoc.Error{code: :unsupported}} =
...> Anydoc.to_markdown_bytes("not a document")Passing anything but a format/0 atom (or nil) is a contract
violation and raises FunctionClauseError:
iex> Anydoc.to_markdown_bytes("a,b\n1,2", :nonsense)
** (FunctionClauseError) no function clause matching in Anydoc.to_markdown_bytes/2
Summary
Functions
Detects the format from the content itself: the signature and identity each container specification designates (PDF header, RTF open group, OLE stream names, ZIP package mimetype/content types).
The format a bare extension names (a leading dot is allowed), matched
case-insensitively. nil for anything unrecognized.
The format a path's extension names. nil when the path has no extension
or names nothing recognized.
Lists every format atom this library accepts.
Parses an in-memory document into an Anydoc.Document, which carries the
rendered Markdown plus every embedded asset's bytes.
Same as to_document/2, but returns the Anydoc.Document directly and
raises Anydoc.Error on failure.
Converts a document file to Markdown.
Same as to_markdown/1, but returns the Markdown directly and raises
Anydoc.Error on failure.
Converts an in-memory document to Markdown.
Same as to_markdown_bytes/2, but returns the Markdown directly and
raises Anydoc.Error on failure.
Types
@type format() ::
:doc
| :docx
| :odt
| :pdf
| :ppt
| :pptx
| :rtf
| :epub
| :excel
| :ods
| :odp
| :csv
A document format, named by the parser that reads it.
format_from_extension/1 maps container variants (.docm, .pptm,
.xlsb, ...) onto these.
Functions
Detects the format from the content itself: the signature and identity each container specification designates (PDF header, RTF open group, OLE stream names, ZIP package mimetype/content types).
Plain-text formats (CSV) carry no signature and return nil; so does
anything unrecognized.
Examples
iex> Anydoc.format_from_bytes("{\\rtf1 hello}")
:rtf
iex> Anydoc.format_from_bytes("a,b\n1,2")
nil
The format a bare extension names (a leading dot is allowed), matched
case-insensitively. nil for anything unrecognized.
Examples
iex> Anydoc.format_from_extension("docx")
:docx
iex> Anydoc.format_from_extension(".XLSB")
:excel
iex> Anydoc.format_from_extension("txt")
nil
The format a path's extension names. nil when the path has no extension
or names nothing recognized.
Examples
iex> Anydoc.format_from_path("slides/deck.PPTM")
:pptx
iex> Anydoc.format_from_path("README")
nil
@spec formats() :: [format()]
Lists every format atom this library accepts.
Examples
iex> :csv in Anydoc.formats()
true
iex> length(Anydoc.formats())
12
@spec to_document(binary(), format() | nil) :: {:ok, Anydoc.Document.t()} | {:error, Anydoc.Error.t()}
Parses an in-memory document into an Anydoc.Document, which carries the
rendered Markdown plus every embedded asset's bytes.
Pass a format/0 to select the parser, or nil to detect it from the
content. Unsupported for :pdf: PDF conversion produces Markdown
directly and has no document-model form; use to_markdown_bytes/2.
Returns {:ok, %Anydoc.Document{}} or {:error, %Anydoc.Error{}}.
Examples
iex> {:ok, document} = Anydoc.to_document("a,b\n1,2", :csv)
iex> document.markdown
"| a | b |\n| --- | --- |\n| 1 | 2 |\n"
iex> document.assets
[]
@spec to_document!(binary(), format() | nil) :: Anydoc.Document.t()
Same as to_document/2, but returns the Anydoc.Document directly and
raises Anydoc.Error on failure.
@spec to_markdown(Path.t()) :: {:ok, String.t()} | {:error, Anydoc.Error.t()}
Converts a document file to Markdown.
The format is detected from the file content (format_from_bytes/1);
the extension is the fallback for signature-less formats (CSV) and
unrecognizable containers.
Returns {:ok, markdown}, or {:error, %Anydoc.Error{}} when the file
could not be read (code :io) or converted.
Examples
{:ok, markdown} = Anydoc.to_markdown("report.docx")
iex> {:error, %Anydoc.Error{code: :io}} = Anydoc.to_markdown("no/such/file.docx")
iex> {:error, %Anydoc.Error{code: :unsupported}} = Anydoc.to_markdown("mix.exs")
Same as to_markdown/1, but returns the Markdown directly and raises
Anydoc.Error on failure.
@spec to_markdown_bytes(binary(), format() | nil) :: {:ok, String.t()} | {:error, Anydoc.Error.t()}
Converts an in-memory document to Markdown.
Pass a format/0 to select the parser, or nil to detect it from the
content (format_from_bytes/1), which signature-less formats (CSV) have
to name explicitly.
Returns {:ok, markdown} or {:error, %Anydoc.Error{}}.
Examples
iex> Anydoc.to_markdown_bytes("name,score\nada,100", :csv)
{:ok, "| name | score |\n| --- | --- |\n| ada | 100 |\n"}
iex> {:error, %Anydoc.Error{code: :unsupported}} =
...> Anydoc.to_markdown_bytes("name,score\nada,100")
Same as to_markdown_bytes/2, but returns the Markdown directly and
raises Anydoc.Error on failure.
Examples
iex> Anydoc.to_markdown_bytes!("a\n1", :csv)
"| a |\n| --- |\n| 1 |\n"