Gumbo.Node (gumbo v0.3.0)

Copy Markdown View Source

A node in a parsed tree, identified by its index into the output's preorder node table.

A node is only meaningful together with the Gumbo.Output handle it came from: the handle owns the tree and validates the index. A hand-built struct can carry an out-of-range index; that is a programmer error, so the scalar accessors raise ArgumentError on one and details/1 returns {:error, :invalid_node}. Nothing dereferences a bad index, and nil keeps exactly one meaning: this node type has no such field.

Accessors by node type

Accessor:document:element / :templatetext-like *
type/1 parent/1 children/1 next/1 previous/1 descendants/1 index_within_parent/1 parse_flags/1 parse_flag?/2 position/1 details/1✓✓✓
has_doctype?/1 doctype_name/1 public_identifier/1 system_identifier/1 quirks_mode/1✓nilnil
tag/1 tag_name/1 namespace/1 attributes/1 attribute/2 attribute_map/1 original_tag/1 original_end_tag/1 end_position/1nil✓nil
text/1 original_text/1nilnil✓

* :text, :cdata, :comment, :whitespace, :processing_instruction.

position/1 reports gumbo's GumboSourcePosition verbatim: line and column are 1-based, column counts characters while offset counts bytes. Parser-inserted elements carry the empty position (all zeros).

Summary

Functions

The element's attribute named name, matched case-insensitively exactly as gumbo_get_attribute does (strcasecmp); nil when absent or off elements.

A name → value map of the element's attributes, built from the same data as Gumbo.Attribute.details/1.

The element's attributes in source order; [] off elements.

The node's direct children in source order, or [] for leaves.

A processing instruction's data; nil off processing instructions.

The node and every node below it, in preorder.

Every field at once, in one NIF crossing.

The doctype's name (lowercased by the tokenizer); nil without one.

The element's end-tag position; nil off elements.

Whether the document had an explicit doctype; nil off the document.

The node's position within its parent's children/1, 0-based.

The element's namespace; nil off elements.

The node's next sibling, or nil when the node is last.

The end tag as written, including the >; the empty binary when the end tag was implied, nil off elements.

The start tag as written, including the trailing >; the empty binary for parser-inserted elements, nil off elements.

The node's text as written in the source; nil off text-like nodes.

The node's parent, or nil for the document node.

Returns whether flag describes the node's insertion.

The parse flags gumbo recorded for this node's insertion.

The node's start position in the source.

The node's previous sibling, or nil when the node is first.

The doctype's public identifier, verbatim; nil when absent.

The document's quirks mode; nil off the document.

The doctype's system identifier, verbatim; nil when absent.

The element's tag, :unknown for names outside gumbo's tag table; nil off elements.

The element's name as written, SVG case-corrected.

A processing instruction's target; nil off processing instructions.

The node's text after entity decoding; nil off text-like nodes.

Types

t()

@type t() :: %Gumbo.Node{index: non_neg_integer(), output: Gumbo.Output.t()}

Functions

attribute(node, name)

@spec attribute(t(), binary()) :: Gumbo.Attribute.t() | nil

The element's attribute named name, matched case-insensitively exactly as gumbo_get_attribute does (strcasecmp); nil when absent or off elements.

name must not contain a NUL byte: the search is a C-string comparison, so a NUL would silently truncate the lookup and could match a different attribute. A name containing one is rejected and nil is returned.

Examples

iex> {:ok, output} = Gumbo.parse(~s(<p class="a">x</p>))
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.attribute(p, "CLASS") |> Gumbo.Attribute.name()
"class"

attribute_map(node)

@spec attribute_map(t()) :: %{required(binary()) => binary()}

A name → value map of the element's attributes, built from the same data as Gumbo.Attribute.details/1.

Lossy: the keys are the normalized names, so namespaces are folded away (an xlink:href and a plain href collide) and the map cannot represent duplicate names. attributes/1 is the lossless path.

Examples

iex> {:ok, output} = Gumbo.parse(~s(<p class="a">x</p>))
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.attribute_map(p)
%{"class" => "a"}

attributes(node)

@spec attributes(t()) :: [Gumbo.Attribute.t()]

The element's attributes in source order; [] off elements.

