Packages
floki
0.25.0
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.1
0.37.0
0.36.3
0.36.2
0.36.1
0.36.0
0.35.4
0.35.3
0.35.2
0.35.1
0.35.0
0.34.3
0.34.2
0.34.1
0.34.0
0.33.1
0.33.0
0.32.1
0.32.0
0.31.0
0.30.1
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.1
0.23.0
0.22.0
0.21.0
0.20.4
0.20.3
0.20.2
0.20.1
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.2
0.17.1
0.17.0
0.16.0
0.15.0
0.14.0
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.0
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.1
0.1.0
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
Floki is a simple HTML parser that enables search for nodes using CSS selectors.
Current section
86 Versions
Jump to
Current section
86 Versions
Compare versions
6
files changed
+136
additions
-20
deletions
| @@ -60,7 +60,7 @@ Add Floki to your `mix.exs`: | |
| 60 60 | ```elixir |
| 61 61 | defp deps do |
| 62 62 | [ |
| 63 | - {:floki, "~> 0.24.0"} |
| 63 | + {:floki, "~> 0.25.0"} |
| 64 64 | ] |
| 65 65 | end |
| 66 66 | ``` |
| @@ -113,7 +113,7 @@ After Rust is set up, you need to add `html5ever` NIF to your dependency list: | |
| 113 113 | ```elixir |
| 114 114 | defp deps do |
| 115 115 | [ |
| 116 | - {:floki, "~> 0.24.0"}, |
| 116 | + {:floki, "~> 0.25.0"}, |
| 117 117 | {:html5ever, "~> 0.7.0"} |
| 118 118 | ] |
| 119 119 | end |
| @@ -145,7 +145,7 @@ First, add `fast_html` to your dependencies: | |
| 145 145 | ```elixir |
| 146 146 | defp deps do |
| 147 147 | [ |
| 148 | - {:floki, "~> 0.24.0"}, |
| 148 | + {:floki, "~> 0.25.0"}, |
| 149 149 | {:fast_html, "~> 1.0"} |
| 150 150 | ] |
| 151 151 | end |
| @@ -158,7 +158,7 @@ Then you need to configure your app to use `fast_html`: | |
| 158 158 | ```elixir |
| 159 159 | # in config/config.exs |
| 160 160 | |
| 161 | - config :floki, :html_parser, Floki.HTMLParser.FastHTML |
| 161 | + config :floki, :html_parser, Floki.HTMLParser.FastHtml |
| 162 162 | ``` |
| 163 163 | |
| 164 164 | ## More about Floki API |
| @@ -15,10 +15,11 @@ | |
| 15 15 | <<"lib/floki/selector/combinator.ex">>,<<"lib/floki/deep_text.ex">>, |
| 16 16 | <<"lib/floki/filter_out.ex">>,<<"lib/floki/flat_text.ex">>, |
| 17 17 | <<"lib/floki/finder.ex">>,<<"lib/floki/traversal.ex">>, |
| 18 | - <<"lib/floki/raw_html.ex">>,<<"lib/floki/html_tree">>, |
| 19 | - <<"lib/floki/html_tree/id_seeder.ex">>,<<"lib/floki/html_tree/comment.ex">>, |
| 20 | - <<"lib/floki/html_tree/text.ex">>,<<"lib/floki/html_tree/html_node.ex">>, |
| 21 | - <<"lib/floki/selector.ex">>,<<"lib/floki/html_tree.ex">>,<<"lib/floki.ex">>, |
| 18 | + <<"lib/floki/raw_html.ex">>,<<"lib/floki/parse_error.ex">>, |
| 19 | + <<"lib/floki/html_tree">>,<<"lib/floki/html_tree/id_seeder.ex">>, |
| 20 | + <<"lib/floki/html_tree/comment.ex">>,<<"lib/floki/html_tree/text.ex">>, |
| 21 | + <<"lib/floki/html_tree/html_node.ex">>,<<"lib/floki/selector.ex">>, |
| 22 | + <<"lib/floki/html_tree.ex">>,<<"lib/floki.ex">>, |
| 22 23 | <<"src/floki_selector_lexer.xrl">>,<<"src/floki_mochi_html.erl">>, |
| 23 24 | <<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,<<"CODE_OF_CONDUCT.md">>, |
| 24 25 | <<"CONTRIBUTING.md">>]}. |
| @@ -31,4 +32,4 @@ | |
| 31 32 | {<<"optional">>,false}, |
| 32 33 | {<<"repository">>,<<"hexpm">>}, |
| 33 34 | {<<"requirement">>,<<"~> 0.5.0">>}]]}. |
| 34 | - {<<"version">>,<<"0.24.0">>}. |
| 35 | + {<<"version">>,<<"0.25.0">>}. |
| @@ -104,6 +104,28 @@ defmodule Floki do | |
| 104 104 | |
| 105 105 | defdelegate parse_document(document), to: Floki.HTMLParser |
| 106 106 | |
| 107 | + @doc """ |
| 108 | + Parses a HTML Document from a string. |
| 109 | + |
| 110 | + Similar to `Floki.parse_document/1`, but raises `Floki.ParseError` if there was an |
| 111 | + error parsing the document. |
| 112 | + |
| 113 | + ## Example |
| 114 | + |
| 115 | + iex> Floki.parse_document!("<html><head></head><body>hello</body></html>") |
| 116 | + [{"html", [], [{"head", [], []}, {"body", [], ["hello"]}]}] |
| 117 | + |
| 118 | + """ |
| 119 | + |
| 120 | + @spec parse_document!(binary()) :: html_tree() |
| 121 | + |
| 122 | + def parse_document!(document) do |
| 123 | + case parse_document(document) do |
| 124 | + {:ok, parsed_document} -> parsed_document |
| 125 | + {:error, message} -> raise Floki.ParseError, message: message |
| 126 | + end |
| 127 | + end |
| 128 | + |
| 107 129 | @doc """ |
| 108 130 | Parses a HTML fragment from a string. |
| 109 131 | |
| @@ -113,7 +135,23 @@ defmodule Floki do | |
| 113 135 | |
| 114 136 | @spec parse_fragment(binary()) :: {:ok, html_tree()} | {:error, String.t()} |
| 115 137 | |
| 116 | - defdelegate parse_fragment(document), to: Floki.HTMLParser |
| 138 | + defdelegate parse_fragment(fragment), to: Floki.HTMLParser |
| 139 | + |
| 140 | + @doc """ |
| 141 | + Parses a HTML fragment from a string. |
| 142 | + |
| 143 | + Similar to `Floki.parse_fragment/1`, but raises `Floki.ParseError` if there was an |
| 144 | + error parsing the fragment. |
| 145 | + """ |
| 146 | + |
| 147 | + @spec parse_fragment!(binary()) :: html_tree() |
| 148 | + |
| 149 | + def parse_fragment!(fragment) do |
| 150 | + case parse_fragment(fragment) do |
| 151 | + {:ok, parsed_fragment} -> parsed_fragment |
| 152 | + {:error, message} -> raise Floki.ParseError, message: message |
| 153 | + end |
| 154 | + end |
| 117 155 | |
| 118 156 | @doc """ |
| 119 157 | Converts HTML tree to raw HTML. |
| @@ -285,21 +323,75 @@ defmodule Floki do | |
| 285 323 | def map(html_tree, fun), do: Finder.map(html_tree, fun) |
| 286 324 | |
| 287 325 | @doc """ |
| 288 | - Traverses a HTML tree structure and returns a new tree structure that is the result of executing a function on all nodes. The function receives a tuple with {name, attributes, children}, and should either return a similar tuple or `nil` to delete the current node. |
| 326 | + Traverses and updates a HTML tree structure. |
| 327 | + |
| 328 | + This function returns a new tree structure that is the result of applying the |
| 329 | + given `fun` on all nodes. |
| 330 | + |
| 331 | + The function `fun` receives a tuple with `{name, attributes, children}`, and |
| 332 | + should either return a similar tuple or `nil` to delete the current node. |
| 289 333 | |
| 290 334 | ## Examples |
| 291 335 | |
| 292 336 | iex> html = {"div", [], ["hello"]} |
| 293 | - iex> Floki.traverse_and_update(html, fn {"div", attrs, children} -> {"p", attrs, children} end) |
| 337 | + iex> Floki.traverse_and_update(html, fn {"div", attrs, children} -> |
| 338 | + ...> {"p", attrs, children} |
| 339 | + ...> end) |
| 294 340 | {"p", [], ["hello"]} |
| 295 341 | |
| 296 342 | iex> html = {"div", [], [{"span", [], ["hello"]}]} |
| 297 | - iex> Floki.traverse_and_update(html, fn {"span", _attrs, _children} -> nil; tag -> tag end) |
| 343 | + iex> Floki.traverse_and_update(html, fn |
| 344 | + ...> {"span", _attrs, _children} -> nil |
| 345 | + ...> tag -> tag |
| 346 | + ...> end) |
| 298 347 | {"div", [], []} |
| 299 348 | """ |
| 300 349 | |
| 350 | + @spec traverse_and_update(html_tree(), (html_tag() -> html_tag() | nil)) :: html_tree() |
| 351 | + |
| 301 352 | defdelegate traverse_and_update(html_tree, fun), to: Floki.Traversal |
| 302 353 | |
| 354 | + @doc """ |
| 355 | + Traverses and updates a HTML tree structure with an accumulator. |
| 356 | + |
| 357 | + This function returns a new tree structure and the final value of accumulator |
| 358 | + which are the result of applying the given `fun` on all nodes. |
| 359 | + |
| 360 | + The function `fun` receives a tuple with `{name, attributes, children}` and |
| 361 | + an accumulator, and should return a 2-tuple like `{new_node, new_acc}`, where |
| 362 | + `new_node` is either a similar tuple or `nil` to delete the current node, and |
| 363 | + `new_acc` is an updated value for the accumulator. |
| 364 | + |
| 365 | + ## Examples |
| 366 | + |
| 367 | + iex> html = [{"div", [], ["hello"]}, {"div", [], ["world"]}] |
| 368 | + iex> Floki.traverse_and_update(html, 0, fn {"div", attrs, children}, acc -> |
| 369 | + ...> {{"p", [{"data-count", to_string(acc)} | attrs], children}, acc + 1} |
| 370 | + ...> end) |
| 371 | + {[ |
| 372 | + {"p", [{"data-count", "0"}], ["hello"]}, |
| 373 | + {"p", [{"data-count", "1"}], ["world"]} |
| 374 | + ], 2} |
| 375 | + |
| 376 | + iex> html = {"div", [], [{"span", [], ["hello"]}]} |
| 377 | + iex> Floki.traverse_and_update(html, [deleted: 0], fn |
| 378 | + ...> {"span", _attrs, _children}, acc -> |
| 379 | + ...> {nil, Keyword.put(acc, :deleted, acc[:deleted] + 1)} |
| 380 | + ...> tag, acc -> |
| 381 | + ...> {tag, acc} |
| 382 | + ...> end) |
| 383 | + {{"div", [], []}, [deleted: 1]} |
| 384 | + """ |
| 385 | + |
| 386 | + @type traverse_acc :: any() |
| 387 | + @spec traverse_and_update( |
| 388 | + html_tree(), |
| 389 | + traverse_acc(), |
| 390 | + (html_tag(), traverse_acc() -> {html_tag() | nil, traverse_acc()}) |
| 391 | + ) :: html_tree() |
| 392 | + |
| 393 | + defdelegate traverse_and_update(html_tree, acc, fun), to: Floki.Traversal |
| 394 | + |
| 303 395 | @doc """ |
| 304 396 | Returns the text nodes from a HTML tree. |
| 305 397 | By default, it will perform a deep search through the HTML tree. |
| @@ -0,0 +1,3 @@ | |
| 1 | + defmodule Floki.ParseError do |
| 2 | + defexception [:message] |
| 3 | + end |
| @@ -1,7 +1,12 @@ | |
| 1 1 | defmodule Floki.Traversal do |
| 2 2 | @moduledoc false |
| 3 3 | |
| 4 | + def traverse_and_update(html_node, fun) |
| 5 | + def traverse_and_update([], _fun), do: [] |
| 4 6 | def traverse_and_update(text, _fun) when is_binary(text), do: text |
| 7 | + def traverse_and_update(xml_tag = {:pi, _, _}, fun), do: fun.(xml_tag) |
| 8 | + def traverse_and_update({:comment, children}, fun), do: fun.({:comment, children}) |
| 9 | + def traverse_and_update(doctype = {:doctype, _, _, _}, fun), do: fun.(doctype) |
| 5 10 | |
| 6 11 | def traverse_and_update([head | tail], fun) do |
| 7 12 | case traverse_and_update(head, fun) do |
| @@ -10,16 +15,31 @@ defmodule Floki.Traversal do | |
| 10 15 | end |
| 11 16 | end |
| 12 17 | |
| 13 | - def traverse_and_update([], _fun), do: [] |
| 14 | - |
| 15 | - def traverse_and_update(xml_tag = {:pi, _, _}, fun), do: fun.(xml_tag) |
| 16 | - |
| 17 18 | def traverse_and_update({elem, attrs, children}, fun) do |
| 18 19 | mapped_children = traverse_and_update(children, fun) |
| 19 20 | fun.({elem, attrs, mapped_children}) |
| 20 21 | end |
| 21 22 | |
| 22 | - def traverse_and_update({:comment, children}, fun), do: fun.({:comment, children}) |
| 23 | + def traverse_and_update(html_node, acc, fun) |
| 24 | + def traverse_and_update([], acc, _fun), do: {[], acc} |
| 25 | + def traverse_and_update(text, acc, _fun) when is_binary(text), do: {text, acc} |
| 26 | + def traverse_and_update(xml_tag = {:pi, _, _}, acc, fun), do: fun.(xml_tag, acc) |
| 27 | + def traverse_and_update({:comment, children}, acc, fun), do: fun.({:comment, children}, acc) |
| 28 | + def traverse_and_update(doctype = {:doctype, _, _, _}, acc, fun), do: fun.(doctype, acc) |
| 23 29 | |
| 24 | - def traverse_and_update(doctype = {:doctype, _, _, _}, fun), do: fun.(doctype) |
| 30 | + def traverse_and_update([head | tail], acc, fun) do |
| 31 | + case traverse_and_update(head, acc, fun) do |
| 32 | + {nil, new_acc} -> |
| 33 | + traverse_and_update(tail, new_acc, fun) |
| 34 | + |
| 35 | + {mapped_head, new_acc} -> |
| 36 | + {mapped_tail, new_acc2} = traverse_and_update(tail, new_acc, fun) |
| 37 | + {[mapped_head | mapped_tail], new_acc2} |
| 38 | + end |
| 39 | + end |
| 40 | + |
| 41 | + def traverse_and_update({elem, attrs, children}, acc, fun) do |
| 42 | + {mapped_children, new_acc} = traverse_and_update(children, acc, fun) |
| 43 | + fun.({elem, attrs, mapped_children}, new_acc) |
| 44 | + end |
| 25 45 | end |
Loading more files…