You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by be...@apache.org on 2014/02/13 17:05:59 UTC

[04/50] mochiweb commit: updated refs/heads/import-upstream to 8eb1f22

move tests into separate module


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/commit/f540b156
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/tree/f540b156
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/diff/f540b156

Branch: refs/heads/import-upstream
Commit: f540b156a75e375eb6331f4aa15d24bb179bb4dc
Parents: bfc0c38
Author: Bob Ippolito <bo...@redivi.com>
Authored: Mon May 6 14:43:57 2013 -0700
Committer: Bob Ippolito <bo...@redivi.com>
Committed: Mon May 6 14:43:57 2013 -0700

----------------------------------------------------------------------
 src/mochiweb_html.erl        | 574 +-------------------------------------
 test/mochiweb_html_tests.erl | 572 +++++++++++++++++++++++++++++++++++++
 2 files changed, 575 insertions(+), 571 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/f540b156/src/mochiweb_html.erl
----------------------------------------------------------------------
diff --git a/src/mochiweb_html.erl b/src/mochiweb_html.erl
index 1d90f3b..c38f138 100644
--- a/src/mochiweb_html.erl
+++ b/src/mochiweb_html.erl
@@ -5,6 +5,9 @@
 -module(mochiweb_html).
 -export([tokens/1, parse/1, parse_tokens/1, to_tokens/1, escape/1,
          escape_attr/1, to_html/1]).
