PureHTML (PureHTML v0.4.0)
View SourceA pure Elixir HTML5 parser with CSS selector querying.
PureHTML parses HTML strings into a tree of nodes and provides functions to query, traverse, and serialize them back to HTML.
Parsing
iex> PureHTML.parse("<p>Hello</p>")
[{"html", [], [{"head", [], []}, {"body", [], [{"p", [], ["Hello"]}]}]}]Querying
iex> html = PureHTML.parse("<div><p class='intro'>Hello</p></div>")
iex> PureHTML.query(html, ".intro")
[{"p", [{"class", "intro"}], ["Hello"]}]Serializing
iex> [{"p", [], ["Hello"]}] |> PureHTML.to_html()
"<p>Hello</p>"Node Format
Nodes are represented as tuples compatible with Floki:
{tag, attrs, children}- Element with tag name, attribute list, and children{:doctype, name, public_id, system_id}- DOCTYPE declaration{:comment, text}- HTML comment"text"- Text content (binary string)
Attributes are lists of {name, value} tuples, sorted alphabetically.
Summary
Functions
Extracts an attribute value from a single node.
Extracts attribute values from a list of nodes.
Finds elements matching a selector and extracts an attribute from them.
Returns the immediate children of a node.
Parses an HTML string into a list of nodes.
Parses HTML like parse/2 and returns {nodes, error_count}.
Finds all nodes matching the CSS selector.
Finds the first node matching the CSS selector.
Extracts text content from an HTML tree or node.
Converts parsed HTML nodes back to an HTML string.
Functions
Extracts an attribute value from a single node.
Returns the attribute value as a string, or nil if the attribute
doesn't exist or the input is not an element.
Examples
iex> node = {"a", [{"href", "/home"}, {"class", "link"}], ["Home"]}
iex> PureHTML.attr(node, "href")
"/home"
iex> node = {"a", [{"href", "/home"}], ["Home"]}
iex> PureHTML.attr(node, "title")
nil
iex> PureHTML.attr("text node", "href")
nil
Extracts attribute values from a list of nodes.
Returns a list of attribute values. Nodes without the attribute are skipped.
Examples
iex> "<a href='/one'>One</a><a href='/two'>Two</a>"
...> |> PureHTML.parse()
...> |> PureHTML.query("a")
...> |> PureHTML.attribute("href")
["/one", "/two"]
Finds elements matching a selector and extracts an attribute from them.
Combines query/2 and attribute/2 into a single call for convenience.
This is the most common pattern for scraping.
Examples
iex> "<nav><a href='/'>Home</a><a href='/about'>About</a></nav>"
...> |> PureHTML.parse()
...> |> PureHTML.attribute("a", "href")
["/", "/about"]
iex> "<div><img src='a.png'><img src='b.png'></div>"
...> |> PureHTML.parse()
...> |> PureHTML.attribute("img", "src")
["a.png", "b.png"]
Returns the immediate children of a node.
Options
:include_text- Include text nodes (default: true)
Examples
iex> node = {"div", [], [{"p", [], ["Hello"]}, "Some text"]}
iex> PureHTML.children(node)
[{"p", [], ["Hello"]}, "Some text"]
iex> node = {"div", [], [{"p", [], ["Hello"]}, "Some text"]}
iex> PureHTML.children(node, include_text: false)
[{"p", [], ["Hello"]}]
Parses an HTML string into a list of nodes.
Returns a list of nodes where each node is one of:
{:doctype, name, public_id, system_id}- DOCTYPE declaration (if present, always first){:comment, text}- HTML comment{tag, attrs, children}- Element with tag name, attributes list, and child nodestext- Text content (binary)
Attributes are represented as a list of {name, value} tuples, sorted alphabetically.
Options
:context- Context element for fragment parsing (e.g.,"div","svg title"):scripting- Whether the scripting flag is enabled (default:true). Whenfalse,<noscript>content is parsed as HTML instead of raw text.
Examples
iex> PureHTML.parse("<p>Hello</p>")
[{"html", [], [{"head", [], []}, {"body", [], [{"p", [], ["Hello"]}]}]}]
iex> PureHTML.parse("<!DOCTYPE html><html></html>")
[{:doctype, "html", nil, nil}, {"html", [], [{"head", [], []}, {"body", [], []}]}]
@spec parse_with_errors( String.t(), keyword() ) :: {[term()], non_neg_integer()}
Parses HTML like parse/2 and returns {nodes, error_count}.
The count follows the parse errors the WHATWG tokenizer and tree
construction rules define (a missing doctype, an unexpected end tag, an
element still open at the end of the input, and so on). Well-formed input
yields 0. Takes the same options as parse/2.
Examples
iex> PureHTML.parse_with_errors("<!DOCTYPE html><p>Hi</p>")
{[{:doctype, "html", nil, nil}, {"html", [], [{"head", [], []}, {"body", [], [{"p", [], ["Hi"]}]}]}], 0}
iex> {_nodes, errors} = PureHTML.parse_with_errors("<p>Hi")
iex> errors
1
Finds all nodes matching the CSS selector.
Supported Selectors
- Tag:
div,p,a - Universal:
* - Class:
.class - ID:
#id - Attribute:
[attr],[attr=value],[attr^=prefix],[attr$=suffix],[attr*=substring] - Selector list:
.a, .b
Examples
iex> html = PureHTML.parse("<div><p class='intro'>Hello</p><p>World</p></div>")
iex> PureHTML.query(html, "p.intro")
[{"p", [{"class", "intro"}], ["Hello"]}]
iex> html = PureHTML.parse("<ul><li>A</li><li>B</li></ul>")
iex> PureHTML.query(html, "li")
[{"li", [], ["A"]}, {"li", [], ["B"]}]
Finds the first node matching the CSS selector.
Returns the first matching node, or nil if no match is found.
More efficient than query/2 when you only need the first result.
Examples
iex> "<ul><li>A</li><li>B</li></ul>"
...> |> PureHTML.parse()
...> |> PureHTML.query_one("li")
{"li", [], ["A"]}
iex> "<div><p>Hello</p></div>"
...> |> PureHTML.parse()
...> |> PureHTML.query_one(".missing")
nil
Extracts text content from an HTML tree or node.
Options
:deep- Traverse all descendants (default: true). When false, only direct text children.:separator- String to insert between text segments (default: ""):strip- Strip whitespace from each segment and remove empty segments (default: false):include_script- Include text from<script>tags (default: false):include_style- Include text from<style>tags (default: false):include_inputs- Include value from<input>and<textarea>(default: false)
Examples
iex> html = PureHTML.parse("<p>Hello <strong>World</strong></p>")
iex> PureHTML.text(html)
"Hello World"
iex> html = PureHTML.parse("<ul><li>A</li><li>B</li></ul>")
iex> PureHTML.text(html, separator: ", ")
"A, B"
iex> html = PureHTML.parse("<div>Hello<script>alert(1)</script>World</div>")
iex> PureHTML.text(html)
"HelloWorld"
iex> html = PureHTML.parse("<form><input value='test'></form>")
iex> PureHTML.text(html, include_inputs: true)
"test"Formatted HTML often contains whitespace for indentation. Use :strip to clean it:
iex> html = PureHTML.parse("<ul>\n <li>One</li>\n <li>Two</li>\n</ul>")
iex> PureHTML.text(html, strip: true, separator: " | ")
"One | Two"
Converts parsed HTML nodes back to an HTML string.
Options
:quote_char- Force"'"or"""for attribute quotes (default: smart quoting):minimize_boolean_attributes- Outputdisabledvsdisabled=disabled(default: true):use_trailing_solidus- Output<br />vs<br>(default: false):escape_rcdata- Escape content in script/style (default: false):strip_whitespace- Collapse whitespace in text nodes (default: false)
Examples
iex> PureHTML.parse("<p>Hello</p>") |> PureHTML.to_html()
"<html><head></head><body><p>Hello</p></body></html>"
iex> PureHTML.to_html([{"div", [{"class", "foo"}], ["text"]}])
"<div class=foo>text</div>"
iex> PureHTML.to_html([{"br", [], []}], use_trailing_solidus: true)
"<br />"