Examples

iex> {:ok, output} = Gumbo.parse(~s(<p class="a" id="b">x</p>))
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> length(Gumbo.Node.attributes(p))
2

children(node)

@spec children(t()) :: [t()]

The node's direct children in source order, or [] for leaves.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Enum.map(Gumbo.Node.children(Gumbo.Output.root(output)), &Gumbo.Node.type/1)
[:element, :element]

data(node)

@spec data(t()) :: binary() | nil

A processing instruction's data; nil off processing instructions.

The data is everything after the target, with its own spacing intact — <?php echo 1; ?> has the data "echo 1; ".

Examples

iex> {:ok, output} = Gumbo.parse_fragment("<?php echo 1; ?>", :body)
iex> [pi] = Gumbo.Output.fragment_nodes(output)
iex> Gumbo.Node.data(pi)
"echo 1; "

descendants(node)

@spec descendants(t()) :: [t()]

The node and every node below it, in preorder.

The walk is iterative — an explicit list used as a stack, one children/1 call per node — so depth is bounded by memory, not by the scheduler's C stack.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Enum.map(Gumbo.Node.descendants(Gumbo.Output.root(output)), &Gumbo.Node.type/1)
[:element, :element, :element, :element, :text]

details(node)

@spec details(t()) :: {:ok, map()} | {:error, :invalid_node}

Every field at once, in one NIF crossing.

The map keys are the accessor names above, with nil for fields the node type does not carry, so details(n)[:tag] == tag(n) for every key. The deliberate exception to the ArgumentError contract: a caller asking for a whole node branches on {:error, :invalid_node} rather than rescues.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> {:ok, details} = Gumbo.Output.root(output) |> Gumbo.Node.details()
iex> details[:type]
:element

doctype_name(node)

@spec doctype_name(t()) :: binary() | nil

The doctype's name (lowercased by the tokenizer); nil without one.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.doctype_name(Gumbo.Output.document(output))
"html"

end_position(node)

@spec end_position(t()) :: Gumbo.Position.t() | nil

The element's end-tag position; nil off elements.

Examples

iex> {:ok, output} = Gumbo.parse(~s(<p class="a">x</p>))
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.end_position(p)
%Gumbo.Position{line: 1, column: 15, offset: 14}

has_doctype?(node)

@spec has_doctype?(t()) :: boolean() | nil

Whether the document had an explicit doctype; nil off the document.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.has_doctype?(Gumbo.Output.document(output))
true

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.has_doctype?(Gumbo.Output.root(output))
nil

index_within_parent(node)

@spec index_within_parent(t()) :: non_neg_integer() | nil

The node's position within its parent's children/1, 0-based.

nil for the document node and any other node gumbo never appended to a parent.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.index_within_parent(p)
0

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.index_within_parent(Gumbo.Output.document(output))
nil

namespace(node)

@spec namespace(t()) :: Gumbo.Types.namespace() | nil

The element's namespace; nil off elements.

Examples

iex> {:ok, output} = Gumbo.parse_fragment("<circle/>", :svg, fragment_namespace: :svg)
iex> [circle] = Gumbo.Output.fragment_nodes(output)
iex> Gumbo.Node.namespace(circle)
:svg

next(node)

@spec next(t()) :: t() | nil

The node's next sibling, or nil when the node is last.

Computed from the parent's children plus the node's index_within_parent/1; gumbo stores no sibling pointers.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.next(p)
nil

original_end_tag(node)

@spec original_end_tag(t()) :: binary() | nil

The end tag as written, including the >; the empty binary when the end tag was implied, nil off elements.

Examples

iex> {:ok, output} = Gumbo.parse(~s(<p class="a">x</p>))
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.original_end_tag(p)
"</p>"

original_tag(node)

@spec original_tag(t()) :: binary() | nil

The start tag as written, including the trailing >; the empty binary for parser-inserted elements, nil off elements.

Examples

iex> {:ok, output} = Gumbo.parse(~s(<p class="a">x</p>))
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.original_tag(p)
"<p class=\"a\">"

original_text(node)

@spec original_text(t()) :: binary() | nil

