View Source MDEx.Document (MDEx v0.3.0)
Tree representation of a CommonMark document.
%MDEx.Document{
nodes: [
%MDEx.Paragraph{
nodes: [
%MDEx.Code{num_backticks: 1, literal: "Elixir"}
]
}
]
}
Each node may contain attributes and children nodes as in the example above where MDEx.Document
contains a MDEx.Paragraph
node which contains a MDEx.Code
node with the attributes :num_backticks
and :literal
.
You can check out each node's documentation in the Document Nodes
section, for example MDEx.HtmlBlock
.
The wrapping MDEx.Document
module represents the root of a document and it implements some behaviours and protocols
to enable operations to fetch, update, and manipulate the document tree.
Let's go through these operations in the examples below.
In these examples we will be using the ~M and ~m to format the content, see their documentation for more info.
String.Chars
Calling Kernel.to_string/1
or interpolating the document AST will format it as CommonMark text.
iex> to_string(~M[# Hello])
"# Hello"
Fragments (nodes without the parent %Document{}
) are also formatted:
iex> to_string(%MDEx.Heading{nodes: [%MDEx.Text{literal: "Hello"}], level: 1})
"# Hello"
And finally interpolation as well:
iex> lang = "elixir"
iex> to_string(~m[`#{lang}`])
"`elixir`"
Access
The Access
behaviour gives you the ability to fetch and update nodes using different types of keys.
Starting with a simple Markdown document with a single heading and a text,
let's fetch only the text node by matching the MDEx.Text
node:
iex> ~M[# Hello][%MDEx.Text{literal: "Hello"}]
[%MDEx.Text{literal: "Hello"}]
That's essentially the same as:
doc = %MDEx.Document{nodes: [%MDEx.Heading{nodes: [%MDEx.Text{literal: "Hello"}], level: 1, setext: false}]},
Enum.filter(
doc,
fn node -> node == %MDEx.Text{literal: "Hello"} end
)
The key can also be modules, atoms, and even functions! For example:
- Fetchs all Code nodes, either by
MDEx.Code
module or the:code
atom representing the Code node
iex> doc = ~M"""
...> # Languages
...>
...> `elixir`
...>
...> `rust`
...> """
iex> doc[MDEx.Code]
[%MDEx.Code{num_backticks: 1, literal: "elixir"}, %MDEx.Code{num_backticks: 1, literal: "rust"}]
iex> doc[:code]
[%MDEx.Code{num_backticks: 1, literal: "elixir"}, %MDEx.Code{num_backticks: 1, literal: "rust"}]
- Dynamically fetch Code nodes where the
:literal
(node content) starts with"eli"
using a function to filter the result
iex> doc = ~M"""
...> # Languages
...>
...> `elixir`
...>
...> `rust`
...> """
iex> doc[fn node -> String.starts_with?(Map.get(node, :literal, ""), "eli") end]
[%MDEx.Code{num_backticks: 1, literal: "elixir"}]
That's the most flexible option, in the case where struct, modules, or atoms are not enough to match the node you want.
This protocol also allows us to update nodes that matches a selector.
In the example below we'll capitalize the content of all MDEx.Code
nodes:
iex> doc = ~M"""
...> # Languages
...>
...> `elixir`
...>
...> `rust`
...>
...> Continue...
...> """
iex> update_in(doc, [:document, Access.key!(:nodes), Access.all(), :code, Access.key!(:literal)], fn literal ->
...> String.upcase(literal)
...> end)
%MDEx.Document{
nodes: [
%MDEx.Heading{nodes: [%MDEx.Text{literal: "Languages"}], level: 1, setext: false},
%MDEx.Paragraph{nodes: [%MDEx.Code{num_backticks: 1, literal: "ELIXIR"}]},
%MDEx.Paragraph{nodes: [%MDEx.Code{num_backticks: 1, literal: "RUST"}]},
%MDEx.Paragraph{nodes: [%MDEx.Text{literal: "Continue…"}]}
]
}
Enumerable
Probably the most used protocol in Elixir, it allows us to call Enum
functions to manipulate the document. Let's see some examples:
- Count the nodes in a document:
iex> doc = ~M"""
...> # Languages
...>
...> `elixir`
...>
...> `rust`
...> """
iex> Enum.count(doc)
7
- Count how many nodes have the
:literal
attribute:
iex> doc = ~M"""
...> # Languages
...>
...> `elixir`
...>
...> `rust`
...> """
iex> Enum.reduce(doc, 0, fn
...> %{literal: _literal}, acc -> acc + 1
...>
...> _node, acc -> acc
...> end)
3
- Returns true if node is member of the document:
iex> doc = ~M"""
...> # Languages
...>
...> `elixir`
...>
...> `rust`
...> """
iex> Enum.member?(doc, %MDEx.Code{literal: "elixir", num_backticks: 1})
true
- Map each node:
iex> doc = ~M"""
...> # Languages
...>
...> `elixir`
...>
...> `rust`
...> """
iex> Enum.map(doc, fn %node{} -> inspect(node) end)
["MDEx.Document", "MDEx.Heading", "MDEx.Text", "MDEx.Paragraph", "MDEx.Code", "MDEx.Paragraph", "MDEx.Code"]
Traverse and Update
Finally you can use the low-level MDEx.traverse_and_update/2
and MDEx.traverse_and_update/3
APIs
to traverse each node of the AST and either update the nodes or do some calculation with an accumulator.
Summary
Types
Fragment of a CommonMark document, a single node.
Selector to match a node in a document.
Tree representation of a CommonMark document.
Functions
Callback implementation for Access.fetch/2
.
Callback implementation for Access.get_and_update/3
.
Callback implementation for Access.fetch/2
.
Types
@type md_node() :: MDEx.FrontMatter.t() | MDEx.BlockQuote.t() | MDEx.List.t() | MDEx.ListItem.t() | MDEx.DescriptionList.t() | MDEx.DescriptionItem.t() | MDEx.DescriptionTerm.t() | MDEx.DescriptionDetails.t() | MDEx.CodeBlock.t() | MDEx.HtmlBlock.t() | MDEx.Paragraph.t() | MDEx.Heading.t() | MDEx.ThematicBreak.t() | MDEx.FootnoteDefinition.t() | MDEx.FootnoteReference.t() | MDEx.Table.t() | MDEx.TableRow.t() | MDEx.TableCell.t() | MDEx.Text.t() | MDEx.TaskItem.t() | MDEx.SoftBreak.t() | MDEx.LineBreak.t() | MDEx.Code.t() | MDEx.HtmlInline.t() | MDEx.Emph.t() | MDEx.Strong.t() | MDEx.Strikethrough.t() | MDEx.Superscript.t() | MDEx.Link.t() | MDEx.Image.t() | MDEx.ShortCode.t() | MDEx.Math.t() | MDEx.MultilineBlockQuote.t() | MDEx.Escaped.t() | MDEx.WikiLink.t() | MDEx.Underline.t() | MDEx.Subscript.t() | MDEx.SpoileredText.t() | MDEx.EscapedTag.t()
Fragment of a CommonMark document, a single node.
Selector to match a node in a document.
@type t() :: %MDEx.Document{nodes: [md_node()]}
Tree representation of a CommonMark document.
Functions
Callback implementation for Access.fetch/2
.
See the Access section for examples.
Callback implementation for Access.get_and_update/3
.
See the Access section for examples.
Callback implementation for Access.fetch/2
.
See the Access section for examples.