Packages
floki
0.3.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
4
files changed
+52
additions
-4
deletions
| @@ -22,6 +22,7 @@ Assuming that you have the following HTML: | |
| 22 22 | <section id="content"> |
| 23 23 | <p class="headline">Floki</p> |
| 24 24 | <a href="http://github.com/philss/floki">Github page</a> |
| 25 | + <span data-model="user">philss</span> |
| 25 26 | </section> |
| 26 27 | <a href="https://hex.pm/packages/floki">Hex package</a> |
| 27 28 | </body> |
| @@ -50,6 +51,10 @@ Floki.find(html, "#content a") | |
| 50 51 | # => [{"a", [{"href", "http://github.com/philss/floki"}], ["Github page"]}] |
| 51 52 | |
| 52 53 | |
| 54 | + Floki.find(html, "[data-model=user]") |
| 55 | + # => [{"span", [{"data-model", "user"}], ["philss"]}] |
| 56 | + |
| 57 | + |
| 53 58 | Floki.find(html, ".headline, a") |
| 54 59 | # => [{"p", [{"class", "headline"}], ["Floki"]}, |
| 55 60 | # => {"a", [{"href", "http://github.com/philss/floki"}], ["Github page"]}, |
| @@ -17,4 +17,4 @@ | |
| 17 17 | [{<<"app">>,<<"mochiweb">>}, |
| 18 18 | {<<"optional">>,nil}, |
| 19 19 | {<<"requirement">>,<<"~> 2.12.2">>}]}]}. |
| 20 | - {<<"version">>,<<"0.2.1">>}. |
| 20 | + {<<"version">>,<<"0.3.0">>}. |
| @@ -16,6 +16,7 @@ defmodule Floki do | |
| 16 16 | <section id="content"> |
| 17 17 | <p class="headline">Floki</p> |
| 18 18 | <a href="http://github.com/philss/floki">Github page</a> |
| 19 | + <span data-model="user">philss</span> |
| 19 20 | </section> |
| 20 21 | </body> |
| 21 22 | </html> |
| @@ -25,6 +26,7 @@ defmodule Floki do | |
| 25 26 | * Floki.find(html, "#content") : returns the section with all children; |
| 26 27 | * Floki.find(html, ".headline") : returns a list with the `p` element; |
| 27 28 | * Floki.find(html, "a") : returns a list with the `a` element; |
| 29 | + * Floki.find(html, "[data-model=user]") : returns a list with elements that match that data attribute; |
| 28 30 | * Floki.find(html, "#content a") # returns all links inside content section; |
| 29 31 | * Floki.find(html, ".headline, a") # returns the .headline elements and links. |
| 30 32 | |
| @@ -71,8 +73,9 @@ defmodule Floki do | |
| 71 73 | def parse(html) do |
| 72 74 | html = "<#{@floki_root_node}>#{html}</#{@floki_root_node}>" |
| 73 75 | {@floki_root_node, [], parsed} = :mochiweb_html.parse(html) |
| 76 | + |
| 74 77 | if length(parsed) == 1, do: hd(parsed), else: parsed |
| 75 | - end |
| 78 | + end |
| 76 79 | |
| 77 80 | @doc """ |
| 78 81 | Finds elements inside a HTML tree or string. |
| @@ -104,7 +107,16 @@ defmodule Floki do | |
| 104 107 | find(html_tree, selector) |
| 105 108 | end |
| 106 109 | |
| 110 | + def find(html_tree, selector) when is_tuple(selector) do |
| 111 | + {:ok, nodes} = find_by_selector(selector, html_tree, &attr_matcher/3, {:ok, []}) |
| 112 | + |
| 113 | + Enum.reverse(nodes) |
| 114 | + end |
| 115 | + |
| 107 116 | def find(html_tree, selector) do |
| 117 | + tag_attr_val_regex = ~r/(?'tag'.+)\[(?'attr'.+)=(?'val'.+)\]/ |
| 118 | + attr_val_regex = ~r/\[(?'attr'.+)=(?'val'.+)\]/ |
| 119 | + |
| 108 120 | cond do |
| 109 121 | String.contains?(selector, ",") -> |
| 110 122 | selectors = String.split(selector, ",") |
| @@ -134,6 +146,16 @@ defmodule Floki do | |
| 134 146 | {_status, nodes} = find_by_selector(id, html_tree, &id_matcher/3, {:ok, []}) |
| 135 147 | |
| 136 148 | List.first(nodes) |
| 149 | + Regex.match?(attr_val_regex, selector) -> |
| 150 | + %{"attr" => attr, "val" => val} = Regex.named_captures(attr_val_regex, selector) |
| 151 | + {:ok, nodes} = find_by_selector({attr, val}, html_tree, &attr_matcher/3, {:ok, []}) |
| 152 | + |
| 153 | + Enum.reverse(nodes) |
| 154 | + Regex.match?(tag_attr_val_regex, selector) -> |
| 155 | + %{"tag" => tag, "attr" => attr, "val" => val} = Regex.named_captures(attr_val_regex, selector) |
| 156 | + {:ok, nodes} = find_by_selector({tag, attr, val}, html_tree, &attr_matcher/3, {:ok, []}) |
| 157 | + |
| 158 | + Enum.reverse(nodes) |
| 137 159 | true -> |
| 138 160 | {:ok, nodes} = find_by_selector(selector, html_tree, &tag_matcher/3, {:ok, []}) |
| 139 161 | |
| @@ -259,7 +281,28 @@ defmodule Floki do | |
| 259 281 | end |
| 260 282 | end |
| 261 283 | |
| 262 | - Enum.reverse values |
| 284 | + Enum.reverse(values) |
| 285 | + end |
| 286 | + |
| 287 | + defp attr_matcher({attr, value}, node, acc) do |
| 288 | + {_, attributes, _} = node |
| 289 | + {:ok, acc_nodes} = acc |
| 290 | + |
| 291 | + if attribute_match?(attributes, attr, value) do |
| 292 | + acc = {:ok, [node|acc_nodes]} |
| 293 | + end |
| 294 | + |
| 295 | + acc |
| 296 | + end |
| 297 | + defp attr_matcher({tag_name, attr, value}, node, acc) do |
| 298 | + {tag, attributes, _} = node |
| 299 | + {:ok, acc_nodes} = acc |
| 300 | + |
| 301 | + if tag == tag_name and attribute_match?(attributes, attr, value) do |
| 302 | + acc = {:ok, [node|acc_nodes]} |
| 303 | + end |
| 304 | + |
| 305 | + acc |
| 263 306 | end |
| 264 307 | |
| 265 308 | defp class_matcher(class_name, node, acc) do |
| @@ -3,7 +3,7 @@ defmodule Floki.Mixfile do | |
| 3 3 | |
| 4 4 | def project do |
| 5 5 | [app: :floki, |
| 6 | - version: "0.2.1", |
| 6 | + version: "0.3.0", |
| 7 7 | elixir: ">= 1.0.0", |
| 8 8 | package: package, |
| 9 9 | description: description, |