Packages
floki
0.10.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
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…