+-ifdef(TEST).
+-export([destack/1, destack/2, is_singleton/1]).
+-endif.
 
 %% This is a macro to placate syntax highlighters..
 -define(QUOTE, $\").
@@ -759,574 +762,3 @@ tokenize_textarea(Bin, S=#decoder{offset=O}, Start) ->
         <<_:Start/binary, Raw/binary>> ->
             {{data, Raw, false}, S}
     end.
-
-
-%%
-%% Tests
-%%
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
-to_html_test() ->
-    ?assertEqual(
-       <<"<html><head><title>hey!</title></head><body><p class=\"foo\">what's up<br /></p><div>sucka</div>RAW!<!-- comment! --></body></html>">>,
-       iolist_to_binary(
-         to_html({html, [],
-                  [{<<"head">>, [],
-                    [{title, <<"hey!">>}]},
-                   {body, [],
-                    [{p, [{class, foo}], [<<"what's">>, <<" up">>, {br}]},
-                     {'div', <<"sucka">>},
-                     {'=', <<"RAW!">>},
-                     {comment, <<" comment! ">>}]}]}))),
-    ?assertEqual(
-       <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">">>,
-       iolist_to_binary(
-         to_html({doctype,
-                  [<<"html">>, <<"PUBLIC">>,
-                   <<"-//W3C//DTD XHTML 1.0 Transitional//EN">>,
-                   <<"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>]}))),
-    ?assertEqual(
-       <<"<html><?xml:namespace prefix=\"o\" ns=\"urn:schemas-microsoft-com:office:office\"?></html>">>,
-       iolist_to_binary(
-         to_html({<<"html">>,[],
-                  [{pi, <<"xml:namespace">>,
-                    [{<<"prefix">>,<<"o">>},
-                     {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}]}))),
-    ok.
-
-escape_test() ->
-    ?assertEqual(
-       <<"&amp;quot;\"word &gt;&lt;&lt;up!&amp;quot;">>,
-       escape(<<"&quot;\"word ><<up!&quot;">>)),
-    ?assertEqual(
-       <<"&amp;quot;\"word &gt;&lt;&lt;up!&amp;quot;">>,
-       escape("&quot;\"word ><<up!&quot;")),
-    ?assertEqual(
-       <<"&amp;quot;\"word &gt;&lt;&lt;up!&amp;quot;">>,
-       escape('&quot;\"word ><<up!&quot;')),
-    ok.
-
-escape_attr_test() ->
-    ?assertEqual(
-       <<"&amp;quot;&quot;word &gt;&lt;&lt;up!&amp;quot;">>,
-       escape_attr(<<"&quot;\"word ><<up!&quot;">>)),
-    ?assertEqual(
-       <<"&amp;quot;&quot;word &gt;&lt;&lt;up!&amp;quot;">>,
-       escape_attr("&quot;\"word ><<up!&quot;")),
-    ?assertEqual(
-       <<"&amp;quot;&quot;word &gt;&lt;&lt;up!&amp;quot;">>,
-       escape_attr('&quot;\"word ><<up!&quot;')),
-    ?assertEqual(
-       <<"12345">>,
-       escape_attr(12345)),
-    ?assertEqual(
-       <<"1.5">>,
-       escape_attr(1.5)),
-    ok.
-
-tokens_test() ->
-    ?assertEqual(
-       [{start_tag, <<"foo">>, [{<<"bar">>, <<"baz">>},
-                                {<<"wibble">>, <<"wibble">>},
-                                {<<"alice">>, <<"bob">>}], true}],
-       tokens(<<"<foo bar=baz wibble='wibble' alice=\"bob\"/>">>)),
-    ?assertEqual(
-       [{start_tag, <<"foo">>, [{<<"bar">>, <<"baz">>},
-                                {<<"wibble">>, <<"wibble">>},
-                                {<<"alice">>, <<"bob">>}], true}],
-       tokens(<<"<foo bar=baz wibble='wibble' alice=bob/>">>)),
-    ?assertEqual(
-       [{comment, <<"[if lt IE 7]>\n<style type=\"text/css\">\n.no_ie { display: none; }\n</style>\n<![endif]">>}],
-       tokens(<<"<!--[if lt IE 7]>\n<style type=\"text/css\">\n.no_ie { display: none; }\n</style>\n<![endif]-->">>)),
-    ?assertEqual(
-       [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
-        {data, <<" A= B <= C ">>, false},
-        {end_tag, <<"script">>}],
-       tokens(<<"<script type=\"text/javascript\"> A= B <= C </script>">>)),
-    ?assertEqual(
-       [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
-        {data, <<" A= B <= C ">>, false},
-        {end_tag, <<"script">>}],
-       tokens(<<"<script type =\"text/javascript\"> A= B <= C </script>">>)),
-    ?assertEqual(
-       [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
-        {data, <<" A= B <= C ">>, false},
-        {end_tag, <<"script">>}],
-       tokens(<<"<script type = \"text/javascript\"> A= B <= C </script>">>)),
-    ?assertEqual(
-       [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
-        {data, <<" A= B <= C ">>, false},
-        {end_tag, <<"script">>}],
-       tokens(<<"<script type= \"text/javascript\"> A= B <= C </script>">>)),
-    ?assertEqual(
-       [{start_tag, <<"textarea">>, [], false},
-        {data, <<"<html></body>">>, false},
-        {end_tag, <<"textarea">>}],
-       tokens(<<"<textarea><html></body></textarea>">>)),
-    ?assertEqual(
-       [{start_tag, <<"textarea">>, [], false},
-        {data, <<"<html></body></textareaz>">>, false}],
-       tokens(<<"<textarea ><html></body></textareaz>">>)),
-    ?assertEqual(
-       [{pi, <<"xml:namespace">>,
-         [{<<"prefix">>,<<"o">>},
-          {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}],
-       tokens(<<"<?xml:namespace prefix=\"o\" ns=\"urn:schemas-microsoft-com:office:office\"?>">>)),
-    ?assertEqual(
-       [{pi, <<"xml:namespace">>,
-         [{<<"prefix">>,<<"o">>},
-          {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}],
-       tokens(<<"<?xml:namespace prefix=o ns=urn:schemas-microsoft-com:office:office \n?>">>)),
-    ?assertEqual(
-       [{pi, <<"xml:namespace">>,
-         [{<<"prefix">>,<<"o">>},
-          {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}],
-       tokens(<<"<?xml:namespace prefix=o ns=urn:schemas-microsoft-com:office:office">>)),
-    ?assertEqual(
-       [{data, <<"<">>, false}],
-       tokens(<<"&lt;">>)),
-    ?assertEqual(
-       [{data, <<"not html ">>, false},
-        {data, <<"< at all">>, false}],
-       tokens(<<"not html < at all">>)),
-    ok.
-
-parse_test() ->
-    D0 = <<"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">
-<html>
- <head>
-   <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">
-   <title>Foo</title>
-   <link rel=\"stylesheet\" type=\"text/css\" href=\"/static/rel/dojo/resources/dojo.css\" media=\"screen\">
-   <link rel=\"stylesheet\" type=\"text/css\" href=\"/static/foo.css\" media=\"screen\">
-   <!--[if lt IE 7]>
-   <style type=\"text/css\">
-     .no_ie { display: none; }
-   </style>
-   <![endif]-->
-   <link rel=\"icon\" href=\"/static/images/favicon.ico\" type=\"image/x-icon\">
-   <link rel=\"shortcut icon\" href=\"/static/images/favicon.ico\" type=\"image/x-icon\">
- </head>
- <body id=\"home\" class=\"tundra\"><![CDATA[&lt;<this<!-- is -->CDATA>&gt;]]></body>
-</html>">>,
-    ?assertEqual(
-       {<<"html">>, [],
-        [{<<"head">>, [],
-          [{<<"meta">>,
-            [{<<"http-equiv">>,<<"Content-Type">>},
-             {<<"content">>,<<"text/html; charset=UTF-8">>}],
-            []},
-           {<<"title">>,[],[<<"Foo">>]},
-           {<<"link">>,
-            [{<<"rel">>,<<"stylesheet">>},
-             {<<"type">>,<<"text/css">>},
-             {<<"href">>,<<"/static/rel/dojo/resources/dojo.css">>},
-             {<<"media">>,<<"screen">>}],
-            []},
-           {<<"link">>,
-            [{<<"rel">>,<<"stylesheet">>},
-             {<<"type">>,<<"text/css">>},
-             {<<"href">>,<<"/static/foo.css">>},
-             {<<"media">>,<<"screen">>}],
-            []},
-           {comment,<<"[if lt IE 7]>\n   <style type=\"text/css\">\n     .no_ie { display: none; }\n   </style>\n   <![endif]">>},
-           {<<"link">>,
-            [{<<"rel">>,<<"icon">>},
-             {<<"href">>,<<"/static/images/favicon.ico">>},
-             {<<"type">>,<<"image/x-icon">>}],
-            []},
-           {<<"link">>,
-            [{<<"rel">>,<<"shortcut icon">>},
-             {<<"href">>,<<"/static/images/favicon.ico">>},
-             {<<"type">>,<<"image/x-icon">>}],
-            []}]},
-         {<<"body">>,
-          [{<<"id">>,<<"home">>},
-           {<<"class">>,<<"tundra">>}],
-          [<<"&lt;<this<!-- is -->CDATA>&gt;">>]}]},
-       parse(D0)),
-    ?assertEqual(
-       {<<"html">>,[],
-        [{pi, <<"xml:namespace">>,
-          [{<<"prefix">>,<<"o">>},
-           {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}]},
-       parse(
-         <<"<html><?xml:namespace prefix=\"o\" ns=\"urn:schemas-microsoft-com:office:office\"?></html>">>)),
-    ?assertEqual(
-       {<<"html">>, [],
-        [{<<"dd">>, [], [<<"foo">>]},
-         {<<"dt">>, [], [<<"bar">>]}]},
-       parse(<<"<html><dd>foo<dt>bar</html>">>)),
-    %% Singleton sadness
-    ?assertEqual(
-       {<<"html">>, [],
-        [{<<"link">>, [], []},
-         <<"foo">>,
-         {<<"br">>, [], []},
-         <<"bar">>]},
-       parse(<<"<html><link>foo<br>bar</html>">>)),
-    ?assertEqual(
-       {<<"html">>, [],
-        [{<<"link">>, [], [<<"foo">>,
-                           {<<"br">>, [], []},
-                           <<"bar">>]}]},
-       parse(<<"<html><link>foo<br>bar</link></html>">>)),
-    %% Case insensitive tags
-    ?assertEqual(
-       {<<"html">>, [],
-        [{<<"head">>, [], [<<"foo">>,
-                           {<<"br">>, [], []},
-                           <<"BAR">>]},
-         {<<"body">>, [{<<"class">>, <<"">>}, {<<"bgcolor">>, <<"#Aa01fF">>}], []}
-        ]},
-       parse(<<"<html><Head>foo<bR>BAR</head><body Class=\"\" bgcolor=\"#Aa01fF\"></BODY></html>">>)),
-    ok.
-
-exhaustive_is_singleton_test() ->
-    T = mochiweb_cover:clause_lookup_table(?MODULE, is_singleton),
-    [?assertEqual(V, is_singleton(K)) || {K, V} <- T].
-
-tokenize_attributes_test() ->
-    ?assertEqual(
-       {<<"foo">>,
-        [{<<"bar">>, <<"b\"az">>},
-         {<<"wibble">>, <<"wibble">>},
-         {<<"taco", 16#c2, 16#a9>>, <<"bell">>},
-         {<<"quux">>, <<"quux">>}],
-        []},
-       parse(<<"<foo bar=\"b&quot;az\" wibble taco&copy;=bell quux">>)),
-    ok.
-
-tokens2_test() ->
-    D0 = <<"<channel><title>from __future__ import *</title><link>http://bob.pythonmac.org</link><description>Bob's Rants</description></channel>">>,
-    ?assertEqual(
-       [{start_tag,<<"channel">>,[],false},
-        {start_tag,<<"title">>,[],false},
-        {data,<<"from __future__ import *">>,false},
-        {end_tag,<<"title">>},
-        {start_tag,<<"link">>,[],true},
-        {data,<<"http://bob.pythonmac.org">>,false},
-        {end_tag,<<"link">>},
-        {start_tag,<<"description">>,[],false},
-        {data,<<"Bob's Rants">>,false},
-        {end_tag,<<"description">>},
-        {end_tag,<<"channel">>}],
-       tokens(D0)),
-    ok.
-
-to_tokens_test() ->
-    ?assertEqual(
-       [{start_tag, <<"p">>, [{class, 1}], false},
-        {end_tag, <<"p">>}],
-       to_tokens({p, [{class, 1}], []})),
-    ?assertEqual(
-       [{start_tag, <<"p">>, [], false},
-        {end_tag, <<"p">>}],
-       to_tokens({p})),
-    ?assertEqual(
-       [{'=', <<"data">>}],
-       to_tokens({'=', <<"data">>})),
-    ?assertEqual(
-       [{comment, <<"comment">>}],
-       to_tokens({comment, <<"comment">>})),
-    %% This is only allowed in sub-tags:
-    %% {p, [{"class", "foo"}]} as {p, [{"class", "foo"}], []}
-    %% On the outside it's always treated as follows:
-    %% {p, [], [{"class", "foo"}]} as {p, [], [{"class", "foo"}]}
-    ?assertEqual(
-       [{start_tag, <<"html">>, [], false},
-        {start_tag, <<"p">>, [{class, 1}], false},
-        {end_tag, <<"p">>},
-        {end_tag, <<"html">>}],
-       to_tokens({html, [{p, [{class, 1}]}]})),
-    ok.
-
-parse2_test() ->
-    D0 = <<"<channel><title>from __future__ import *</title><link>http://bob.pythonmac.org<br>foo</link><description>Bob's Rants</description></channel>">>,
-    ?assertEqual(
-       {<<"channel">>,[],
-        [{<<"title">>,[],[<<"from __future__ import *">>]},
-         {<<"link">>,[],[
-                         <<"http://bob.pythonmac.org">>,
-                         {<<"br">>,[],[]},
-                         <<"foo">>]},
-         {<<"description">>,[],[<<"Bob's Rants">>]}]},
-       parse(D0)),
-    ok.
-
-parse_tokens_test() ->
-    D0 = [{doctype,[<<"HTML">>,<<"PUBLIC">>,<<"-//W3C//DTD HTML 4.01 Transitional//EN">>]},
-          {data,<<"\n">>,true},
-          {start_tag,<<"html">>,[],false}],
-    ?assertEqual(
-       {<<"html">>, [], []},
-       parse_tokens(D0)),
-    D1 = D0 ++ [{end_tag, <<"html">>}],
-    ?assertEqual(
-       {<<"html">>, [], []},
-       parse_tokens(D1)),
-    D2 = D0 ++ [{start_tag, <<"body">>, [], false}],
-    ?assertEqual(
-       {<<"html">>, [], [{<<"body">>, [], []}]},
-       parse_tokens(D2)),
-    D3 = D0 ++ [{start_tag, <<"head">>, [], false},
-                {end_tag, <<"head">>},
-                {start_tag, <<"body">>, [], false}],
-    ?assertEqual(
-       {<<"html">>, [], [{<<"head">>, [], []}, {<<"body">>, [], []}]},
-       parse_tokens(D3)),
-    D4 = D3 ++ [{data,<<"\n">>,true},
-                {start_tag,<<"div">>,[{<<"class">>,<<"a">>}],false},
-                {start_tag,<<"a">>,[{<<"name">>,<<"#anchor">>}],false},
-                {end_tag,<<"a">>},
-                {end_tag,<<"div">>},
-                {start_tag,<<"div">>,[{<<"class">>,<<"b">>}],false},
-                {start_tag,<<"div">>,[{<<"class">>,<<"c">>}],false},
-                {end_tag,<<"div">>},
-                {end_tag,<<"div">>}],
-    ?assertEqual(
-       {<<"html">>, [],
-        [{<<"head">>, [], []},
-         {<<"body">>, [],
-          [{<<"div">>, [{<<"class">>, <<"a">>}], [{<<"a">>, [{<<"name">>, <<"#anchor">>}], []}]},
-           {<<"div">>, [{<<"class">>, <<"b">>}], [{<<"div">>, [{<<"class">>, <<"c">>}], []}]}
-          ]}]},
-       parse_tokens(D4)),
-    D5 = [{start_tag,<<"html">>,[],false},
-          {data,<<"\n">>,true},
-          {data,<<"boo">>,false},
-          {data,<<"hoo">>,false},
-          {data,<<"\n">>,true},
-          {end_tag,<<"html">>}],
-    ?assertEqual(
-       {<<"html">>, [], [<<"\nboohoo\n">>]},
-       parse_tokens(D5)),
-    D6 = [{start_tag,<<"html">>,[],false},
-          {data,<<"\n">>,true},
-          {data,<<"\n">>,true},
-          {end_tag,<<"html">>}],
-    ?assertEqual(
-       {<<"html">>, [], []},
-       parse_tokens(D6)),
-    D7 = [{start_tag,<<"html">>,[],false},
-          {start_tag,<<"ul">>,[],false},
-          {start_tag,<<"li">>,[],false},
-          {data,<<"word">>,false},
-          {start_tag,<<"li">>,[],false},
-          {data,<<"up">>,false},
-          {end_tag,<<"li">>},
-          {start_tag,<<"li">>,[],false},
-          {data,<<"fdsa">>,false},
-          {start_tag,<<"br">>,[],true},
-          {data,<<"asdf">>,false},
-          {end_tag,<<"ul">>},
-          {end_tag,<<"html">>}],
-    ?assertEqual(
-       {<<"html">>, [],
-        [{<<"ul">>, [],
-          [{<<"li">>, [], [<<"word">>]},
-           {<<"li">>, [], [<<"up">>]},
-           {<<"li">>, [], [<<"fdsa">>,{<<"br">>, [], []}, <<"asdf">>]}]}]},
-       parse_tokens(D7)),
-    ok.
-
-destack_test() ->
-    {<<"a">>, [], []} =
-        destack([{<<"a">>, [], []}]),
-    {<<"a">>, [], [{<<"b">>, [], []}]} =
-        destack([{<<"b">>, [], []}, {<<"a">>, [], []}]),
-    {<<"a">>, [], [{<<"b">>, [], [{<<"c">>, [], []}]}]} =
-     destack([{<<"c">>, [], []}, {<<"b">>, [], []}, {<<"a">>, [], []}]),
-    [{<<"a">>, [], [{<<"b">>, [], [{<<"c">>, [], []}]}]}] =
-     destack(<<"b">>,
-             [{<<"c">>, [], []}, {<<"b">>, [], []}, {<<"a">>, [], []}]),
-    [{<<"b">>, [], [{<<"c">>, [], []}]}, {<<"a">>, [], []}] =
-     destack(<<"c">>,
-             [{<<"c">>, [], []}, {<<"b">>, [], []},{<<"a">>, [], []}]),
-    ok.
-
-doctype_test() ->
-    ?assertEqual(
-       {<<"html">>,[],[{<<"head">>,[],[]}]},
-       mochiweb_html:parse("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"
-                           "<html><head></head></body></html>")),
-    %% http://code.google.com/p/mochiweb/issues/detail?id=52
-    ?assertEqual(
-       {<<"html">>,[],[{<<"head">>,[],[]}]},
-       mochiweb_html:parse("<html>"
-                           "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"
-                           "<head></head></body></html>")),
-    %% http://github.com/mochi/mochiweb/pull/13
-    ?assertEqual(
-       {<<"html">>,[],[{<<"head">>,[],[]}]},
-       mochiweb_html:parse("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"/>"
-                           "<html>"
-                           "<head></head></body></html>")),
-    ok.
-
-dumb_br_test() ->
-    %% http://code.google.com/p/mochiweb/issues/detail?id=71
-    ?assertEqual(
-       {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>]},
-       mochiweb_html:parse("<div><br/><br/>z</br/></br/></div>")),
-    ?assertEqual(
-       {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>]},
-       mochiweb_html:parse("<div><br><br>z</br/></br/></div>")),
-    ?assertEqual(
-       {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>, {<<"br">>, [], []}, {<<"br">>, [], []}]},
-       mochiweb_html:parse("<div><br><br>z<br/><br/></div>")),
-    ?assertEqual(
-       {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>]},
-       mochiweb_html:parse("<div><br><br>z</br></br></div>")).
-
-
-php_test() ->
-    %% http://code.google.com/p/mochiweb/issues/detail?id=71
-    ?assertEqual(
-       [{pi, <<"php\n">>}],
-       mochiweb_html:tokens(
-         "<?php\n?>")),
-    ?assertEqual(
-       {<<"div">>, [], [{pi, <<"php\n">>}]},
-       mochiweb_html:parse(
-         "<div><?php\n?></div>")),
-    ok.
-
-parse_unquoted_attr_test() ->
-    D0 = <<"<html><img src=/images/icon.png/></html>">>,
-    ?assertEqual(
-        {<<"html">>,[],[
-            { <<"img">>, [ { <<"src">>, <<"/images/icon.png">> } ], [] }
-        ]},
-        mochiweb_html:parse(D0)),
-
-    D1 = <<"<html><img src=/images/icon.png></img></html>">>,
-        ?assertEqual(
-            {<<"html">>,[],[
-                { <<"img">>, [ { <<"src">>, <<"/images/icon.png">> } ], [] }
-            ]},
-            mochiweb_html:parse(D1)),
-
-    D2 = <<"<html><img src=/images/icon&gt;.png width=100></img></html>">>,
-        ?assertEqual(
-            {<<"html">>,[],[
-                { <<"img">>, [ { <<"src">>, <<"/images/icon>.png">> }, { <<"width">>, <<"100">> } ], [] }
-            ]},
-            mochiweb_html:parse(D2)),
-    ok.
-
-parse_quoted_attr_test() ->
-    D0 = <<"<html><img src='/images/icon.png'></html>">>,
-    ?assertEqual(
-        {<<"html">>,[],[
-            { <<"img">>, [ { <<"src">>, <<"/images/icon.png">> } ], [] }
-        ]},
-        mochiweb_html:parse(D0)),
-
-    D1 = <<"<html><img src=\"/images/icon.png'></html>">>,
-    ?assertEqual(
-        {<<"html">>,[],[
-            { <<"img">>, [ { <<"src">>, <<"/images/icon.png'></html>">> } ], [] }
-        ]},
-        mochiweb_html:parse(D1)),
-
-    D2 = <<"<html><img src=\"/images/icon&gt;.png\"></html>">>,
-    ?assertEqual(
-        {<<"html">>,[],[
-            { <<"img">>, [ { <<"src">>, <<"/images/icon>.png">> } ], [] }
-        ]},
-        mochiweb_html:parse(D2)),
-
-    %% Quoted attributes can contain whitespace and newlines
-    D3 = <<"<html><a href=\"#\" onclick=\"javascript: test(1,\ntrue);\"></html>">>,
-    ?assertEqual(
-        {<<"html">>,[],[
-            { <<"a">>, [ { <<"href">>, <<"#">> }, {<<"onclick">>, <<"javascript: test(1,\ntrue);">>} ], [] }
-        ]},
-        mochiweb_html:parse(D3)),
-    ok.
-
-parse_missing_attr_name_test() ->
-    D0 = <<"<html =black></html>">>,
-    ?assertEqual(
-        {<<"html">>, [ { <<"=">>, <<"=">> }, { <<"black">>, <<"black">> } ], [] },
-       mochiweb_html:parse(D0)),
-    ok.
-
-parse_broken_pi_test() ->
-        D0 = <<"<html><?xml:namespace prefix = o ns = \"urn:schemas-microsoft-com:office:office\" /></html>">>,
-        ?assertEqual(
-                {<<"html">>, [], [
-                        { pi, <<"xml:namespace">>, [ { <<"prefix">>, <<"o">> },
-                                                     { <<"ns">>, <<"urn:schemas-microsoft-com:office:office">> } ] }
-                ] },
-                mochiweb_html:parse(D0)),
-        ok.
-
-parse_funny_singletons_test() ->
-        D0 = <<"<html><input><input>x</input></input></html>">>,
-        ?assertEqual(
-                {<<"html">>, [], [
-                        { <<"input">>, [], [] },
-                        { <<"input">>, [], [ <<"x">> ] }
-                ] },
-                mochiweb_html:parse(D0)),
-        ok.
-
-to_html_singleton_test() ->
-    D0 = <<"<link />">>,
-    T0 = {<<"link">>,[],[]},
-    ?assertEqual(D0, iolist_to_binary(to_html(T0))),
-
-    D1 = <<"<head><link /></head>">>,
-    T1 = {<<"head">>,[],[{<<"link">>,[],[]}]},
-    ?assertEqual(D1, iolist_to_binary(to_html(T1))),
-
-    D2 = <<"<head><link /><link /></head>">>,
-    T2 = {<<"head">>,[],[{<<"link">>,[],[]}, {<<"link">>,[],[]}]},
-    ?assertEqual(D2, iolist_to_binary(to_html(T2))),
-
-    %% Make sure singletons are converted to singletons.
-    D3 = <<"<head><link /></head>">>,
-    T3 = {<<"head">>,[],[{<<"link">>,[],[<<"funny">>]}]},
-    ?assertEqual(D3, iolist_to_binary(to_html(T3))),
-
-    D4 = <<"<link />">>,
-    T4 = {<<"link">>,[],[<<"funny">>]},
-    ?assertEqual(D4, iolist_to_binary(to_html(T4))),
-
-    ok.
-
-parse_amp_test_() ->
-    [?_assertEqual(
-       {<<"html">>,[],
-        [{<<"body">>,[{<<"onload">>,<<"javascript:A('1&2')">>}],[]}]},
-       mochiweb_html:parse("<html><body onload=\"javascript:A('1&2')\"></body></html>")),
-     ?_assertEqual(
-        {<<"html">>,[],
-         [{<<"body">>,[{<<"onload">>,<<"javascript:A('1& 2')">>}],[]}]},
-        mochiweb_html:parse("<html><body onload=\"javascript:A('1& 2')\"></body></html>")),
-     ?_assertEqual(
-        {<<"html">>,[],
-         [{<<"body">>,[],[<<"& ">>]}]},
-        mochiweb_html:parse("<html><body>& </body></html>")),
-     ?_assertEqual(
-        {<<"html">>,[],
-         [{<<"body">>,[],[<<"&">>]}]},
-        mochiweb_html:parse("<html><body>&</body></html>"))].
-
-parse_unescaped_lt_test() ->
-    D1 = <<"<div> < < <a href=\"/\">Back</a></div>">>,
-    ?assertEqual(
-        {<<"div">>, [], [<<" < < ">>, {<<"a">>, [{<<"href">>, <<"/">>}],
-                                       [<<"Back">>]}]},
-        mochiweb_html:parse(D1)),
-
-    D2 = <<"<div> << <a href=\"/\">Back</a></div>">>,
-    ?assertEqual(
-        {<<"div">>, [], [<<" << ">>, {<<"a">>, [{<<"href">>, <<"/">>}],
-                                      [<<"Back">>]}]},
-    mochiweb_html:parse(D2)).
-
--endif.

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/f540b156/test/mochiweb_html_tests.erl
----------------------------------------------------------------------
diff --git a/test/mochiweb_html_tests.erl b/test/mochiweb_html_tests.erl
new file mode 100644
index 0000000..cf97fc4
--- /dev/null
+++ b/test/mochiweb_html_tests.erl
@@ -0,0 +1,572 @@
+-module(mochiweb_html_tests).
+-include_lib("eunit/include/eunit.hrl").
+
+to_html_test() ->
+    ?assertEqual(
+       <<"<html><head><title>hey!</title></head><body><p class=\"foo\">what's up<br /></p><div>sucka</div>RAW!<!-- comment! --></body></html>">>,
+       iolist_to_binary(
+         mochiweb_html:to_html({html, [],
+                  [{<<"head">>, [],
+                    [{title, <<"hey!">>}]},
+                   {body, [],
+                    [{p, [{class, foo}], [<<"what's">>, <<" up">>, {br}]},
+                     {'div', <<"sucka">>},
+                     {'=', <<"RAW!">>},
+                     {comment, <<" comment! ">>}]}]}))),
+    ?assertEqual(
+       <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">">>,
+       iolist_to_binary(
+         mochiweb_html:to_html({doctype,
+                  [<<"html">>, <<"PUBLIC">>,
+                   <<"-//W3C//DTD XHTML 1.0 Transitional//EN">>,
+                   <<"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>]}))),
+    ?assertEqual(
+       <<"<html><?xml:namespace prefix=\"o\" ns=\"urn:schemas-microsoft-com:office:office\"?></html>">>,
+       iolist_to_binary(
+         mochiweb_html:to_html({<<"html">>,[],
+                  [{pi, <<"xml:namespace">>,
+                    [{<<"prefix">>,<<"o">>},
+                     {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}]}))),
+    ok.
+
+escape_test() ->
+    ?assertEqual(
+       <<"&amp;quot;\"word &gt;&lt;&lt;up!&amp;quot;">>,
+       mochiweb_html:escape(<<"&quot;\"word ><<up!&quot;">>)),
+    ?assertEqual(
+       <<"&amp;quot;\"word &gt;&lt;&lt;up!&amp;quot;">>,
+       mochiweb_html:escape("&quot;\"word ><<up!&quot;")),
+    ?assertEqual(
+       <<"&amp;quot;\"word &gt;&lt;&lt;up!&amp;quot;">>,
+       mochiweb_html:escape('&quot;\"word ><<up!&quot;')),
+    ok.
+
+escape_attr_test() ->
+    ?assertEqual(
+       <<"&amp;quot;&quot;word &gt;&lt;&lt;up!&amp;quot;">>,
+       mochiweb_html:escape_attr(<<"&quot;\"word ><<up!&quot;">>)),
+    ?assertEqual(
+       <<"&amp;quot;&quot;word &gt;&lt;&lt;up!&amp;quot;">>,
+       mochiweb_html:escape_attr("&quot;\"word ><<up!&quot;")),
+    ?assertEqual(
+       <<"&amp;quot;&quot;word &gt;&lt;&lt;up!&amp;quot;">>,
+       mochiweb_html:escape_attr('&quot;\"word ><<up!&quot;')),
+    ?assertEqual(
+       <<"12345">>,
+       mochiweb_html:escape_attr(12345)),
+    ?assertEqual(
+       <<"1.5">>,
+       mochiweb_html:escape_attr(1.5)),
+    ok.
+
+tokens_test() ->
+    ?assertEqual(
+       [{start_tag, <<"foo">>, [{<<"bar">>, <<"baz">>},
+                                {<<"wibble">>, <<"wibble">>},
+                                {<<"alice">>, <<"bob">>}], true}],
+       mochiweb_html:tokens(<<"<foo bar=baz wibble='wibble' alice=\"bob\"/>">>)),
+    ?assertEqual(
+       [{start_tag, <<"foo">>, [{<<"bar">>, <<"baz">>},
+                                {<<"wibble">>, <<"wibble">>},
+                                {<<"alice">>, <<"bob">>}], true}],
+       mochiweb_html:tokens(<<"<foo bar=baz wibble='wibble' alice=bob/>">>)),
+    ?assertEqual(
+       [{comment, <<"[if lt IE 7]>\n<style type=\"text/css\">\n.no_ie { display: none; }\n</style>\n<![endif]">>}],
+       mochiweb_html:tokens(<<"<!--[if lt IE 7]>\n<style type=\"text/css\">\n.no_ie { display: none; }\n</style>\n<![endif]-->">>)),
+    ?assertEqual(
+       [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+        {data, <<" A= B <= C ">>, false},
+        {end_tag, <<"script">>}],
+       mochiweb_html:tokens(<<"<script type=\"text/javascript\"> A= B <= C </script>">>)),
+    ?assertEqual(
+       [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+        {data, <<" A= B <= C ">>, false},
+        {end_tag, <<"script">>}],
+       mochiweb_html:tokens(<<"<script type =\"text/javascript\"> A= B <= C </script>">>)),
+    ?assertEqual(
+       [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+        {data, <<" A= B <= C ">>, false},
+        {end_tag, <<"script">>}],
+       mochiweb_html:tokens(<<"<script type = \"text/javascript\"> A= B <= C </script>">>)),
+    ?assertEqual(
+       [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+        {data, <<" A= B <= C ">>, false},
+        {end_tag, <<"script">>}],
+       mochiweb_html:tokens(<<"<script type= \"text/javascript\"> A= B <= C </script>">>)),
+    ?assertEqual(
+       [{start_tag, <<"textarea">>, [], false},
+        {data, <<"<html></body>">>, false},
+        {end_tag, <<"textarea">>}],
+       mochiweb_html:tokens(<<"<textarea><html></body></textarea>">>)),
+    ?assertEqual(
+       [{start_tag, <<"textarea">>, [], false},
+        {data, <<"<html></body></textareaz>">>, false}],
+       mochiweb_html:tokens(<<"<textarea ><html></body></textareaz>">>)),
+    ?assertEqual(
+       [{pi, <<"xml:namespace">>,
+         [{<<"prefix">>,<<"o">>},
+          {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}],
+       mochiweb_html:tokens(<<"<?xml:namespace prefix=\"o\" ns=\"urn:schemas-microsoft-com:office:office\"?>">>)),
+    ?assertEqual(
+       [{pi, <<"xml:namespace">>,
+         [{<<"prefix">>,<<"o">>},
+          {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}],
+       mochiweb_html:tokens(<<"<?xml:namespace prefix=o ns=urn:schemas-microsoft-com:office:office \n?>">>)),
+    ?assertEqual(
+       [{pi, <<"xml:namespace">>,
+         [{<<"prefix">>,<<"o">>},
+          {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}],
+       mochiweb_html:tokens(<<"<?xml:namespace prefix=o ns=urn:schemas-microsoft-com:office:office">>)),
+    ?assertEqual(
+       [{data, <<"<">>, false}],
+       mochiweb_html:tokens(<<"&lt;">>)),
+    ?assertEqual(
+       [{data, <<"not html ">>, false},
+        {data, <<"< at all">>, false}],
+       mochiweb_html:tokens(<<"not html < at all">>)),
+    ok.
+
+parse_test() ->
+    D0 = <<"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">
+<html>
+ <head>
+   <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">
+   <title>Foo</title>
+   <link rel=\"stylesheet\" type=\"text/css\" href=\"/static/rel/dojo/resources/dojo.css\" media=\"screen\">
+   <link rel=\"stylesheet\" type=\"text/css\" href=\"/static/foo.css\" media=\"screen\">
+   <!--[if lt IE 7]>
+   <style type=\"text/css\">
+     .no_ie { display: none; }
+   </style>
+   <![endif]-->
+   <link rel=\"icon\" href=\"/static/images/favicon.ico\" type=\"image/x-icon\">
+   <link rel=\"shortcut icon\" href=\"/static/images/favicon.ico\" type=\"image/x-icon\">
+ </head>
+ <body id=\"home\" class=\"tundra\"><![CDATA[&lt;<this<!-- is -->CDATA>&gt;]]></body>
+</html>">>,
+    ?assertEqual(
+       {<<"html">>, [],
+        [{<<"head">>, [],
+          [{<<"meta">>,
+            [{<<"http-equiv">>,<<"Content-Type">>},
+             {<<"content">>,<<"text/html; charset=UTF-8">>}],
+            []},
+           {<<"title">>,[],[<<"Foo">>]},
+           {<<"link">>,
+            [{<<"rel">>,<<"stylesheet">>},
+             {<<"type">>,<<"text/css">>},
+             {<<"href">>,<<"/static/rel/dojo/resources/dojo.css">>},
+             {<<"media">>,<<"screen">>}],
+            []},
+           {<<"link">>,
+            [{<<"rel">>,<<"stylesheet">>},
+             {<<"type">>,<<"text/css">>},
+             {<<"href">>,<<"/static/foo.css">>},
+             {<<"media">>,<<"screen">>}],
+            []},
+           {comment,<<"[if lt IE 7]>\n   <style type=\"text/css\">\n     .no_ie { display: none; }\n   </style>\n   <![endif]">>},
+           {<<"link">>,
+            [{<<"rel">>,<<"icon">>},
+             {<<"href">>,<<"/static/images/favicon.ico">>},
+             {<<"type">>,<<"image/x-icon">>}],
+            []},
+           {<<"link">>,
+            [{<<"rel">>,<<"shortcut icon">>},
+             {<<"href">>,<<"/static/images/favicon.ico">>},
+             {<<"type">>,<<"image/x-icon">>}],
+            []}]},
+         {<<"body">>,
+          [{<<"id">>,<<"home">>},
+           {<<"class">>,<<"tundra">>}],
+          [<<"&lt;<this<!-- is -->CDATA>&gt;">>]}]},
+       mochiweb_html:parse(D0)),
+    ?assertEqual(
+       {<<"html">>,[],
+        [{pi, <<"xml:namespace">>,
+          [{<<"prefix">>,<<"o">>},
+           {<<"ns">>,<<"urn:schemas-microsoft-com:office:office">>}]}]},
+       mochiweb_html:parse(
+         <<"<html><?xml:namespace prefix=\"o\" ns=\"urn:schemas-microsoft-com:office:office\"?></html>">>)),
+    ?assertEqual(
+       {<<"html">>, [],
+        [{<<"dd">>, [], [<<"foo">>]},
+         {<<"dt">>, [], [<<"bar">>]}]},
+       mochiweb_html:parse(<<"<html><dd>foo<dt>bar</html>">>)),
+    %% Singleton sadness
+    ?assertEqual(
+       {<<"html">>, [],
+        [{<<"link">>, [], []},
+         <<"foo">>,
+         {<<"br">>, [], []},
+         <<"bar">>]},
+       mochiweb_html:parse(<<"<html><link>foo<br>bar</html>">>)),
+    ?assertEqual(
+       {<<"html">>, [],
+        [{<<"link">>, [], [<<"foo">>,
+                           {<<"br">>, [], []},
+                           <<"bar">>]}]},
+       mochiweb_html:parse(<<"<html><link>foo<br>bar</link></html>">>)),
+    %% Case insensitive tags
+    ?assertEqual(
+       {<<"html">>, [],
+        [{<<"head">>, [], [<<"foo">>,
+                           {<<"br">>, [], []},
+                           <<"BAR">>]},
+         {<<"body">>, [{<<"class">>, <<"">>}, {<<"bgcolor">>, <<"#Aa01fF">>}], []}
+        ]},
+       mochiweb_html:parse(<<"<html><Head>foo<bR>BAR</head><body Class=\"\" bgcolor=\"#Aa01fF\"></BODY></html>">>)),
+    ok.
+
+exhaustive_is_singleton_test() ->
+    T = mochiweb_cover:clause_lookup_table(mochiweb_html, is_singleton),
+    [?assertEqual(V, mochiweb_html:is_singleton(K)) || {K, V} <- T].
+
+tokenize_attributes_test() ->
+    ?assertEqual(
+       {<<"foo">>,
+        [{<<"bar">>, <<"b\"az">>},
+         {<<"wibble">>, <<"wibble">>},
+         {<<"taco", 16#c2, 16#a9>>, <<"bell">>},
+         {<<"quux">>, <<"quux">>}],
+        []},
+       mochiweb_html:parse(<<"<foo bar=\"b&quot;az\" wibble taco&copy;=bell quux">>)),
+    ok.
+
+tokens2_test() ->
+    D0 = <<"<channel><title>from __future__ import *</title><link>http://bob.pythonmac.org</link><description>Bob's Rants</description></channel>">>,
+    ?assertEqual(
+       [{start_tag,<<"channel">>,[],false},
+        {start_tag,<<"title">>,[],false},
+        {data,<<"from __future__ import *">>,false},
+        {end_tag,<<"title">>},
+        {start_tag,<<"link">>,[],true},
+        {data,<<"http://bob.pythonmac.org">>,false},
+        {end_tag,<<"link">>},
+        {start_tag,<<"description">>,[],false},
+        {data,<<"Bob's Rants">>,false},
+        {end_tag,<<"description">>},
+        {end_tag,<<"channel">>}],
+       mochiweb_html:tokens(D0)),
+    ok.
+
+to_tokens_test() ->
+    ?assertEqual(
+       [{start_tag, <<"p">>, [{class, 1}], false},
+        {end_tag, <<"p">>}],
+       mochiweb_html:to_tokens({p, [{class, 1}], []})),
+    ?assertEqual(
+       [{start_tag, <<"p">>, [], false},
+        {end_tag, <<"p">>}],
+       mochiweb_html:to_tokens({p})),
+    ?assertEqual(
+       [{'=', <<"data">>}],
+       mochiweb_html:to_tokens({'=', <<"data">>})),
+    ?assertEqual(
+       [{comment, <<"comment">>}],
+       mochiweb_html:to_tokens({comment, <<"comment">>})),
+    %% This is only allowed in sub-tags:
+    %% {p, [{"class", "foo"}]} as {p, [{"class", "foo"}], []}
+    %% On the outside it's always treated as follows:
+    %% {p, [], [{"class", "foo"}]} as {p, [], [{"class", "foo"}]}
+    ?assertEqual(
+       [{start_tag, <<"html">>, [], false},
+        {start_tag, <<"p">>, [{class, 1}], false},
+        {end_tag, <<"p">>},
+        {end_tag, <<"html">>}],
+       mochiweb_html:to_tokens({html, [{p, [{class, 1}]}]})),
+    ok.
+
+parse2_test() ->
+    D0 = <<"<channel><title>from __future__ import *</title><link>http://bob.pythonmac.org<br>foo</link><description>Bob's Rants</description></channel>">>,
+    ?assertEqual(
+       {<<"channel">>,[],
+        [{<<"title">>,[],[<<"from __future__ import *">>]},
+         {<<"link">>,[],[
+                         <<"http://bob.pythonmac.org">>,
+                         {<<"br">>,[],[]},
+                         <<"foo">>]},
+         {<<"description">>,[],[<<"Bob's Rants">>]}]},
+       mochiweb_html:parse(D0)),
+    ok.
+
+parse_tokens_test() ->
+    D0 = [{doctype,[<<"HTML">>,<<"PUBLIC">>,<<"-//W3C//DTD HTML 4.01 Transitional//EN">>]},
+          {data,<<"\n">>,true},
+          {start_tag,<<"html">>,[],false}],
+    ?assertEqual(
+       {<<"html">>, [], []},
+       mochiweb_html:parse_tokens(D0)),
+    D1 = D0 ++ [{end_tag, <<"html">>}],
+    ?assertEqual(
+       {<<"html">>, [], []},
+       mochiweb_html:parse_tokens(D1)),
+    D2 = D0 ++ [{start_tag, <<"body">>, [], false}],
+    ?assertEqual(
+       {<<"html">>, [], [{<<"body">>, [], []}]},
+       mochiweb_html:parse_tokens(D2)),
+    D3 = D0 ++ [{start_tag, <<"head">>, [], false},
+                {end_tag, <<"head">>},
+                {start_tag, <<"body">>, [], false}],
+    ?assertEqual(
+       {<<"html">>, [], [{<<"head">>, [], []}, {<<"body">>, [], []}]},
+       mochiweb_html:parse_tokens(D3)),
+    D4 = D3 ++ [{data,<<"\n">>,true},
+                {start_tag,<<"div">>,[{<<"class">>,<<"a">>}],false},
+                {start_tag,<<"a">>,[{<<"name">>,<<"#anchor">>}],false},
+                {end_tag,<<"a">>},
+                {end_tag,<<"div">>},
+                {start_tag,<<"div">>,[{<<"class">>,<<"b">>}],false},
+                {start_tag,<<"div">>,[{<<"class">>,<<"c">>}],false},
+                {end_tag,<<"div">>},
+                {end_tag,<<"div">>}],
+    ?assertEqual(
+       {<<"html">>, [],
+        [{<<"head">>, [], []},
+         {<<"body">>, [],
+          [{<<"div">>, [{<<"class">>, <<"a">>}], [{<<"a">>, [{<<"name">>, <<"#anchor">>}], []}]},
+           {<<"div">>, [{<<"class">>, <<"b">>}], [{<<"div">>, [{<<"class">>, <<"c">>}], []}]}
+          ]}]},
+       mochiweb_html:parse_tokens(D4)),
+    D5 = [{start_tag,<<"html">>,[],false},
+          {data,<<"\n">>,true},
+          {data,<<"boo">>,false},
+          {data,<<"hoo">>,false},
+          {data,<<"\n">>,true},
+          {end_tag,<<"html">>}],
+    ?assertEqual(
+       {<<"html">>, [], [<<"\nboohoo\n">>]},
+       mochiweb_html:parse_tokens(D5)),
+    D6 = [{start_tag,<<"html">>,[],false},
+          {data,<<"\n">>,true},
+          {data,<<"\n">>,true},
+          {end_tag,<<"html">>}],
+    ?assertEqual(
+       {<<"html">>, [], []},
+       mochiweb_html:parse_tokens(D6)),
+    D7 = [{start_tag,<<"html">>,[],false},
+          {start_tag,<<"ul">>,[],false},
+          {start_tag,<<"li">>,[],false},
+          {data,<<"word">>,false},
+          {start_tag,<<"li">>,[],false},
+          {data,<<"up">>,false},
+          {end_tag,<<"li">>},
+          {start_tag,<<"li">>,[],false},
+          {data,<<"fdsa">>,false},
+          {start_tag,<<"br">>,[],true},
+          {data,<<"asdf">>,false},
+          {end_tag,<<"ul">>},
+          {end_tag,<<"html">>}],
+    ?assertEqual(
+       {<<"html">>, [],
+        [{<<"ul">>, [],
+          [{<<"li">>, [], [<<"word">>]},
+           {<<"li">>, [], [<<"up">>]},
+           {<<"li">>, [], [<<"fdsa">>,{<<"br">>, [], []}, <<"asdf">>]}]}]},
+       mochiweb_html:parse_tokens(D7)),
+    ok.
+
+destack_test() ->
+    ?assertEqual(
+       {<<"a">>, [], []},
+       mochiweb_html:destack([{<<"a">>, [], []}])),
+    ?assertEqual(
+       {<<"a">>, [], [{<<"b">>, [], []}]},
+       mochiweb_html:destack([{<<"b">>, [], []}, {<<"a">>, [], []}])),
+    ?assertEqual(
+       {<<"a">>, [], [{<<"b">>, [], [{<<"c">>, [], []}]}]},
+       mochiweb_html:destack(
+         [{<<"c">>, [], []}, {<<"b">>, [], []}, {<<"a">>, [], []}])),
+    ?assertEqual(
+       [{<<"a">>, [], [{<<"b">>, [], [{<<"c">>, [], []}]}]}],
+       mochiweb_html:destack(
+         <<"b">>,
+         [{<<"c">>, [], []}, {<<"b">>, [], []}, {<<"a">>, [], []}])),
+    ?assertEqual(
+       [{<<"b">>, [], [{<<"c">>, [], []}]}, {<<"a">>, [], []}],
+       mochiweb_html:destack(
+         <<"c">>,
+         [{<<"c">>, [], []}, {<<"b">>, [], []},{<<"a">>, [], []}])),
+    ok.
+
+doctype_test() ->
+    ?assertEqual(
+       {<<"html">>,[],[{<<"head">>,[],[]}]},
+       mochiweb_html:parse("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"
+                           "<html><head></head></body></html>")),
+    %% http://code.google.com/p/mochiweb/issues/detail?id=52
+    ?assertEqual(
+       {<<"html">>,[],[{<<"head">>,[],[]}]},
+       mochiweb_html:parse("<html>"
+                           "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"
+                           "<head></head></body></html>")),
+    %% http://github.com/mochi/mochiweb/pull/13
+    ?assertEqual(
+       {<<"html">>,[],[{<<"head">>,[],[]}]},
+       mochiweb_html:parse("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"/>"
+                           "<html>"
+                           "<head></head></body></html>")),
+    ok.
+
+dumb_br_test() ->
+    %% http://code.google.com/p/mochiweb/issues/detail?id=71
+    ?assertEqual(
+       {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>]},
+       mochiweb_html:parse("<div><br/><br/>z</br/></br/></div>")),
+    ?assertEqual(
+       {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>]},
+       mochiweb_html:parse("<div><br><br>z</br/></br/></div>")),
+    ?assertEqual(
+       {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>, {<<"br">>, [], []}, {<<"br">>, [], []}]},
+       mochiweb_html:parse("<div><br><br>z<br/><br/></div>")),
+    ?assertEqual(
+       {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>]},
+       mochiweb_html:parse("<div><br><br>z</br></br></div>")).
+
+
+php_test() ->
+    %% http://code.google.com/p/mochiweb/issues/detail?id=71
+    ?assertEqual(
+       [{pi, <<"php\n">>}],
+       mochiweb_html:tokens(
+         "<?php\n?>")),
+    ?assertEqual(
+       {<<"div">>, [], [{pi, <<"php\n">>}]},
+       mochiweb_html:parse(
+         "<div><?php\n?></div>")),
+    ok.
+
+parse_unquoted_attr_test() ->
+    D0 = <<"<html><img src=/images/icon.png/></html>">>,
+    ?assertEqual(
+        {<<"html">>,[],[
+            { <<"img">>, [ { <<"src">>, <<"/images/icon.png">> } ], [] }
+        ]},
+        mochiweb_html:parse(D0)),
+
+    D1 = <<"<html><img src=/images/icon.png></img></html>">>,
+        ?assertEqual(
+            {<<"html">>,[],[
+                { <<"img">>, [ { <<"src">>, <<"/images/icon.png">> } ], [] }
+            ]},
+            mochiweb_html:parse(D1)),
+
+    D2 = <<"<html><img src=/images/icon&gt;.png width=100></img></html>">>,
+        ?assertEqual(
+            {<<"html">>,[],[
+                { <<"img">>, [ { <<"src">>, <<"/images/icon>.png">> }, { <<"width">>, <<"100">> } ], [] }
+            ]},
+            mochiweb_html:parse(D2)),
+    ok.
+
+parse_quoted_attr_test() ->
+    D0 = <<"<html><img src='/images/icon.png'></html>">>,
+    ?assertEqual(
+        {<<"html">>,[],[
+            { <<"img">>, [ { <<"src">>, <<"/images/icon.png">> } ], [] }
+        ]},
+        mochiweb_html:parse(D0)),
+
+    D1 = <<"<html><img src=\"/images/icon.png'></html>">>,
+    ?assertEqual(
+        {<<"html">>,[],[
+            { <<"img">>, [ { <<"src">>, <<"/images/icon.png'></html>">> } ], [] }
+        ]},
+        mochiweb_html:parse(D1)),
+
+    D2 = <<"<html><img src=\"/images/icon&gt;.png\"></html>">>,
+    ?assertEqual(
+        {<<"html">>,[],[
+            { <<"img">>, [ { <<"src">>, <<"/images/icon>.png">> } ], [] }
+        ]},
+        mochiweb_html:parse(D2)),
+
+    %% Quoted attributes can contain whitespace and newlines
+    D3 = <<"<html><a href=\"#\" onclick=\"javascript: test(1,\ntrue);\"></html>">>,
+    ?assertEqual(
+        {<<"html">>,[],[
+            { <<"a">>, [ { <<"href">>, <<"#">> }, {<<"onclick">>, <<"javascript: test(1,\ntrue);">>} ], [] }
+        ]},
+        mochiweb_html:parse(D3)),
+    ok.
+
+parse_missing_attr_name_test() ->
+    D0 = <<"<html =black></html>">>,
+    ?assertEqual(
+        {<<"html">>, [ { <<"=">>, <<"=">> }, { <<"black">>, <<"black">> } ], [] },
+       mochiweb_html:parse(D0)),
+    ok.
+
+parse_broken_pi_test() ->
+        D0 = <<"<html><?xml:namespace prefix = o ns = \"urn:schemas-microsoft-com:office:office\" /></html>">>,
+        ?assertEqual(
+                {<<"html">>, [], [
+                        { pi, <<"xml:namespace">>, [ { <<"prefix">>, <<"o">> },
+                                                     { <<"ns">>, <<"urn:schemas-microsoft-com:office:office">> } ] }
+                ] },
+                mochiweb_html:parse(D0)),
+        ok.
+
+parse_funny_singletons_test() ->
+        D0 = <<"<html><input><input>x</input></input></html>">>,
+        ?assertEqual(
+                {<<"html">>, [], [
+                        { <<"input">>, [], [] },
+                        { <<"input">>, [], [ <<"x">> ] }
+                ] },
+                mochiweb_html:parse(D0)),
+        ok.
+
+to_html_singleton_test() ->
+    D0 = <<"<link />">>,
+    T0 = {<<"link">>,[],[]},
+    ?assertEqual(D0, iolist_to_binary(mochiweb_html:to_html(T0))),
+
+    D1 = <<"<head><link /></head>">>,
+    T1 = {<<"head">>,[],[{<<"link">>,[],[]}]},
+    ?assertEqual(D1, iolist_to_binary(mochiweb_html:to_html(T1))),
+
+    D2 = <<"<head><link /><link /></head>">>,
+    T2 = {<<"head">>,[],[{<<"link">>,[],[]}, {<<"link">>,[],[]}]},
+    ?assertEqual(D2, iolist_to_binary(mochiweb_html:to_html(T2))),
+
+    %% Make sure singletons are converted to singletons.
+    D3 = <<"<head><link /></head>">>,
+    T3 = {<<"head">>,[],[{<<"link">>,[],[<<"funny">>]}]},
+    ?assertEqual(D3, iolist_to_binary(mochiweb_html:to_html(T3))),
+
+    D4 = <<"<link />">>,
+    T4 = {<<"link">>,[],[<<"funny">>]},
+    ?assertEqual(D4, iolist_to_binary(mochiweb_html:to_html(T4))),
+
+    ok.
+
+parse_amp_test_() ->
+    [?_assertEqual(
+       {<<"html">>,[],
+        [{<<"body">>,[{<<"onload">>,<<"javascript:A('1&2')">>}],[]}]},
+       mochiweb_html:parse("<html><body onload=\"javascript:A('1&2')\"></body></html>")),
+     ?_assertEqual(
+        {<<"html">>,[],
+         [{<<"body">>,[{<<"onload">>,<<"javascript:A('1& 2')">>}],[]}]},
+        mochiweb_html:parse("<html><body onload=\"javascript:A('1& 2')\"></body></html>")),
+     ?_assertEqual(
+        {<<"html">>,[],
+         [{<<"body">>,[],[<<"& ">>]}]},
+        mochiweb_html:parse("<html><body>& </body></html>")),
+     ?_assertEqual(
+        {<<"html">>,[],
+         [{<<"body">>,[],[<<"&">>]}]},
+        mochiweb_html:parse("<html><body>&</body></html>"))].
+
+parse_unescaped_lt_test() ->
+    D1 = <<"<div> < < <a href=\"/\">Back</a></div>">>,
+    ?assertEqual(
+        {<<"div">>, [], [<<" < < ">>, {<<"a">>, [{<<"href">>, <<"/">>}],
+                                       [<<"Back">>]}]},
+        mochiweb_html:parse(D1)),
+
+    D2 = <<"<div> << <a href=\"/\">Back</a></div>">>,
+    ?assertEqual(
+        {<<"div">>, [], [<<" << ">>, {<<"a">>, [{<<"href">>, <<"/">>}],
+                                      [<<"Back">>]}]},
+    mochiweb_html:parse(D2)).