ExAnydoc.to_document/2 exposes anydoc's information-preserving representation
when Markdown alone is not enough. It retains blocks, inline styles, notes, and
embedded assets.
bytes = File.read!("report.docx")
{:ok, %ExAnydoc.Document{} = document} =
ExAnydoc.to_document(bytes)Traverse text
Blocks are recursive tagged tuples. A basic traversal can collect paragraph and heading text like this:
defmodule DocumentText do
def from_blocks(blocks), do: Enum.flat_map(blocks, &from_block/1)
defp from_block({:heading, %{content: content}}), do: from_inlines(content)
defp from_block({:paragraph, content}), do: from_inlines(content)
defp from_block({:block_quote, blocks}), do: from_blocks(blocks)
defp from_block(_other), do: []
defp from_inlines(inlines) do
Enum.flat_map(inlines, fn
{:text, %{text: text}} -> [text]
{:link, %{content: content}} -> from_inlines(content)
:line_break -> ["\n"]
_other -> []
end)
end
endLists, table cells, block quotes, and notes can themselves contain blocks. A
production traversal should recurse into the variants it needs. The complete
shape of every variant is documented in ExAnydoc.Document types.
Tables
A table contains a rectangular grid. An {:origin, cell} slot owns the cell
content and span. A {:covered, coordinates} slot points back to the origin of
a merged cell. header_rows identifies the leading header rows and kind is
either :data or :layout.
Assets
An image can reference {:asset, id}. Find the matching entry in
document.assets to access its media type and original bytes:
asset = Enum.find(document.assets, &(&1.id == asset_id))
File.write!("image.bin", asset.bytes)Asset bytes are held in BEAM binaries. Account for their size when processing large or untrusted documents and do not persist them without validating their media type and contents.