The node's text as written in the source; nil off text-like nodes.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.original_text(Enum.at(Gumbo.Node.children(p), 0))
"x"

parent(node)

@spec parent(t()) :: t() | nil

The node's parent, or nil for the document node.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.parent(Gumbo.Output.document(output))
nil

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.parent(Gumbo.Output.root(output)) |> Gumbo.Node.type()
:document

parse_flag?(node, flag)

@spec parse_flag?(t(), Gumbo.Types.parse_flag()) :: boolean()

Returns whether flag describes the node's insertion.

:normal means no parse flags are set; any other flag tests its bit.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.parse_flag?(Gumbo.Output.root(output), :by_parser)
true

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.parse_flag?(p, :normal)
true

parse_flags(node)

@spec parse_flags(t()) :: [Gumbo.Types.parse_flag()]

The parse flags gumbo recorded for this node's insertion.

[] means GUMBO_INSERTION_NORMAL; parser-inserted nodes report why, e.g. [:by_parser, :implied] for an implied <html>.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.parse_flags(Gumbo.Output.root(output))
[:by_parser, :implicit_end_tag, :implied]

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.parse_flags(p)
[]

position(node)

@spec position(t()) :: Gumbo.Position.t()

The node's start position in the source.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.position(p)
%Gumbo.Position{line: 1, column: 16, offset: 15}

previous(node)

@spec previous(t()) :: t() | nil

The node's previous sibling, or nil when the node is first.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x<b>y</b></p>")
iex> b = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "b"))
iex> Gumbo.Node.previous(b) |> Gumbo.Node.type()
:text

public_identifier(node)

@spec public_identifier(t()) :: binary() | nil

The doctype's public identifier, verbatim; nil when absent.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.public_identifier(Gumbo.Output.document(output))
""

quirks_mode(node)

@spec quirks_mode(t()) :: Gumbo.Types.quirks_mode() | nil

The document's quirks mode; nil off the document.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.quirks_mode(Gumbo.Output.document(output))
:no_quirks

system_identifier(node)

@spec system_identifier(t()) :: binary() | nil

The doctype's system identifier, verbatim; nil when absent.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.system_identifier(Gumbo.Output.document(output))
""

tag(node)

@spec tag(t()) :: Gumbo.Tag.t() | nil

The element's tag, :unknown for names outside gumbo's tag table; nil off elements.

Examples

iex> {:ok, output} = Gumbo.parse("<my-tag>x</my-tag>")
iex> node = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag(&1) == :unknown))
iex> Gumbo.Node.tag(node)
:unknown

tag_name(node)

@spec tag_name(t()) :: binary() | nil

The element's name as written, SVG case-corrected.

For known tags this is gumbo's normalized name; for :unknown tags the name is recovered from original_tag/1. Parser-inserted elements have no source, so an :unknown tag there yields nil.

Examples

iex> {:ok, output} = Gumbo.parse("<my-tag>x</my-tag>")
iex> node = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag(&1) == :unknown))
iex> Gumbo.Node.tag_name(node)
"my-tag"

target(node)

@spec target(t()) :: binary() | nil

A processing instruction's target; nil off processing instructions.

The spec names a processing instruction's payload in two parts, and a browser reports them apart: <?pi?> has the target "pi" and the data "". The C library keeps them in one string, which is what text/1 returns.

Examples

iex> {:ok, output} = Gumbo.parse_fragment("<?pi x?>", :body)
iex> [pi] = Gumbo.Output.fragment_nodes(output)
iex> Gumbo.Node.target(pi)
"pi"

text(node)

@spec text(t()) :: binary() | nil

The node's text after entity decoding; nil off text-like nodes.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> p = output |> Gumbo.Output.root() |> Gumbo.Node.descendants() |> Enum.find(&(Gumbo.Node.tag_name(&1) == "p"))
iex> Gumbo.Node.text(Enum.at(Gumbo.Node.children(p), 0))
"x"

type(node)

@spec type(t()) :: Gumbo.Types.node_type()

The node's Gumbo.Types.node_type/0.

Examples

iex> {:ok, output} = Gumbo.parse("<!doctype html><p>x</p>")
iex> Gumbo.Node.type(Gumbo.Output.root(output))
:element