diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..d3d4b1ec7e668b3b1621b10f8d92b206f11af70f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [1.0.0] - 2019-12-02 +### Changed +- **BREAKING:** `:fast_html.decode` now returns an array of nodes at the top level, instead of a single node. This was done because it's possible to have more than one root node, for example in (` ` both the comment and the `html` tag are root nodes). + +### Fixed +- Worker going into infinite loop when decoding a document with more than one root node. diff --git a/c_src/myhtml_worker.c b/c_src/myhtml_worker.c index 8285f92ec3ee7abfbcf914f082577e1f0ec78ded..32c6670351f75cb99aa852d5317fd17935f3e0e9 100644 --- a/c_src/myhtml_worker.c +++ b/c_src/myhtml_worker.c @@ -57,7 +57,7 @@ static void err_term(ei_x_buff * response, const char * error_atom); static parse_flags_t decode_parse_flags(state_t * state, int arity); static void decode(state_t * state, ei_x_buff * response, const char * bin_data, size_t bin_size, parse_flags_t parse_flags); -static void build_tree(ei_x_buff * response, myhtml_tree_t * tree, myhtml_tree_node_t * node, parse_flags_t parse_flags); +static void build_tree(ei_x_buff * response, myhtml_tree_t * tree, parse_flags_t parse_flags); static void prepare_node_attrs(ei_x_buff * response, myhtml_tree_node_t * node); static inline char * lowercase(char * c); @@ -298,8 +298,7 @@ static void decode (state_t * state, ei_x_buff * response, const char * bin_data } // build tree - myhtml_tree_node_t * root = myhtml_tree_get_document (tree); - build_tree (response, tree, root->child, parse_flags); + build_tree (response, tree, parse_flags); myhtml_tree_destroy (tree); } @@ -389,12 +388,15 @@ static void prepare_comment (ei_x_buff * response, const char * node_comment, si #endif -static void build_tree (ei_x_buff * response, myhtml_tree_t * tree, myhtml_tree_node_t * node, parse_flags_t parse_flags) +static void build_tree (ei_x_buff * response, myhtml_tree_t * tree, parse_flags_t parse_flags) { - myhtml_tree_node_t * current_node = node; + myhtml_tree_node_t * node = myhtml_tree_get_document (tree); tstack stack; tstack_init (&stack, 30); + tstack_push (&stack, node); + + myhtml_tree_node_t * current_node = node->child; // ok we're going to send an actual response so start encoding it response->index = 0; @@ -411,7 +413,6 @@ static void build_tree (ei_x_buff * response, myhtml_tree_t * tree, myhtml_tree_ { size_t text_len; const char * node_text = myhtml_node_text (current_node, &text_len); - EMIT_LIST_HDR; ei_x_encode_binary (response, node_text, text_len); } @@ -453,11 +454,7 @@ static void build_tree (ei_x_buff * response, myhtml_tree_t * tree, myhtml_tree_ strncpy (tag_string, tag_name, sizeof buffer - 1); } - if (stack.used > 0) - { - EMIT_LIST_HDR; - } - + EMIT_LIST_HDR; prepare_tag_header (response, tag_string, current_node, parse_flags); if (current_node->child) diff --git a/lib/fast_html.ex b/lib/fast_html.ex index 7ed80c9fe95d309c0c28e7510899ce37542d2965..05e6624b0ff9357e6649a4854d0976a349fb63c9 100644 --- a/lib/fast_html.ex +++ b/lib/fast_html.ex @@ -32,36 +32,36 @@ defmodule :fast_html do ## Examples iex> :fast_html.decode("

Hello world

") - {:ok, {"html", [], [{"head", [], []}, {"body", [], [{"h1", [], ["Hello world"]}]}]}} + {:ok, [{"html", [], [{"head", [], []}, {"body", [], [{"h1", [], ["Hello world"]}]}]}]} iex> :fast_html.decode("Hello world", timeout: 0) {:error, :timeout} iex> :fast_html.decode("Hi there") - {:ok, {"html", [], + {:ok, [{"html", [], [{"head", [], []}, - {"body", [], [{"span", [{"class", "hello"}], ["Hi there"]}]}]}} + {"body", [], [{"span", [{"class", "hello"}], ["Hi there"]}]}]}]} iex> :fast_html.decode("") - {:ok, {"html", [], [{"head", [], []}, {"body", [], [comment: " a comment "]}]}} + {:ok, [{"html", [], [{"head", [], []}, {"body", [], [comment: " a comment "]}]}]} iex> :fast_html.decode("
") - {:ok, {"html", [], [{"head", [], []}, {"body", [], [{"br", [], []}]}]}} + {:ok, [{"html", [], [{"head", [], []}, {"body", [], [{"br", [], []}]}]}]} iex> :fast_html.decode("

Hello world

