Current section

86 Versions

Jump to

Compare versions

5 files changed
+81 additions
-28 deletions
  @@ -43,6 +43,10 @@ Floki.find(html, "#content")
43 43 Floki.find(html, "p.headline")
44 44 # => [{"p", [{"class", "headline"}], ["Floki"]}]
45 45
46 + Floki.find(html, "p.headline")
47 + |> Floki.raw_html
48 + # => <p class="headline">Floki</p>
49 +
46 50
47 51 Floki.find(html, "a")
48 52 # => [{"a", [{"href", "http://github.com/philss/floki"}], ["Github page"]},
  @@ -129,6 +133,14 @@ Floki.find(html, ".example")
129 133 # => [{"div", [{"class", "example"}], []}]
130 134 ```
131 135
136 + To convert your node tree back to raw HTML (spaces are ignored):
137 +
138 + ```elixir
139 + Floki.find(html, ".example")
140 + |> Flok.raw_html
141 + # => <div class="example"></div>
142 + ```
143 +
132 144 To fetch some attribute from elements, try:
133 145
134 146 ```elixir
  @@ -22,4 +22,4 @@
22 22 [{<<"app">>,<<"mochiweb">>},
23 23 {<<"optional">>,false},
24 24 {<<"requirement">>,<<"~> 2.12.2">>}]}]}.
25 - {<<"version">>,<<"0.5.0">>}.
25 + {<<"version">>,<<"0.6.0">>}.
  @@ -77,6 +77,47 @@ defmodule Floki do
