Current section

86 Versions

Jump to

Compare versions

7 files changed
+79 additions
-49 deletions
  @@ -100,7 +100,7 @@ Add Floki in your `mix.exs`, as a dependency:
100 100 ```elixir
101 101 defp deps do
102 102 [
103 - {:floki, "~> 0.8.1"}
103 + {:floki, "~> 0.9.0"}
104 104 ]
105 105 end
106 106 ```
  @@ -2,7 +2,7 @@
2 2 {<<"build_tools">>,[<<"mix">>]}.
3 3 {<<"description">>,
4 4 <<"Floki is a simple HTML parser that enables search for nodes using CSS selectors.">>}.
5 - {<<"elixir">>,<<">= 1.0.0">>}.
5 + {<<"elixir">>,<<">= 1.1.0">>}.
6 6 {<<"files">>,
7 7 [<<"lib/floki.ex">>,<<"lib/floki/attribute_selector.ex">>,
8 8 <<"lib/floki/combinator.ex">>,<<"lib/floki/deep_text.ex">>,
  @@ -22,4 +22,4 @@
22 22 {<<"name">>,<<"mochiweb_html">>},
23 23 {<<"optional">>,false},
24 24 {<<"requirement">>,<<"~> 2.15">>}]]}.
25 - {<<"version">>,<<"0.9.0">>}.
25 + {<<"version">>,<<"0.10.0">>}.
  @@ -14,7 +14,7 @@ defmodule Floki.Finder do
14 14 @doc """
15 15 Find elements inside a HTML tree.
16 16
17 - Second argument can be either a selector string, a selector struct or an array of selector structs.
17 + Second argument can be either a selector string, a selector struct or a list of selector structs.
18 18 """
19 19
20 20 @spec find(html_tree, selector) :: html_tree
  @@ -68,18 +68,7 @@ defmodule Floki.Finder do
68 68 case combinator do
69 69 nil -> [html_node|acc]
70 70 _ ->
71 - case combinator.match_type do
72 - :descendant ->
73 - traverse(children_nodes, sibling_nodes, combinator.selector, acc)
74 - :child ->
75 - traverse_child(children_nodes, sibling_nodes, combinator.selector, acc)
76 - :sibling ->
77 - traverse_sibling(children_nodes, sibling_nodes, combinator.selector, acc)
78 - :general_sibling ->
79 - traverse_general_sibling(children_nodes, sibling_nodes, combinator.selector, acc)
80 - other ->
81 - raise "Combinator of type \"#{other}\" not implemented"
82 - end
71 + traverse_using(combinator, children_nodes, sibling_nodes, acc)
83 72 end
84 73 else
85 74 acc
  @@ -88,14 +77,33 @@ defmodule Floki.Finder do
88 77 traverse(children_nodes, sibling_nodes, selector, acc)
89 78 end
90 79
80 + defp traverse_using(combinator, children_nodes, sibling_nodes, acc) do
81 + selector = combinator.selector
82 +
83 + case combinator.match_type do
84 + :descendant ->
85 + traverse(children_nodes, sibling_nodes, selector, acc)
86 + :child ->
87 + traverse_child(children_nodes, sibling_nodes, selector, acc)
88 + :sibling ->
89 + traverse_sibling(children_nodes, sibling_nodes, selector, acc)
90 + :general_sibling ->
91 + traverse_general_sibling(children_nodes, sibling_nodes, selector, acc)
92 + other ->
93 + raise "Combinator of type \"#{other}\" not implemented"
94 + end
95 + end
96 +
91 97 defp traverse_child(nodes, sibling_nodes, selector, acc) do
92 98 Enum.reduce(nodes, acc, fn(n, res_acc) ->
93 99 if Selector.match?(n, selector) do
94 - case selector.combinator do
100 + combinator = selector.combinator
101 +
102 + case combinator do
95 103 nil -> [n|res_acc]
96 104 _ ->
97 105 {_, _, children_nodes} = n
98 - traverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc)
106 + traverse_using(combinator, children_nodes, sibling_nodes, res_acc)
99 107 end
100 108 else
101 109 res_acc
  @@ -6,12 +6,12 @@ defmodule Floki.Selector do
6 6 alias Floki.Selector
7 7 alias Floki.AttributeSelector
8 8
9 - defstruct id: nil, type: nil, classes: [], attributes: [], combinator: nil
9 + defstruct id: nil, type: nil, classes: [], attributes: [], combinator: nil, namespace: nil
10 10
11 11 @doc """
12 12 Returns if a given node matches with a given selector.
13 13 """
14 - def match?(_node, %Selector{id: nil, type: nil, classes: [], attributes: []}) do
14 + def match?(_node, %Selector{id: nil, type: nil, classes: [], attributes: [], namespace: nil}) do
15 15 false
16 16 end
17 17 def match?(nil, _selector), do: false
  @@ -19,6 +19,7 @@ defmodule Floki.Selector do
