anchor.diff:      {Tag, Rest1, S1} = tokenize_literal(Rest, ?INC_COL(S), []),
       {Attrs, Rest2, S2} = tokenize_attributes(Rest1, S1, []),
       {Rest3, S3, HasSlash} = find_gt(Rest2, S2, false),
  -    Singleton = HasSlash orelse is_singleton(string:to_lower(Tag)),
  +    Singleton = HasSlash orelse is_singleton(norm(binary_to_list(Tag))),
       {{start_tag, Tag, Attrs, Singleton}, Rest3, S3};
   tokenize(Rest, S) ->
       tokenize_data(Rest, S, [], true).
   
   test_parse() ->
  -    D0 = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">
  +    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\">
  
positive.code:     tokenize_literal(Rest, S, []).
      
  tokenize_word([], S, _Quote, Acc) ->
      {lists:reverse(Acc), [], S};
  tokenize_word([Quote | Rest], S, Quote, Acc) ->
      {lists:reverse(Acc), Rest, ?INC_COL(S)};
  tokenize_word([$& | Rest], S, Quote, Acc) ->
      {{data, Data, false}, S1, Rest1} = tokenize_charref(Rest, ?INC_COL(S), []),
      tokenize_word(Rest1, S1, Quote, lists:reverse(Data, Acc));
  tokenize_word([C | Rest], S, Quote, Acc) ->
      tokenize_word(Rest, ?INC_CHAR(S, C), Quote, [C | Acc]).
  
  tokenize_literal([], S, Acc) ->
      {lists:reverse(Acc), [], S};
  tokenize_literal(Rest=">" ++ _, S, Acc) ->
      {lists:reverse(Acc), Rest, S};
  tokenize_literal([$& | Rest], S, Acc) ->
      {{data, Data, false}, S1, Rest1} = tokenize_charref(Rest, ?INC_COL(S), []),
      tokenize_literal(Rest1, S1, lists:reverse(Data, Acc));
  tokenize_literal(Rest=[C | _], S, Acc) when ?IS_WHITESPACE(C)
                                              orelse C =:= $/
                                              orelse C =:= $= ->
      {lists:reverse(Acc), Rest, S};
  tokenize_literal([C | Rest], S, Acc) ->
      tokenize_literal(Rest, ?INC_COL(S), [C | Acc]).
  
  tokenize_cdata([], S, Acc) ->
      {{data, lists:reverse(Acc), false}, [], S};
  tokenize_cdata("]]>" ++ Rest, S, Acc) ->
      {{data, lists:reverse(Acc), false}, Rest, ?ADV_COL(S, 3)};
  tokenize_cdata([C | Rest], S, Acc) ->
      tokenize_cdata(Rest, ?INC_CHAR(S, C), [C | Acc]).
  
  tokenize_comment([], S, Acc) ->
      {{comment, lists:reverse(Acc)}, [], S};
  tokenize_comment("-->" ++ Rest, S, Acc) ->
      {{comment, lists:reverse(Acc)}, Rest, ?ADV_COL(S, 3)};
  tokenize_comment([C | Rest], S, Acc) ->
      tokenize_comment(Rest, ?INC_CHAR(S, C), [C | Acc]).
  
negative.code:     Expect = <<"<html><head><title>hey!</title></head><body><p class=\"foo\">what's up<br /></p><div>sucka</div><!-- comment! --></body></html>">>,
      Expect = iolist_to_binary(
                 to_html({html, [],
                          [{<<"head">>, [],
                            [{title, <<"hey!">>}]},
                           {body, [],
                            [{p, [{class, foo}], [<<"what's">>, <<" up">>, {br}]},
                             {'div', <<"sucka">>},
                             {comment, <<" comment! ">>}]}]})),
      Expect1 = <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">">>,
      Expect1 = 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">>]})),
      ok.
  to_html([], Acc) ->
      lists:reverse(Acc);
  to_html([{'=', Content} | Rest], Acc) ->
      to_html(Rest, [Content | Acc]);
  to_html([{pi, Tag, Attrs} | Rest], Acc) ->
      Open = [<<"<?">>,
              Tag,
              attrs_to_html(Attrs, []),
              <<"?>">>],
      to_html(Rest, [Open | Acc]);
  to_html([{comment, Comment} | Rest], Acc) ->
      to_html(Rest, [[<<"<!--">>, Comment, <<"-->">>] | Acc]);
  to_html([{doctype, Parts} | Rest], Acc) ->
      Inside = doctype_to_html(Parts, Acc),
      to_html(Rest, [[<<"<!DOCTYPE">>, Inside, <<">">>] | Acc]);
  to_html([{data, Data, _Whitespace} | Rest], Acc) ->
      to_html(Rest, [escape(Data) | Acc]);
  to_html([{start_tag, Tag, Attrs, Singleton} | Rest], Acc) ->
      Open = [<<"<">>,
              Tag,
              attrs_to_html(Attrs, []),
              case Singleton of
                  true -> <<" />">>;
                  false -> <<">">>
              end],
      to_html(Rest, [Open | Acc]);
  to_html([{end_tag, Tag} | Rest], Acc) ->
      to_html(Rest, [[<<"</">>, Tag, <<">">>] | Acc]).
  
  doctype_to_html([], Acc) ->
      lists:reverse(Acc);
  doctype_to_html([Word | Rest], Acc) ->
      case lists:all(fun (C) -> ?IS_LITERAL_SAFE(C) end,
                     binary_to_list(iolist_to_binary(Word))) of
          true ->
              doctype_to_html(Rest, [[<<" ">>, Word] | Acc]);
          false ->
              doctype_to_html(Rest, [[<<" \"">>, escape_attr(Word), ?QUOTE] | Acc])
      end.
  
  attrs_to_html([], Acc) ->
      lists:reverse(Acc);
  attrs_to_html([{K, V} | Rest], Acc) ->
      attrs_to_html(Rest,
                    [[<<" ">>, escape(K), <<"=\"">>,
                      escape_attr(V), <<"\"">>] | Acc]).
      
  test_escape() ->
      <<"&amp;quot;\"word &lt;&lt;up!&amp;quot;">> =
          escape(<<"&quot;\"word <<up!&quot;">>),
      ok.
  
  test_escape_attr() ->
      <<"&amp;quot;&quot;word &lt;&lt;up!&amp;quot;">> =
          escape_attr(<<"&quot;\"word <<up!&quot;">>),
      ok.
  
  escape([], Acc) ->
      list_to_binary(lists:reverse(Acc));
  escape("<" ++ Rest, Acc) ->
      escape(Rest, lists:reverse("&lt;", Acc));
  escape(">" ++ Rest, Acc) ->
  