77 77 Parser.parse(html)
78 78 end
79 79
80 + @self_closing_tags ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "mete", "param", "source", "track", "wbr"]
81 +
82 + @doc """
83 + Converts HTML tree to raw HTML.
84 + Note that the resultant HTML may be different from the original one.
85 + Spaces after tags and doctypes are ignored.
86 +
87 + ## Examples
88 +
89 + iex> Floki.parse(~s(<div class="wrapper">my content</div>)) |> Floki.raw_html
90 + ~s(<div class="wrapper">my content</div>)
91 +
92 + """
93 +
94 + def raw_html(html_tree), do: raw_html(html_tree, "")
95 + defp raw_html([], html), do: html
96 + defp raw_html(tuple, html) when is_tuple(tuple), do: raw_html([tuple], html)
97 + defp raw_html([value|tail], html) when is_binary(value), do: raw_html(tail, html <> value)
98 + defp raw_html([{elem, attrs, value}|tail], html) do
99 + raw_html(tail, html <> tag_for(elem, attrs |> tag_attrs, value))
100 + end
101 +
102 + defp tag_attrs(attr_list) do
103 + attr_list
104 + |> Enum.reduce("", fn({attr, value}, t) -> ~s(#{t} #{attr}="#{value}") end)
105 + |> String.strip
106 + end
107 +
108 + defp tag_for(elem, attrs, _value) when elem in @self_closing_tags do
109 + case attrs do
110 + "" -> "<#{elem}/>"
111 + _ -> "<#{elem} #{attrs}/>"
112 + end
113 + end
114 + defp tag_for(elem, attrs, value) do
115 + case attrs do
116 + "" -> "<#{elem}>#{raw_html(value)}</#{elem}>"
117 + _ -> "<#{elem} #{attrs}>#{raw_html(value)}</#{elem}>"
118 + end
119 + end
120 +
80 121 @doc """
81 122 Find elements inside a HTML tree or string.
  @@ -1,6 +1,6 @@
1 1 defmodule Floki.Finder do
2 2 @moduledoc """
3 - The finder engine transverse the HTML tree searching for nodes matching
3 + The finder engine traverse the HTML tree searching for nodes matching
4 4 selectors.
5 5 """
6 6
  @@ -20,7 +20,7 @@ defmodule Floki.Finder do
20 20 selectors = get_selectors(selector_as_string)
21 21
22 22 html_tree
23 - |> transverse([], selectors, [])
23 + |> traverse([], selectors, [])
24 24 |> Enum.reverse
25 25 end
26 26
  @@ -30,21 +30,21 @@ defmodule Floki.Finder do
30 30 end
31 31 end
32 32
33 - defp transverse(_, _, [], acc), do: acc
34 - defp transverse({}, _, _, acc), do: acc
35 - defp transverse([], _, _, acc), do: acc
36 - defp transverse(string, _, _, acc) when is_binary(string), do: acc
37 - defp transverse({:comment, _comment},_, _, acc), do: acc
38 - defp transverse({:pi, _xml, _xml_attrs},_, _, acc), do: acc
39 - defp transverse([node|sibling_nodes], _, selectors, acc) do
40 - acc = transverse(node, sibling_nodes, selectors, acc)
41 - transverse(sibling_nodes, [], selectors, acc)
33 + defp traverse(_, _, [], acc), do: acc
34 + defp traverse({}, _, _, acc), do: acc
35 + defp traverse([], _, _, acc), do: acc
36 + defp traverse(string, _, _, acc) when is_binary(string), do: acc
37 + defp traverse({:comment, _comment},_, _, acc), do: acc
38 + defp traverse({:pi, _xml, _xml_attrs},_, _, acc), do: acc
39 + defp traverse([node|sibling_nodes], _, selectors, acc) do
40 + acc = traverse(node, sibling_nodes, selectors, acc)
41 + traverse(sibling_nodes, [], selectors, acc)
42 42 end
43 - defp transverse(node, sibling_nodes, [head_selector|tail_selectors], acc) do
44 - acc = transverse(node, sibling_nodes, head_selector, acc)
45 - transverse(node, sibling_nodes, tail_selectors, acc)
43 + defp traverse(node, sibling_nodes, [head_selector|tail_selectors], acc) do
44 + acc = traverse(node, sibling_nodes, head_selector, acc)
45 + traverse(node, sibling_nodes, tail_selectors, acc)
46 46 end
47 - defp transverse({_, _, children_nodes} = node, sibling_nodes, selector, acc) do
47 + defp traverse({_, _, children_nodes} = node, sibling_nodes, selector, acc) do
48 48 acc =
49 49 if Selector.match?(node, selector) do
50 50 combinator = selector.combinator
  @@ -53,13 +53,13 @@ defmodule Floki.Finder do
53 53 _ ->
54 54 case combinator.match_type do
55 55 :descendant ->
56 - transverse(children_nodes, sibling_nodes, combinator.selector, acc)
56 + traverse(children_nodes, sibling_nodes, combinator.selector, acc)
57 57 :child ->
58 - transverse_child(children_nodes, sibling_nodes, combinator.selector, acc)
58 + traverse_child(children_nodes, sibling_nodes, combinator.selector, acc)
59 59 :sibling ->
60 - transverse_sibling(children_nodes, sibling_nodes, combinator.selector, acc)
60 + traverse_sibling(children_nodes, sibling_nodes, combinator.selector, acc)
61 61 :general_sibling ->
62 - transverse_general_sibling(children_nodes, sibling_nodes, combinator.selector, acc)
62 + traverse_general_sibling(children_nodes, sibling_nodes, combinator.selector, acc)
63 63 other ->
64 64 raise "Combinator of type \"#{other}\" not implemented"
65 65 end
  @@ -68,17 +68,17 @@ defmodule Floki.Finder do
68 68 acc
69 69 end
70 70
71 - transverse(children_nodes, sibling_nodes, selector, acc)
71 + traverse(children_nodes, sibling_nodes, selector, acc)
72 72 end
73 73
74 - defp transverse_child(nodes, sibling_nodes, selector, acc) do
74 + defp traverse_child(nodes, sibling_nodes, selector, acc) do
75 75 Enum.reduce(nodes, acc, fn(n, res_acc) ->
76 76 if Selector.match?(n, selector) do
77 77 case selector.combinator do
78 78 nil -> [n|res_acc]
79 79 _ ->
80 80 {_, _, children_nodes} = n
81 - transverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc)
81 + traverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc)
82 82 end
83 83 else
84 84 res_acc
  @@ -86,7 +86,7 @@ defmodule Floki.Finder do
86 86 end)
87 87 end
88 88
89 - defp transverse_sibling(_nodes, sibling_nodes, selector, acc) do
89 + defp traverse_sibling(_nodes, sibling_nodes, selector, acc) do
90 90 sibling_node = Enum.drop_while(sibling_nodes, &ignore_node?/1) |> hd
91 91
92 92 if Selector.match?(sibling_node, selector) do
  @@ -94,14 +94,14 @@ defmodule Floki.Finder do
94 94 nil -> [sibling_node|acc]
95 95 _ ->
96 96 {_, _, children_nodes} = sibling_node
97 - transverse(children_nodes, sibling_nodes, selector.combinator.selector, acc)
97 + traverse(children_nodes, sibling_nodes, selector.combinator.selector, acc)
98 98 end
99 99 else
100 100 acc
101 101 end
102 102 end
103 103
104 - defp transverse_general_sibling(_nodes, sibling_nodes, selector, acc) do
104 + defp traverse_general_sibling(_nodes, sibling_nodes, selector, acc) do
105 105 sibling_nodes = Enum.drop_while(sibling_nodes, &ignore_node?/1)
106 106
107 107 Enum.reduce(sibling_nodes, acc, fn(sibling_node, res_acc) ->
  @@ -110,7 +110,7 @@ defmodule Floki.Finder do
110 110 nil -> [sibling_node|res_acc]
111 111 _ ->
112 112 {_, _, children_nodes} = sibling_node
113 - transverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc)
113 + traverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc)
114 114 end
115 115 else
116 116 res_acc
  @@ -3,7 +3,7 @@ defmodule Floki.Mixfile do
3 3
4 4 def project do
5 5 [app: :floki,
6 - version: "0.5.0",
6 + version: "0.6.0",
7 7 name: "Floki",
8 8 elixir: ">= 1.0.0",
9 9 package: package,