Current section

86 Versions

Jump to

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…