", format: [:html_atoms]) - {:ok, {:html, [], [{:head, [], []}, {:body, [], [{:h1, [], ["Hello world"]}]}]}} + {:ok, [{:html, [], [{:head, [], []}, {:body, [], [{:h1, [], ["Hello world"]}]}]}]} iex> :fast_html.decode("
", format: [:nil_self_closing]) - {:ok, {"html", [], [{"head", [], []}, {"body", [], [{"br", [], nil}]}]}} + {:ok, [{"html", [], [{"head", [], []}, {"body", [], [{"br", [], nil}]}]}]} iex> :fast_html.decode("", format: [:comment_tuple3]) - {:ok, {"html", [], [{"head", [], []}, {"body", [], [{:comment, [], " a comment "}]}]}} + {:ok, [{"html", [], [{"head", [], []}, {"body", [], [{:comment, [], " a comment "}]}]}]} iex> html = "" iex> :fast_html.decode(html, format: [:html_atoms, :nil_self_closing, :comment_tuple3]) - {:ok, {:html, [], + {:ok, [{:html, [], [{:head, [], []}, - {:body, [], [{:comment, [], " a comment "}, {"unknown", [], nil}]}]}} + {:body, [], [{:comment, [], " a comment "}, {"unknown", [], nil}]}]}]} """ @spec decode(String.t(), format: [format_flag()]) :: diff --git a/mix.exs b/mix.exs index d6fc257fcc5e64d5f3cbf7a126b2b278adc2112d..50c97ad01f53fb0d09779458be48da551a00fb99 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule FastHtml.Mixfile do def project do [ app: :fast_html, - version: "0.99.4", + version: "1.0.0", elixir: "~> 1.5", deps: deps(), package: package(), diff --git a/test/fast_html_test.exs b/test/fast_html_test.exs index 60f46bcfac04d740e8cdbd36625e4c6ba1214463..fb88bda1b6a4fd22e027a1b0506f01d5cd074093 100644 --- a/test/fast_html_test.exs +++ b/test/fast_html_test.exs @@ -3,68 +3,78 @@ defmodule :fast_html_test do doctest :fast_html test "doesn't segfault when is encountered" do - assert {:ok, {"html", _attrs, _children}} = :fast_html.decode("
") + assert {:ok, [{"html", _attrs, _children}]} = :fast_html.decode("
") end test "builds a tree, formatted like mochiweb by default" do assert {:ok, - {"html", [], - [ - {"head", [], []}, - {"body", [], - [ - {"br", [], []} - ]} - ]}} = :fast_html.decode("
") + [ + {"html", [], + [ + {"head", [], []}, + {"body", [], + [ + {"br", [], []} + ]} + ]} + ]} = :fast_html.decode("
") end test "builds a tree, html tags as atoms" do assert {:ok, - {:html, [], - [ - {:head, [], []}, - {:body, [], - [ - {:br, [], []} - ]} - ]}} = :fast_html.decode("
", format: [:html_atoms]) + [ + {:html, [], + [ + {:head, [], []}, + {:body, [], + [ + {:br, [], []} + ]} + ]} + ]} = :fast_html.decode("
", format: [:html_atoms]) end test "builds a tree, nil self closing" do assert {:ok, - {"html", [], - [ - {"head", [], []}, - {"body", [], - [ - {"br", [], nil}, - {"esi:include", [], nil} - ]} - ]}} = :fast_html.decode("
", format: [:nil_self_closing]) + [ + {"html", [], + [ + {"head", [], []}, + {"body", [], + [ + {"br", [], nil}, + {"esi:include", [], nil} + ]} + ]} + ]} = :fast_html.decode("
", format: [:nil_self_closing]) end test "builds a tree, multiple format options" do assert {:ok, - {:html, [], - [ - {:head, [], []}, - {:body, [], - [ - {:br, [], nil} - ]} - ]}} = :fast_html.decode("
", format: [:html_atoms, :nil_self_closing]) + [ + {:html, [], + [ + {:head, [], []}, + {:body, [], + [ + {:br, [], nil} + ]} + ]} + ]} = :fast_html.decode("
", format: [:html_atoms, :nil_self_closing]) end test "attributes" do assert {:ok, - {:html, [], - [ - {:head, [], []}, - {:body, [], - [ - {:span, [{"id", "test"}, {"class", "foo garble"}], []} - ]} - ]}} = + [ + {:html, [], + [ + {:head, [], []}, + {:body, [], + [ + {:span, [{"id", "test"}, {"class", "foo garble"}], []} + ]} + ]} + ]} = :fast_html.decode(~s'', format: [:html_atoms] ) @@ -72,14 +82,16 @@ defmodule :fast_html_test do test "single attributes" do assert {:ok, - {:html, [], - [ - {:head, [], []}, - {:body, [], - [ - {:button, [{"disabled", "disabled"}, {"class", "foo garble"}], []} - ]} - ]}} = + [ + {:html, [], + [ + {:head, [], []}, + {:body, [], + [ + {:button, [{"disabled", "disabled"}, {"class", "foo garble"}], []} + ]} + ]} + ]} = :fast_html.decode(~s'