Current section

86 Versions

Jump to

Compare versions

13 files changed
+1093 additions
-220 deletions
  @@ -91,7 +91,7 @@ You can install Floki by adding a dependency to your mix file (mix.exs):
91 91 ```elixir
92 92 defp deps do
93 93 [
94 - {:floki, "~> 0.3"}
94 + {:floki, "~> 0.4"}
95 95 ]
96 96 end
97 97 ```
  @@ -5,9 +5,13 @@
5 5 <<"A HTML parser and searcher.\n\nYou can search inside HTML documents using CSS like selectors.">>}.
6 6 {<<"elixir">>,<<">= 1.0.0">>}.
7 7 {<<"files">>,
8 - [<<"lib/floki.ex">>,<<"lib/floki/deep_text.ex">>,<<"lib/floki/finder.ex">>,
9 - <<"lib/floki/flat_text.ex">>,<<"lib/floki/matchers.ex">>,
10 - <<"lib/floki/parser.ex">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>]}.
8 + [<<"lib/floki.ex">>,<<"lib/floki/attribute_selector.ex">>,
9 + <<"lib/floki/combinator.ex">>,<<"lib/floki/deep_text.ex">>,
10 + <<"lib/floki/finder.ex">>,<<"lib/floki/flat_text.ex">>,
11 + <<"lib/floki/parser.ex">>,<<"lib/floki/selector.ex">>,
12 + <<"lib/floki/selector_parser.ex">>,<<"lib/floki/selector_tokenizer.ex">>,
13 + <<"src/floki_selector_lexer.erl">>,<<"src/floki_selector_lexer.xrl">>,
14 + <<"mix.exs">>,<<"README.md">>,<<"LICENSE">>]}.
11 15 {<<"licenses">>,[<<"MIT">>]}.
12 16 {<<"links">>,
13 17 [{<<"Docs">>,<<"http://hexdocs.pm/floki">>},
  @@ -18,4 +22,4 @@
18 22 [{<<"app">>,<<"mochiweb">>},
19 23 {<<"optional">>,false},
20 24 {<<"requirement">>,<<"~> 2.12.2">>}]}]}.
21 - {<<"version">>,<<"0.3.3">>}.
25 + {<<"version">>,<<"0.4.0">>}.
  @@ -81,18 +81,13 @@ defmodule Floki do
81 81 Finds elements inside a HTML tree or string.
82 82 You can search by class, tag name or id.
83 83
84 - It is possible to compose searches:
85 -
86 - Floki.find(html_string, ".class")
87 - |> Floki.find(".another-class-inside-small-scope")
88 -
89 84 ## Examples
90 85
91 86 iex> Floki.find("<p><span class=hint>hello</span></p>", ".hint")
92 87 [{"span", [{"class", "hint"}], ["hello"]}]
93 88
94 - iex> "<body><div id=important><div>Content</div></div></body>" |> Floki.find("#important")
95 - {"div", [{"id", "important"}], [{"div", [], ["Content"]}]}
89 + iex> Floki.find("<body><div id=important><div>Content</div></div></body>", "#important")
90 + [{"div", [{"id", "important"}], [{"div", [], ["Content"]}]}]
96 91
97 92 iex> Floki.find("<p><a href='https://google.com'>Google</a></p>", "a")
98 93 [{"a", [{"href", "https://google.com"}], ["Google"]}]
  @@ -101,49 +96,13 @@ defmodule Floki do