19 19 def match?({:pi, _xml, _xml_attrs}, _selector), do: false
20 20 def match?(html_node, selector) do
21 21 id_match?(html_node, selector.id)
22 + && namespace_match?(html_node, selector.namespace)
22 23 && type_match?(html_node, selector.type)
23 24 && classes_matches?(html_node, selector.classes)
24 25 && attributes_matches?(html_node, selector.attributes)
  @@ -35,9 +36,26 @@ defmodule Floki.Selector do
35 36 end
36 37 end
37 38
39 + defp namespace_match?(_node, nil), do: true
40 + defp namespace_match?(_node, "*"), do: true
41 + defp namespace_match?({type_maybe_with_namespace, _, _}, namespace) do
42 + case String.split(type_maybe_with_namespace, ":") do
43 + [ns, _type] ->
44 + ns == namespace
45 + [_type] -> false
46 + end
47 + end
48 +
38 49 defp type_match?(_node, nil), do: true
39 - defp type_match?({type, _, _}, type), do: true
40 - defp type_match?({_type, _, _}, "*"), do: true
50 + defp type_match?(_node, "*"), do: true
51 + defp type_match?({type_maybe_with_namespace, _, _}, type) do
52 + case String.split(type_maybe_with_namespace, ":") do
53 + [_ns, tp] ->
54 + tp == type
55 + [tp] ->
56 + tp == type
57 + end
58 + end
41 59 defp type_match?(_, _), do: false
42 60
43 61 defp classes_matches?(_node, []), do: true
  @@ -14,52 +14,55 @@ defmodule Floki.SelectorParser do
14 14 """
15 15
16 16 def parse(tokens) do
17 - parse(tokens, %Selector{})
17 + do_parse(tokens, %Selector{})
18 18 end
19 19
20 - defp parse([], selector), do: selector
21 - defp parse([{:identifier, _, type}|t], selector) do
22 - parse(t, %{selector | type: to_string(type)})
20 + defp do_parse([], selector), do: selector
21 + defp do_parse([{:identifier, _, namespace}, {:namespace_pipe, _}|t], selector) do
22 + do_parse(t, %{selector | namespace: to_string(namespace)})
23 23 end
24 - defp parse([{'*', _}|t], selector) do
25 - parse(t, %{selector | type: "*"})
24 + defp do_parse([{:identifier, _, type}|t], selector) do
25 + do_parse(t, %{selector | type: to_string(type)})
26 26 end
27 - defp parse([{:hash, _, id}|t], selector) do
28 - parse(t, %{selector | id: to_string(id)})
27 + defp do_parse([{'*', _}|t], selector) do
28 + do_parse(t, %{selector | type: "*"})
29 29 end
30 - defp parse([{:class, _, class}|t], selector) do
31 - parse(t, %{selector | classes: [to_string(class)|selector.classes]})
30 + defp do_parse([{:hash, _, id}|t], selector) do
31 + do_parse(t, %{selector | id: to_string(id)})
32 32 end
33 - defp parse([{'[', _}|t], selector) do
33 + defp do_parse([{:class, _, class}|t], selector) do
34 + do_parse(t, %{selector | classes: [to_string(class)|selector.classes]})
35 + end
36 + defp do_parse([{'[', _}|t], selector) do
34 37 {t, result} = consume_attribute(t)
35 38
36 - parse(t, %{selector | attributes: [result|selector.attributes]})
39 + do_parse(t, %{selector | attributes: [result|selector.attributes]})
37 40 end
38 - defp parse([{:space, _}|t], selector) do
41 + defp do_parse([{:space, _}|t], selector) do
39 42 {t, combinator} = consume_combinator(t, :descendant)
40 43
41 - parse(t, %{selector | combinator: combinator})
44 + do_parse(t, %{selector | combinator: combinator})
42 45 end
43 - defp parse([{:greater, _}|t], selector) do
46 + defp do_parse([{:greater, _}|t], selector) do
44 47 {t, combinator} = consume_combinator(t, :child)
45 48
46 - parse(t, %{selector | combinator: combinator})
49 + do_parse(t, %{selector | combinator: combinator})
47 50 end
48 - defp parse([{:plus, _}|t], selector) do
51 + defp do_parse([{:plus, _}|t], selector) do
49 52 {t, combinator} = consume_combinator(t, :sibling)
50 53
51 - parse(t, %{selector | combinator: combinator})
54 + do_parse(t, %{selector | combinator: combinator})
52 55 end
53 - defp parse([{:tilde, _}|t], selector) do
56 + defp do_parse([{:tilde, _}|t], selector) do
54 57 {t, combinator} = consume_combinator(t, :general_sibling)
55 58
56 - parse(t, %{selector | combinator: combinator})
59 + do_parse(t, %{selector | combinator: combinator})
57 60 end
58 - defp parse([{:unknown, _, unknown}|t], selector) do
61 + defp do_parse([{:unknown, _, unknown}|t], selector) do
59 62 # TODO: find a better way to notify unknown tokens
60 63 IO.puts("Unknown token #{inspect unknown}. Ignoring.")
61 64
62 - parse(t, selector)
65 + do_parse(t, selector)
63 66 end
64 67
65 68 defp consume_attribute(tokens), do: consume_attribute(:consuming, tokens, %AttributeSelector{})
Loading more files…