Current section

86 Versions

Jump to

Compare versions

4 files changed
+187 additions
-21 deletions
  @@ -1,17 +1,59 @@
1 - Floki - search inside HTML documents
1 + Floki
2 2 =====
3 3
4 4 [![Build Status](https://travis-ci.org/philss/floki.svg?branch=master)](https://travis-ci.org/philss/floki)
5 5
6 - Floki is useful to search inside HTML documents using query selectors (like jQuery).
7 - Under the hood, it uses the [Mochiweb](https://github.com/mochi/mochiweb) HTML parser.
6 + A HTML parser and seeker.
8 7
9 - This version works with simple CSS selectors (without nesting or group).
10 - List of selectors:
8 + This is a simple HTML parser that enables searching using CSS like selectors.
11 9
12 - * class selectors - Ex.: `.class-name`
13 - * id selectors - Ex.: `#element-id`
14 - * tag selectors - Ex.: `img`
10 + You can search elements by class, tag name and id.
11 +
12 + [Check the documentation](http://hexdocs.pm/floki).
13 +
14 + ## Example
15 +
16 + Assuming that you have the following HTML:
17 +
18 + ```html
19 + <html>
20 + <body>
21 + <section id="content">
22 + <p class="headline">Floki</p>
23 + <a href="http://github.com/philss/floki">Github page</a>
24 + </section>
25 + </body>
26 + </html>
27 + ```
28 +
29 + You can perform the following queries:
30 +
31 + * Floki.find(html, "#content") : returns the section with all children;
32 + * Floki.find(html, ".headline") : returns a list with the `p` element;
33 + * Floki.find(html, "a") : returns a list with the `a` element.
34 +
35 + Each HTML node is represented by a tuple like:
36 +
37 + {tag_name, attributes, chidren_nodes}
38 +
39 + Example of node:
40 +
41 + {"p", [{"class", "headline"}], ["Floki"]}
42 +
43 + So even if the only child node is the element text, it is represented
44 + inside a list.
45 +
46 + You can write a simple HTML crawler (with support of [HTTPoison](https://github.com/edgurgel/httpoison)) with a few lines of code:
47 +
48 + ```elixir
49 + html
50 + |> Floki.find(".pages")
51 + |> Floki.find("a")
52 + |> Floki.attribute("href")
53 + |> Enum.map(fn(url) -> HTTPoison.get!(url) end)
54 + ```
55 +
56 + It is simple as that!
15 57
16 58 ## API
  @@ -2,14 +2,16 @@
2 2 {<<"build_tools">>,[<<"mix">>]}.
3 3 {<<"contributors">>,[<<"Philip Sampaio Silva">>]}.
4 4 {<<"description">>,
5 - <<"Floki is useful to search elements inside HTML documents using query selectors (like jQuery).\n">>}.
5 + <<"A HTML parser and seeker.\n\nYou can search inside HTML documents using CSS like selectors.\n">>}.
6 6 {<<"elixir">>,<<"~> 1.0.0">>}.
7 7 {<<"files">>,
8 8 [<<"lib/floki.ex">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,
9 9 <<"src/mochinum.erl">>,<<"src/mochiutf8.erl">>,
10 10 <<"src/mochiweb_charref.erl">>,<<"src/mochiweb_html.erl">>]}.
11 11 {<<"licenses">>,[<<"MIT">>]}.
12 - {<<"links">>,#{<<"GitHub">> => <<"https://github.com/philss/floki">>}}.
12 + {<<"links">>,
13 + #{<<"Docs">> => <<"http://hexdocs.pm/floki">>,
14 + <<"GitHub">> => <<"https://github.com/philss/floki">>}}.
13 15 {<<"name">>,<<"floki">>}.
14 16 {<<"requirements">>,#{}}.
15 - {<<"version">>,<<"0.0.3">>}.
17 + {<<"version">>,<<"0.0.4">>}.
  @@ -1,8 +1,93 @@
1 1 defmodule Floki do
2 + @moduledoc """
3 + A HTML parser and seeker.
4 +
5 + This is a simple HTML parser that enables searching using CSS like selectors.
6 +
7 + You can search elements by class, tag name and id.
8 +
9 + ## Example
10 +
11 + Assuming that you have the following HTML:
12 +
13 + <!doctype html>
14 + <html>
15 + <body>
16 + <section id="content">
17 + <p class="headline">Floki</p>
18 + <a href="http://github.com/philss/floki">Github page</a>
19 + </section>
20 + </body>
21 + </html>
22 +
23 + You can perform the following queries:
24 +
25 + * Floki.find(html, "#content") : returns the section with all children;
26 + * Floki.find(html, ".headline") : returns a list with the `p` element;
27 + * Floki.find(html, "a") : returns a list with the `a` element.
28 +
29 + Each HTML node is represented by a tuple like:
30 +
31 + {tag_name, attributes, chidren_nodes}
32 +
33 + Example of node:
34 +
35 + {"p", [{"class", "headline"}], ["Floki"]}
36 +
37 + So even if the only child node is the element text, it is represented
38 + inside a list.
39 +
40 + You can write a simple HTML crawler (with support of [HTTPoison](https://github.com/edgurgel/httpoison)) with a few lines of code:
41 +
42 + html
43 + |> Floki.find(".pages")
44 + |> Floki.find("a")
45 + |> Floki.attribute("href")
46 + |> Enum.map(fn(url) -> HTTPoison.get!(url) end)
47 +
48 + It is simple as that!
49 + """
50 +
51 + @type html_tree :: tuple | list
52 +
53 + @doc """
54 + Parses a HTML string.
55 +
56 + ## Examples
57 +
58 + iex> Floki.parse("<div class=js-action>hello world</div>")
59 + {"div", [{"class", "js-action" }], ["hello world"]}
60 +
61 + """
62 + @spec parse(binary) :: html_tree
2 63 def parse(html) do
3 64 :mochiweb_html.parse(html)
4 65 end
5 66
67 + @doc """
68 + Finds elements inside a HTML tree or string.
69 + You can search by class, tag name or id.
70 +
71 + It is possible to compose searches:
72 +
73 + Floki.find(html_string, ".class")
74 + |> Floki.find(".another-class-inside-small-scope"
75 +
76 + ## Examples
77 +
78 + iex> Floki.find("<p><span class=hint>hello</span></p>", ".hint")
79 + [{"span", [{ "class", "hint" }], ["hello"]}]
80 +
81 + iex> "<body><div id=important><div>Content</div></div></body>" |> Floki.find("#important")
82 + {"div", [{"id", "important"}], [{"div", [], ["Content"]}]}
83 +
84 + iex> Floki.find("<p><a href='https://google.com'>Google</a></p>", "a")
85 + [{"a", [{"href", "https://google.com"}], ["Google"]}]
86 +
87 + """
88 +
89 + @spec find(binary | html_tree, binary) :: html_tree
90 +
6 91 def find(html, selector) when is_binary(html) do
7 92 parse(html)
8 93 |> find(selector)
  @@ -23,19 +108,46 @@ defmodule Floki do
23 108 |> Enum.reverse
24 109 end
25 110
111 + @doc """
112 + Returns a list with attribute values for a given selector.
113 +
114 + ## Examples
115 +
116 + iex> Floki.attribute("<a href='https://google.com'>Google</a>", "a", "href")
117 + ["https://google.com"]
118 +
119 + """
120 +
121 + @spec attribute(binary | html_tree, binary, binary) :: list
122 +
26 123 def attribute(html, selector, attribute_name) do
27 124 html
28 125 |> find(selector)
29 126 |> get_attribute_values(attribute_name)
30 127 end
31 128
129 + @doc """
130 + Returns a list with attribute values from elements.
131 +
132 + ## Examples
133 +
134 + iex> Floki.attribute("<a href='https://google.com'>Google</a>", "href")
135 + ["https://google.com"]
136 +
137 + """
138 +
139 + @spec attribute(binary | html_tree, binary) :: list
140 +
32 141 def attribute(elements, attribute_name) do
33 142 elements
34 143 |> get_attribute_values(attribute_name)
35 144 end
36 145
37 - defp class_match?(attributes, class) do
38 - attribute_match?(attributes, "class", class)
146 +
147 + defp attribute_match?(attributes, attribute_name) do
148 + attributes |> Enum.find(fn({attr_name, _}) ->
149 + attr_name == attribute_name
150 + end)
39 151 end
40 152
41 153 defp attribute_match?(attributes, attribute_name, value) do
  @@ -62,20 +174,24 @@ defmodule Floki do
62 174 find_by_selector(selector, child_node, matcher, acc)
63 175 end
64 176
177 +
178 + defp get_attribute_values(element, attr_name) when is_tuple(element) do
179 + get_attribute_values([element], attr_name)
180 + end
65 181 defp get_attribute_values(elements, attr_name) do
66 182 Enum.map(elements, fn(el) ->
67 183 { _name, attributes, _childs } = el
68 184
69 - attribute_match?(attributes, attr_name, "")
185 + attribute_match?(attributes, attr_name)
70 186 end)
71 187 |> Enum.reject(fn(x) -> is_nil(x) end)
72 188 |> Enum.map(fn({_attr_name, value}) -> value end)
73 189 end
74 190
75 - defp class_matcher(selector, node, acc) do
191 + defp class_matcher(class_name, node, acc) do
76 192 { _, attributes, _ } = node
77 193
78 - if class_match?(attributes, selector) do
194 + if attribute_match?(attributes, "class", class_name) do
79 195 acc = [node|acc]
80 196 end
  @@ -3,11 +3,11 @@ defmodule Floki.Mixfile do
3 3
4 4 def project do
5 5 [app: :floki,
6 - version: "0.0.3",
6 + version: "0.0.4",
7 7 elixir: "~> 1.0.0",
8 8 package: package,
9 9 description: description,
10 - docs: [readme: true, main: "README.md"],
10 + docs: [readme: true, main: "README"],
11 11 deps: deps]
12 12 end
13 13
  @@ -28,12 +28,15 @@ defmodule Floki.Mixfile do
28 28 #
29 29 # Type `mix help deps` for more examples and options
30 30 defp deps do
31 - []
31 + [{:earmark, "~> 0.1", only: :dev},
32 + {:ex_doc, "~> 0.6", only: :dev}]
32 33 end
33 34
34 35 defp description do
35 36 """
36 - Floki is useful to search elements inside HTML documents using query selectors (like jQuery).
37 + A HTML parser and seeker.
38 +
39 + You can search inside HTML documents using CSS like selectors.
37 40 """
38 41 end
39 42
  @@ -42,7 +45,10 @@ defmodule Floki.Mixfile do
42 45 contributors: ["Philip Sampaio Silva"],
43 46 licenses: ["MIT"],
44 47 files: ["lib", "priv", "mix.exs", "README*", "readme*", "LICENSE*", "license*", "src"],
45 - links: %{"GitHub" => "https://github.com/philss/floki"}
48 + links: %{
49 + "GitHub" => "https://github.com/philss/floki",
50 + "Docs" => "http://hexdocs.pm/floki"
51 + }
46 52 }
47 53 end
48 54 end