lex_test.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. package html // import "github.com/tdewolff/parse/html"
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "testing"
  7. "github.com/tdewolff/parse"
  8. "github.com/tdewolff/test"
  9. )
  10. type TTs []TokenType
  11. func TestTokens(t *testing.T) {
  12. var tokenTests = []struct {
  13. html string
  14. expected []TokenType
  15. }{
  16. {"<html></html>", TTs{StartTagToken, StartTagCloseToken, EndTagToken}},
  17. {"<img/>", TTs{StartTagToken, StartTagVoidToken}},
  18. {"<!-- comment -->", TTs{CommentToken}},
  19. {"<!-- comment --!>", TTs{CommentToken}},
  20. {"<p>text</p>", TTs{StartTagToken, StartTagCloseToken, TextToken, EndTagToken}},
  21. {"<input type='button'/>", TTs{StartTagToken, AttributeToken, StartTagVoidToken}},
  22. {"<input type='button' value=''/>", TTs{StartTagToken, AttributeToken, AttributeToken, StartTagVoidToken}},
  23. {"<input type='=/>' \r\n\t\f value=\"'\" name=x checked />", TTs{StartTagToken, AttributeToken, AttributeToken, AttributeToken, AttributeToken, StartTagVoidToken}},
  24. {"<!doctype>", TTs{DoctypeToken}},
  25. {"<!doctype html>", TTs{DoctypeToken}},
  26. {"<?bogus>", TTs{CommentToken}},
  27. {"</0bogus>", TTs{CommentToken}},
  28. {"<!bogus>", TTs{CommentToken}},
  29. {"< ", TTs{TextToken}},
  30. {"</", TTs{TextToken}},
  31. // raw tags
  32. {"<title><p></p></title>", TTs{StartTagToken, StartTagCloseToken, TextToken, EndTagToken}},
  33. {"<TITLE><p></p></TITLE>", TTs{StartTagToken, StartTagCloseToken, TextToken, EndTagToken}},
  34. {"<plaintext></plaintext>", TTs{StartTagToken, StartTagCloseToken, TextToken}},
  35. {"<script></script>", TTs{StartTagToken, StartTagCloseToken, EndTagToken}},
  36. {"<script>var x='</script>';</script>", TTs{StartTagToken, StartTagCloseToken, TextToken, EndTagToken, TextToken, EndTagToken}},
  37. {"<script><!--var x='</script>';--></script>", TTs{StartTagToken, StartTagCloseToken, TextToken, EndTagToken, TextToken, EndTagToken}},
  38. {"<script><!--var x='<script></script>';--></script>", TTs{StartTagToken, StartTagCloseToken, TextToken, EndTagToken}},
  39. {"<script><!--var x='<script>';--></script>", TTs{StartTagToken, StartTagCloseToken, TextToken, EndTagToken}},
  40. {"<![CDATA[ test ]]>", TTs{TextToken}},
  41. {"<svg>text</svg>", TTs{SvgToken}},
  42. {"<math>text</math>", TTs{MathToken}},
  43. {`<svg>text<x a="</svg>"></x></svg>`, TTs{SvgToken}},
  44. {"<a><svg>text</svg></a>", TTs{StartTagToken, StartTagCloseToken, SvgToken, EndTagToken}},
  45. // early endings
  46. {"<!-- comment", TTs{CommentToken}},
  47. {"<? bogus comment", TTs{CommentToken}},
  48. {"<foo", TTs{StartTagToken}},
  49. {"</foo", TTs{EndTagToken}},
  50. {"<foo x", TTs{StartTagToken, AttributeToken}},
  51. {"<foo x=", TTs{StartTagToken, AttributeToken}},
  52. {"<foo x='", TTs{StartTagToken, AttributeToken}},
  53. {"<foo x=''", TTs{StartTagToken, AttributeToken}},
  54. {"<!DOCTYPE note SYSTEM", TTs{DoctypeToken}},
  55. {"<![CDATA[ test", TTs{TextToken}},
  56. {"<script>", TTs{StartTagToken, StartTagCloseToken}},
  57. {"<script><!--", TTs{StartTagToken, StartTagCloseToken, TextToken}},
  58. {"<script><!--var x='<script></script>';-->", TTs{StartTagToken, StartTagCloseToken, TextToken}},
  59. // go-fuzz
  60. {"</>", TTs{EndTagToken}},
  61. }
  62. for _, tt := range tokenTests {
  63. t.Run(tt.html, func(t *testing.T) {
  64. l := NewLexer(bytes.NewBufferString(tt.html))
  65. i := 0
  66. for {
  67. token, _ := l.Next()
  68. if token == ErrorToken {
  69. test.T(t, l.Err(), io.EOF)
  70. test.T(t, i, len(tt.expected), "when error occurred we must be at the end")
  71. break
  72. }
  73. test.That(t, i < len(tt.expected), "index", i, "must not exceed expected token types size", len(tt.expected))
  74. if i < len(tt.expected) {
  75. test.T(t, token, tt.expected[i], "token types must match")
  76. }
  77. i++
  78. }
  79. })
  80. }
  81. test.T(t, TokenType(100).String(), "Invalid(100)")
  82. }
  83. func TestTags(t *testing.T) {
  84. var tagTests = []struct {
  85. html string
  86. expected string
  87. }{
  88. {"<foo:bar.qux-norf/>", "foo:bar.qux-norf"},
  89. {"<foo?bar/qux>", "foo?bar/qux"},
  90. {"<!DOCTYPE note SYSTEM \"Note.dtd\">", " note SYSTEM \"Note.dtd\""},
  91. {"</foo >", "foo"},
  92. // early endings
  93. {"<foo ", "foo"},
  94. }
  95. for _, tt := range tagTests {
  96. t.Run(tt.html, func(t *testing.T) {
  97. l := NewLexer(bytes.NewBufferString(tt.html))
  98. for {
  99. token, _ := l.Next()
  100. if token == ErrorToken {
  101. test.T(t, l.Err(), io.EOF)
  102. test.Fail(t, "when error occurred we must be at the end")
  103. break
  104. } else if token == StartTagToken || token == EndTagToken || token == DoctypeToken {
  105. test.String(t, string(l.Text()), tt.expected)
  106. break
  107. }
  108. }
  109. })
  110. }
  111. }
  112. func TestAttributes(t *testing.T) {
  113. var attributeTests = []struct {
  114. attr string
  115. expected []string
  116. }{
  117. {"<foo a=\"b\" />", []string{"a", "\"b\""}},
  118. {"<foo \nchecked \r\n value\r=\t'=/>\"' />", []string{"checked", "", "value", "'=/>\"'"}},
  119. {"<foo bar=\" a \n\t\r b \" />", []string{"bar", "\" a \n\t\r b \""}},
  120. {"<foo a/>", []string{"a", ""}},
  121. {"<foo /=/>", []string{"/", "/"}},
  122. // early endings
  123. {"<foo x", []string{"x", ""}},
  124. {"<foo x=", []string{"x", ""}},
  125. {"<foo x='", []string{"x", "'"}},
  126. }
  127. for _, tt := range attributeTests {
  128. t.Run(tt.attr, func(t *testing.T) {
  129. l := NewLexer(bytes.NewBufferString(tt.attr))
  130. i := 0
  131. for {
  132. token, _ := l.Next()
  133. if token == ErrorToken {
  134. test.T(t, l.Err(), io.EOF)
  135. test.T(t, i, len(tt.expected), "when error occurred we must be at the end")
  136. break
  137. } else if token == AttributeToken {
  138. test.That(t, i+1 < len(tt.expected), "index", i+1, "must not exceed expected attributes size", len(tt.expected))
  139. if i+1 < len(tt.expected) {
  140. test.String(t, string(l.Text()), tt.expected[i], "attribute keys must match")
  141. test.String(t, string(l.AttrVal()), tt.expected[i+1], "attribute keys must match")
  142. i += 2
  143. }
  144. }
  145. }
  146. })
  147. }
  148. }
  149. func TestErrors(t *testing.T) {
  150. var errorTests = []struct {
  151. html string
  152. col int
  153. }{
  154. {"a\x00b", 2},
  155. }
  156. for _, tt := range errorTests {
  157. t.Run(tt.html, func(t *testing.T) {
  158. l := NewLexer(bytes.NewBufferString(tt.html))
  159. for {
  160. token, _ := l.Next()
  161. if token == ErrorToken {
  162. if tt.col == 0 {
  163. test.T(t, l.Err(), io.EOF)
  164. } else if perr, ok := l.Err().(*parse.Error); ok {
  165. test.T(t, perr.Col, tt.col)
  166. } else {
  167. test.Fail(t, "bad error:", l.Err())
  168. }
  169. break
  170. }
  171. }
  172. })
  173. }
  174. }
  175. ////////////////////////////////////////////////////////////////
  176. var J int
  177. var ss = [][]byte{
  178. []byte(" style"),
  179. []byte("style"),
  180. []byte(" \r\n\tstyle"),
  181. []byte(" style"),
  182. []byte(" x"),
  183. []byte("x"),
  184. }
  185. func BenchmarkWhitespace1(b *testing.B) {
  186. for i := 0; i < b.N; i++ {
  187. for _, s := range ss {
  188. j := 0
  189. for {
  190. if c := s[j]; c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' {
  191. j++
  192. } else {
  193. break
  194. }
  195. }
  196. J += j
  197. }
  198. }
  199. }
  200. func BenchmarkWhitespace2(b *testing.B) {
  201. for i := 0; i < b.N; i++ {
  202. for _, s := range ss {
  203. j := 0
  204. for {
  205. if c := s[j]; c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' {
  206. j++
  207. continue
  208. }
  209. break
  210. }
  211. J += j
  212. }
  213. }
  214. }
  215. func BenchmarkWhitespace3(b *testing.B) {
  216. for i := 0; i < b.N; i++ {
  217. for _, s := range ss {
  218. j := 0
  219. for {
  220. if c := s[j]; c != ' ' && c != '\t' && c != '\n' && c != '\r' && c != '\f' {
  221. break
  222. }
  223. j++
  224. }
  225. J += j
  226. }
  227. }
  228. }
  229. ////////////////////////////////////////////////////////////////
  230. func ExampleNewLexer() {
  231. l := NewLexer(bytes.NewBufferString("<span class='user'>John Doe</span>"))
  232. out := ""
  233. for {
  234. tt, data := l.Next()
  235. if tt == ErrorToken {
  236. break
  237. }
  238. out += string(data)
  239. }
  240. fmt.Println(out)
  241. // Output: <span class='user'>John Doe</span>
  242. }