Anydoc (anydoc_ex v0.1.0)

Copy Markdown View Source

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

FormatAtomExtensions
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.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")
:odt

Errors

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

Types

A document format, named by the parser that reads it.

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

format()

@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

format_from_bytes(bytes)

@spec format_from_bytes(binary()) :: format() | nil

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

format_from_extension(extension)

@spec format_from_extension(String.t()) :: format() | 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

format_from_path(path)

@spec format_from_path(Path.t()) :: format() | 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

formats()

@spec formats() :: [format()]

Lists every format atom this library accepts.

Examples

iex> :csv in Anydoc.formats()
true
iex> length(Anydoc.formats())
12

to_document(bytes, format \\ nil)

@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
[]

to_document!(bytes, format \\ nil)

@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.

to_markdown(path)

@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")

to_markdown!(path)

@spec to_markdown!(Path.t()) :: String.t()

Same as to_markdown/1, but returns the Markdown directly and raises Anydoc.Error on failure.

to_markdown_bytes(bytes, format \\ nil)

@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")

to_markdown_bytes!(bytes, format \\ nil)

@spec to_markdown_bytes!(binary(), format() | nil) :: String.t()

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"