101 96
102 97 @spec find(binary | html_tree, binary) :: html_tree
103 98
99 + def find(html, selector) when is_binary(html) do
100 + Floki.Parser.parse(html) |> Finder.find(selector)
101 + end
104 102 def find(html, selector) do
105 103 Finder.find(html, selector)
106 104 end
107 105
108 - @doc """
109 - Returns a list with attribute values for a given selector.
110 -
111 - ## Examples
112 -
113 - iex> Floki.attribute("<a href='https://google.com'>Google</a>", "a", "href")
114 - ["https://google.com"]
115 -
116 - """
117 -
118 - @spec attribute(binary | html_tree, binary, binary) :: list
119 -
120 - def attribute(html, selector, attribute_name) do
121 - html
122 - |> find(selector)
123 - |> Finder.attribute_values(attribute_name)
124 - end
125 -
126 - @doc """
127 - Returns a list with attribute values from elements.
128 -
129 - ## Examples
130 -
131 - iex> Floki.attribute("<a href=https://google.com>Google</a>", "href")
132 - ["https://google.com"]
133 -
134 - """
135 -
136 - @spec attribute(binary | html_tree, binary) :: list
137 -
138 - def attribute(html_tree, attribute_name) when is_binary(html_tree) do
139 - html_tree
140 - |> parse
141 - |> Finder.attribute_values(attribute_name)
142 - end
143 - def attribute(elements, attribute_name) do
144 - Finder.attribute_values(elements, attribute_name)
145 - end
146 -
147 106 @doc """
148 107 Returns the text nodes from a HTML tree.
149 108 By default, it will perform a deep search through the HTML tree.
  @@ -176,4 +135,65 @@ defmodule Floki do
176 135
177 136 search_strategy.get(html_tree)
178 137 end
138 +
139 + @doc """
140 + Returns a list with attribute values for a given selector.
141 +
142 + ## Examples
143 +
144 + iex> Floki.attribute("<a href='https://google.com'>Google</a>", "a", "href")
145 + ["https://google.com"]
146 +
147 + """
148 +
149 + @spec attribute(binary | html_tree, binary, binary) :: list
150 +
151 + def attribute(html, selector, attribute_name) do
152 + html
153 + |> find(selector)
154 + |> attribute_values(attribute_name)
155 + end
156 +
157 + @doc """
158 + Returns a list with attribute values from elements.
159 +
160 + ## Examples
161 +
162 + iex> Floki.attribute("<a href=https://google.com>Google</a>", "href")
163 + ["https://google.com"]
164 +
165 + """
166 +
167 + @spec attribute(binary | html_tree, binary) :: list
168 +
169 + def attribute(html_tree, attribute_name) when is_binary(html_tree) do
170 + html_tree
171 + |> parse
172 + |> attribute_values(attribute_name)
173 + end
174 + def attribute(elements, attribute_name) do
175 + attribute_values(elements, attribute_name)
176 + end
177 +
178 + defp attribute_values(element, attr_name) when is_tuple(element) do
179 + attribute_values([element], attr_name)
180 + end
181 + defp attribute_values(elements, attr_name) do
182 + values = Enum.reduce elements, [], fn({_, attributes, _}, acc) ->
183 + case attribute_match?(attributes, attr_name) do
184 + {_attr_name, value} ->
185 + [value|acc]
186 + _ ->
187 + acc
188 + end
189 + end
190 +
191 + Enum.reverse(values)
192 + end
193 +
194 + defp attribute_match?(attributes, attribute_name) do
195 + Enum.find attributes, fn({attr_name, _}) ->
196 + attr_name == attribute_name
197 + end
198 + end
179 199 end
  @@ -0,0 +1,45 @@
1 + defmodule Floki.AttributeSelector do
2 + alias Floki.AttributeSelector
3 +
4 + defstruct match_type: nil, attribute: nil, value: nil
5 +
6 + def match?(attributes, s = %AttributeSelector{match_type: nil, value: nil}) do
7 + attribute_present?(s.attribute, attributes)
8 + end
9 + def match?(attributes, s = %AttributeSelector{match_type: :equal}) do
10 + get_value(s.attribute, attributes) == s.value
11 + end
12 + def match?(attributes, s = %AttributeSelector{match_type: :includes}) do
13 + value = get_value(s.attribute, attributes)
14 +
15 + whitespace_values = String.split(value, " ")
16 +
17 + Enum.any? whitespace_values, fn(v) -> v == s.value end
18 + end
19 + def match?(attributes, s = %AttributeSelector{match_type: :dash_match}) do
20 + value = get_value(s.attribute, attributes)
21 +
22 + value == s.value || String.starts_with?(value, "#{s.value}-")
23 + end
24 + def match?(attributes, s = %AttributeSelector{match_type: :prefix_match}) do
25 + get_value(s.attribute, attributes) |> String.starts_with?(s.value)
26 + end
27 + def match?(attributes, s = %AttributeSelector{match_type: :sufix_match}) do
28 + get_value(s.attribute, attributes) |> String.ends_with?(s.value)
29 + end
30 + def match?(attributes, s = %AttributeSelector{match_type: :substring_match}) do
31 + get_value(s.attribute, attributes) |> String.contains?(s.value)
32 + end
33 +
34 + defp get_value(attr_name, attributes) do
35 + {_attr_name, value} = Enum.find attributes, {attr_name, ""}, fn({k, _v}) ->
36 + k == attr_name
37 + end
38 +
39 + value
40 + end
41 +
42 + defp attribute_present?(name, attributes) do
43 + Enum.any? attributes, fn({k, _v}) -> k == name end
44 + end
45 + end
  @@ -0,0 +1,3 @@
1 + defmodule Floki.Combinator do
2 + defstruct match_type: nil, selector: nil
3 + end
Loading more files…