Packages
floki
0.6.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
5
files changed
+81
additions
-28
deletions
| @@ -43,6 +43,10 @@ Floki.find(html, "#content") | |
| 43 43 | Floki.find(html, "p.headline") |
| 44 44 | # => [{"p", [{"class", "headline"}], ["Floki"]}] |
| 45 45 | |
| 46 | + Floki.find(html, "p.headline") |
| 47 | + |> Floki.raw_html |
| 48 | + # => <p class="headline">Floki</p> |
| 49 | + |
| 46 50 | |
| 47 51 | Floki.find(html, "a") |
| 48 52 | # => [{"a", [{"href", "http://github.com/philss/floki"}], ["Github page"]}, |
| @@ -129,6 +133,14 @@ Floki.find(html, ".example") | |
| 129 133 | # => [{"div", [{"class", "example"}], []}] |
| 130 134 | ``` |
| 131 135 | |
| 136 | + To convert your node tree back to raw HTML (spaces are ignored): |
| 137 | + |
| 138 | + ```elixir |
| 139 | + Floki.find(html, ".example") |
| 140 | + |> Flok.raw_html |
| 141 | + # => <div class="example"></div> |
| 142 | + ``` |
| 143 | + |
| 132 144 | To fetch some attribute from elements, try: |
| 133 145 | |
| 134 146 | ```elixir |
| @@ -22,4 +22,4 @@ | |
| 22 22 | [{<<"app">>,<<"mochiweb">>}, |
| 23 23 | {<<"optional">>,false}, |
| 24 24 | {<<"requirement">>,<<"~> 2.12.2">>}]}]}. |
| 25 | - {<<"version">>,<<"0.5.0">>}. |
| 25 | + {<<"version">>,<<"0.6.0">>}. |
| @@ -77,6 +77,47 @@ defmodule Floki do | |
| 77 77 | Parser.parse(html) |
| 78 78 | end |
| 79 79 | |
| 80 | + @self_closing_tags ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "mete", "param", "source", "track", "wbr"] |
| 81 | + |
| 82 | + @doc """ |
| 83 | + Converts HTML tree to raw HTML. |
| 84 | + Note that the resultant HTML may be different from the original one. |
| 85 | + Spaces after tags and doctypes are ignored. |
| 86 | + |
| 87 | + ## Examples |
| 88 | + |
| 89 | + iex> Floki.parse(~s(<div class="wrapper">my content</div>)) |> Floki.raw_html |
| 90 | + ~s(<div class="wrapper">my content</div>) |
| 91 | + |
| 92 | + """ |
| 93 | + |
| 94 | + def raw_html(html_tree), do: raw_html(html_tree, "") |
| 95 | + defp raw_html([], html), do: html |
| 96 | + defp raw_html(tuple, html) when is_tuple(tuple), do: raw_html([tuple], html) |
| 97 | + defp raw_html([value|tail], html) when is_binary(value), do: raw_html(tail, html <> value) |
| 98 | + defp raw_html([{elem, attrs, value}|tail], html) do |
| 99 | + raw_html(tail, html <> tag_for(elem, attrs |> tag_attrs, value)) |
| 100 | + end |
| 101 | + |
| 102 | + defp tag_attrs(attr_list) do |
| 103 | + attr_list |
| 104 | + |> Enum.reduce("", fn({attr, value}, t) -> ~s(#{t} #{attr}="#{value}") end) |
| 105 | + |> String.strip |
| 106 | + end |
| 107 | + |
| 108 | + defp tag_for(elem, attrs, _value) when elem in @self_closing_tags do |
| 109 | + case attrs do |
| 110 | + "" -> "<#{elem}/>" |
| 111 | + _ -> "<#{elem} #{attrs}/>" |
| 112 | + end |
| 113 | + end |
| 114 | + defp tag_for(elem, attrs, value) do |
| 115 | + case attrs do |
| 116 | + "" -> "<#{elem}>#{raw_html(value)}</#{elem}>" |
| 117 | + _ -> "<#{elem} #{attrs}>#{raw_html(value)}</#{elem}>" |
| 118 | + end |
| 119 | + end |
| 120 | + |
| 80 121 | @doc """ |
| 81 122 | Find elements inside a HTML tree or string. |
| @@ -1,6 +1,6 @@ | |
| 1 1 | defmodule Floki.Finder do |
| 2 2 | @moduledoc """ |
| 3 | - The finder engine transverse the HTML tree searching for nodes matching |
| 3 | + The finder engine traverse the HTML tree searching for nodes matching |
| 4 4 | selectors. |
| 5 5 | """ |
| 6 6 | |
| @@ -20,7 +20,7 @@ defmodule Floki.Finder do | |
| 20 20 | selectors = get_selectors(selector_as_string) |
| 21 21 | |
| 22 22 | html_tree |
| 23 | - |> transverse([], selectors, []) |
| 23 | + |> traverse([], selectors, []) |
| 24 24 | |> Enum.reverse |
| 25 25 | end |
| 26 26 | |
| @@ -30,21 +30,21 @@ defmodule Floki.Finder do | |
| 30 30 | end |
| 31 31 | end |
| 32 32 | |
| 33 | - defp transverse(_, _, [], acc), do: acc |
| 34 | - defp transverse({}, _, _, acc), do: acc |
| 35 | - defp transverse([], _, _, acc), do: acc |
| 36 | - defp transverse(string, _, _, acc) when is_binary(string), do: acc |
| 37 | - defp transverse({:comment, _comment},_, _, acc), do: acc |
| 38 | - defp transverse({:pi, _xml, _xml_attrs},_, _, acc), do: acc |
| 39 | - defp transverse([node|sibling_nodes], _, selectors, acc) do |
| 40 | - acc = transverse(node, sibling_nodes, selectors, acc) |
| 41 | - transverse(sibling_nodes, [], selectors, acc) |
| 33 | + defp traverse(_, _, [], acc), do: acc |
| 34 | + defp traverse({}, _, _, acc), do: acc |
| 35 | + defp traverse([], _, _, acc), do: acc |
| 36 | + defp traverse(string, _, _, acc) when is_binary(string), do: acc |
| 37 | + defp traverse({:comment, _comment},_, _, acc), do: acc |
| 38 | + defp traverse({:pi, _xml, _xml_attrs},_, _, acc), do: acc |
| 39 | + defp traverse([node|sibling_nodes], _, selectors, acc) do |
| 40 | + acc = traverse(node, sibling_nodes, selectors, acc) |
| 41 | + traverse(sibling_nodes, [], selectors, acc) |
| 42 42 | end |
| 43 | - defp transverse(node, sibling_nodes, [head_selector|tail_selectors], acc) do |
| 44 | - acc = transverse(node, sibling_nodes, head_selector, acc) |
| 45 | - transverse(node, sibling_nodes, tail_selectors, acc) |
| 43 | + defp traverse(node, sibling_nodes, [head_selector|tail_selectors], acc) do |
| 44 | + acc = traverse(node, sibling_nodes, head_selector, acc) |
| 45 | + traverse(node, sibling_nodes, tail_selectors, acc) |
| 46 46 | end |
| 47 | - defp transverse({_, _, children_nodes} = node, sibling_nodes, selector, acc) do |
| 47 | + defp traverse({_, _, children_nodes} = node, sibling_nodes, selector, acc) do |
| 48 48 | acc = |
| 49 49 | if Selector.match?(node, selector) do |
| 50 50 | combinator = selector.combinator |
| @@ -53,13 +53,13 @@ defmodule Floki.Finder do | |
| 53 53 | _ -> |
| 54 54 | case combinator.match_type do |
| 55 55 | :descendant -> |
| 56 | - transverse(children_nodes, sibling_nodes, combinator.selector, acc) |
| 56 | + traverse(children_nodes, sibling_nodes, combinator.selector, acc) |
| 57 57 | :child -> |
| 58 | - transverse_child(children_nodes, sibling_nodes, combinator.selector, acc) |
| 58 | + traverse_child(children_nodes, sibling_nodes, combinator.selector, acc) |
| 59 59 | :sibling -> |
| 60 | - transverse_sibling(children_nodes, sibling_nodes, combinator.selector, acc) |
| 60 | + traverse_sibling(children_nodes, sibling_nodes, combinator.selector, acc) |
| 61 61 | :general_sibling -> |
| 62 | - transverse_general_sibling(children_nodes, sibling_nodes, combinator.selector, acc) |
| 62 | + traverse_general_sibling(children_nodes, sibling_nodes, combinator.selector, acc) |
| 63 63 | other -> |
| 64 64 | raise "Combinator of type \"#{other}\" not implemented" |
| 65 65 | end |
| @@ -68,17 +68,17 @@ defmodule Floki.Finder do | |
| 68 68 | acc |
| 69 69 | end |
| 70 70 | |
| 71 | - transverse(children_nodes, sibling_nodes, selector, acc) |
| 71 | + traverse(children_nodes, sibling_nodes, selector, acc) |
| 72 72 | end |
| 73 73 | |
| 74 | - defp transverse_child(nodes, sibling_nodes, selector, acc) do |
| 74 | + defp traverse_child(nodes, sibling_nodes, selector, acc) do |
| 75 75 | Enum.reduce(nodes, acc, fn(n, res_acc) -> |
| 76 76 | if Selector.match?(n, selector) do |
| 77 77 | case selector.combinator do |
| 78 78 | nil -> [n|res_acc] |
| 79 79 | _ -> |
| 80 80 | {_, _, children_nodes} = n |
| 81 | - transverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc) |
| 81 | + traverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc) |
| 82 82 | end |
| 83 83 | else |
| 84 84 | res_acc |
| @@ -86,7 +86,7 @@ defmodule Floki.Finder do | |
| 86 86 | end) |
| 87 87 | end |
| 88 88 | |
| 89 | - defp transverse_sibling(_nodes, sibling_nodes, selector, acc) do |
| 89 | + defp traverse_sibling(_nodes, sibling_nodes, selector, acc) do |
| 90 90 | sibling_node = Enum.drop_while(sibling_nodes, &ignore_node?/1) |> hd |
| 91 91 | |
| 92 92 | if Selector.match?(sibling_node, selector) do |
| @@ -94,14 +94,14 @@ defmodule Floki.Finder do | |
| 94 94 | nil -> [sibling_node|acc] |
| 95 95 | _ -> |
| 96 96 | {_, _, children_nodes} = sibling_node |
| 97 | - transverse(children_nodes, sibling_nodes, selector.combinator.selector, acc) |
| 97 | + traverse(children_nodes, sibling_nodes, selector.combinator.selector, acc) |
| 98 98 | end |
| 99 99 | else |
| 100 100 | acc |
| 101 101 | end |
| 102 102 | end |
| 103 103 | |
| 104 | - defp transverse_general_sibling(_nodes, sibling_nodes, selector, acc) do |
| 104 | + defp traverse_general_sibling(_nodes, sibling_nodes, selector, acc) do |
| 105 105 | sibling_nodes = Enum.drop_while(sibling_nodes, &ignore_node?/1) |
| 106 106 | |
| 107 107 | Enum.reduce(sibling_nodes, acc, fn(sibling_node, res_acc) -> |
| @@ -110,7 +110,7 @@ defmodule Floki.Finder do | |
| 110 110 | nil -> [sibling_node|res_acc] |
| 111 111 | _ -> |
| 112 112 | {_, _, children_nodes} = sibling_node |
| 113 | - transverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc) |
| 113 | + traverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc) |
| 114 114 | end |
| 115 115 | else |
| 116 116 | res_acc |
| @@ -3,7 +3,7 @@ defmodule Floki.Mixfile do | |
| 3 3 | |
| 4 4 | def project do |
| 5 5 | [app: :floki, |
| 6 | - version: "0.5.0", |
| 6 | + version: "0.6.0", |
| 7 7 | name: "Floki", |
| 8 8 | elixir: ">= 1.0.0", |
| 9 9 | package: package, |