OILS / lazylex / html_test.py View on Github | oils.pub

363 lines, 226 significant
1#!/usr/bin/env python2
2from __future__ import print_function
3
4import unittest
5
6from lazylex import html # module under test log = html.log
7from doctools.util import log
8
9
10class FunctionsTest(unittest.TestCase):
11
12 def testToText(self):
13 # type: () -> None
14 t = html.ToText('<b name="&amp;"> three &lt; four && five </b>')
15 self.assertEqual(' three < four && five ', t)
16
17
18def _MakeTagLexer(s):
19 # type: (str) -> html.TagLexer
20 lex = html.TagLexer(s)
21 lex.Reset(0, len(s))
22 return lex
23
24
25def _PrintTokens(lex):
26 # type: (html.TagLexer) -> None
27 log('')
28 log('tag = %r', lex.GetTagName())
29 for tok, start, end in lex.Tokens():
30 log('%s %r', tok, lex.s[start:end])
31
32
33class TagLexerTest(unittest.TestCase):
34
35 def testTagName_DEPRECATED(self):
36 # type: () -> None
37 lex = _MakeTagLexer('<a href=foo class="bar" />')
38 self.assertEqual('a', lex.GetTagName())
39
40 def testGetAttrRaw(self):
41 # type: () -> None
42 lex = _MakeTagLexer('<a>')
43 _PrintTokens(lex)
44 self.assertEqual(None, lex.GetAttrRaw('oops'))
45
46 # <a novalue> means lex.Get('novalue') == ''
47 # https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttribute
48 # We are not distinguishing <a novalue=""> from <a novalue> in this API
49 lex = _MakeTagLexer('<a novalue>')
50 _PrintTokens(lex)
51 self.assertEqual('', lex.GetAttrRaw('novalue'))
52
53 lex = _MakeTagLexer('<a href="double quoted">')
54 _PrintTokens(lex)
55
56 self.assertEqual('double quoted', lex.GetAttrRaw('href'))
57 self.assertEqual(None, lex.GetAttrRaw('oops'))
58
59 lex = _MakeTagLexer('<a href=foo class="bar">')
60 _PrintTokens(lex)
61 self.assertEqual('bar', lex.GetAttrRaw('class'))
62
63 lex = _MakeTagLexer('<a href=foo class="bar" />')
64 _PrintTokens(lex)
65 self.assertEqual('bar', lex.GetAttrRaw('class'))
66
67 lex = _MakeTagLexer('<a href="?foo=1&amp;bar=2" />')
68 self.assertEqual('?foo=1&amp;bar=2', lex.GetAttrRaw('href'))
69
70 def testAllAttrs(self):
71 # type: () -> None
72 """
73 [('key', 'value')] for all
74 """
75 # closed
76 lex = _MakeTagLexer('<a href=foo class="bar" />')
77 self.assertEqual([('href', 'foo'), ('class', 'bar')],
78 lex.AllAttrsRaw())
79
80 lex = _MakeTagLexer('<a href="?foo=1&amp;bar=2" />')
81 self.assertEqual([('href', '?foo=1&amp;bar=2')], lex.AllAttrsRaw())
82
83 def testEmptyMissingValues(self):
84 # type: () -> None
85 # equivalent to <button disabled="">
86 lex = _MakeTagLexer('<button disabled>')
87 all_attrs = lex.AllAttrsRaw()
88 self.assertEqual([('disabled', '')], all_attrs)
89
90 slices = lex.AllAttrsRawSlice()
91 log('slices %s', slices)
92
93 lex = _MakeTagLexer(
94 '''<p double="" single='' empty= value missing empty2=>''')
95 all_attrs = lex.AllAttrsRaw()
96 self.assertEqual([
97 ('double', ''),
98 ('single', ''),
99 ('empty', 'value'),
100 ('missing', ''),
101 ('empty2', ''),
102 ], all_attrs)
103 # TODO: should have
104 log('all %s', all_attrs)
105
106 slices = lex.AllAttrsRawSlice()
107 log('slices %s', slices)
108
109 def testInvalidTag(self):
110 # type: () -> None
111 try:
112 lex = _MakeTagLexer('<a foo=bar !></a>')
113 all_attrs = lex.AllAttrsRaw()
114 except html.LexError as e:
115 print(e)
116 else:
117 self.fail('Expected LexError')
118
119
120def _MakeAttrValueLexer(s):
121 # type: (str) -> html.AttrValueLexer
122 lex = html.AttrValueLexer(s)
123 lex.Reset(0, len(s))
124 return lex
125
126
127class AttrValueLexerTest(unittest.TestCase):
128
129 def testGood(self):
130 # type: () -> None
131 lex = _MakeAttrValueLexer('?foo=42&amp;bar=99')
132 n = lex.NumTokens()
133 self.assertEqual(3, n)
134
135
136class LexerTest(unittest.TestCase):
137
138 def testInvalid(self):
139 # type: () -> None
140 from data_lang.htm8_test import ValidTokenList
141 for s in INVALID_LEX:
142 try:
143 tokens = ValidTokenList(s)
144 except html.LexError as e:
145 print(e)
146 else:
147 self.fail('Expected LexError %r' % s)
148
149 def testValid(self):
150 # type: () -> None
151
152 from data_lang.htm8_test import Lex
153
154 for s, _ in VALID_LEX:
155 tokens = Lex(s)
156 print()
157
158
159INVALID_LEX = [
160 '< >',
161 '<a><',
162 '&amp<',
163 '&<',
164 # Hm > is allowed?
165 #'a > b',
166 'a < b',
167 '<!-- unfinished comment',
168 '<? unfinished processing',
169 '</div bad=attr> <a> <b>',
170
171 # not allowed, but 3 > 4 is allowed
172 '<a> 3 < 4 </a>',
173 # Not a CDATA tag
174 '<STYLEz><</STYLEz>',
175]
176
177SKIP = 0
178UNCHANGED = 1
179
180VALID_LEX = [
181 # TODO: convert these to XML
182 ('<foo></foo>', UNCHANGED),
183 ('<foo x=y></foo>', ''),
184 #('<foo x="&"></foo>', '<foo x="&amp;"></foo>'),
185 ('<foo x="&"></foo>', ''),
186
187 # Allowed with BadAmpersand
188 ('<p> x & y </p>', '<p> x &amp; y </p>'),
189]
190
191INVALID_PARSE = [
192 '<a></b>',
193 '<a>', # missing closing tag
194 '<meta></meta>', # this is a self-closing tag
195]
196
197VALID_PARSE = [
198 ('<!DOCTYPE html>\n', ''),
199 ('<!DOCTYPE>', ''),
200
201 # empty strings
202 ('<p x=""></p>', UNCHANGED),
203 ("<p x=''></p>", UNCHANGED),
204 ('<self-closing a="b" />', UNCHANGED),
205
206 # We could also normalize CDATA?
207 # Note that CDATA has an escaping problem: you need to handle it ]]> with
208 # concatenation. It just "pushes the problem around".
209 # So I think it's better to use ONE kind of escaping, which is &lt;
210 ('<script><![CDATA[ <wtf> >< ]]></script>', UNCHANGED),
211
212 # allowed, but 3 < 4 is not allowed
213 ('<a> 3 > 4 </a>', '<a> 3 &gt; 4 </a>'),
214 # allowed, but 3 > 4 is not allowed
215 ('<p x="3 < 4"></p>', ''),
216 ('<b><a href="foo">link</a></b>', UNCHANGED),
217
218 # TODO: should be self-closing
219 #('<meta><a></a>', '<meta/><a></a>'),
220 ('<meta><a></a>', ''),
221
222 # no attribute
223 ('<button disabled></button>', ''),
224 ('<button disabled=></button>', ''),
225 ('<button disabled= ></button>', ''),
226
227 # single quoted is pretty common
228 ("<a href='single'></a>", ''),
229
230 # Conceding to reality - I used these myself
231 ('<a href=ble.sh></a>', ''),
232 ('<a href=foo.html></a>', ''),
233 ('<foo x="&"></foo>', ''),
234
235 # caps
236 ('<foo></FOO>', ''),
237 ('<Foo></fOO>', ''),
238
239 # capital VOID tag
240 ('<META><a></a>', ''),
241 ('<script><</script>', ''),
242 # matching
243 ('<SCRipt><</SCRipt>', ''),
244 ('<SCRIPT><</SCRIPT>', ''),
245 ('<STYLE><</STYLE>', ''),
246 #'<SCRipt><</script>',
247
248 # Regression test from blog
249 ('<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>',
250 '')
251
252 # Note: Python HTMLParser.py does DYNAMIC compilation of regex with re.I
253 # flag to handle this! Gah I want something faster.
254 #'<script><</SCRIPT>',
255
256 # TODO: Test <svg> and <math> ?
257]
258
259VALID_XML = [
260 '<meta></meta>',
261]
262
263INVALID_TAG_LEX = [
264 # not allowed, but 3 < 4 is allowed
265 '<p x="3 > 4"></p>',
266 # same thing
267 '<a href=">"></a>',
268 '<a foo=bar !></a>', # bad attr
269]
270
271
272class ValidateTest(unittest.TestCase):
273
274 def testInvalidOld(self):
275 # type: () -> None
276 counters = html.Counters()
277 for s in INVALID_LEX + INVALID_TAG_LEX:
278 try:
279 html.Validate(s, html.BALANCED_TAGS, counters)
280 except html.LexError as e:
281 print(e)
282 else:
283 self.fail('Expected LexError %r' % s)
284
285 for s in INVALID_PARSE:
286 try:
287 html.Validate(s, html.BALANCED_TAGS, counters)
288 except html.ParseError as e:
289 print(e)
290 else:
291 self.fail('Expected ParseError')
292
293 def testValidOld(self):
294 # type: () -> None
295 counters = html.Counters()
296 for s, _ in VALID_PARSE:
297 html.Validate(s, html.BALANCED_TAGS, counters)
298 print('HTML5 %r' % s)
299 #print('HTML5 attrs %r' % counters.debug_attrs)
300
301 def testValidXmlOld(self):
302 # type: () -> None
303 counters = html.Counters()
304 for s in VALID_XML:
305 html.ValidateOld(s, html.BALANCED_TAGS | html.NO_SPECIAL_TAGS,
306 counters)
307 print('XML %r' % s)
308 #print('XML attrs %r' % counters.debug_attrs)
309
310 def testInvalid(self):
311 # type: () -> None
312 counters = html.Counters()
313 for s in INVALID_LEX + INVALID_TAG_LEX:
314 try:
315 html.Validate(s, html.BALANCED_TAGS, counters)
316 except html.LexError as e:
317 print(e)
318 else:
319 self.fail('Expected LexError %r' % s)
320
321 for s in INVALID_PARSE:
322 try:
323 html.Validate(s, html.BALANCED_TAGS, counters)
324 except html.ParseError as e:
325 print(e)
326 else:
327 self.fail('Expected ParseError')
328
329 def testValid(self):
330 # type: () -> None
331 counters = html.Counters()
332 for s, _ in VALID_PARSE:
333 html.Validate(s, html.BALANCED_TAGS, counters)
334 print('HTML5 %r' % s)
335 #print('HTML5 attrs %r' % counters.debug_attrs)
336
337 def testValidXml(self):
338 # type: () -> None
339 counters = html.Counters()
340 for s in VALID_XML:
341 html.Validate(s, html.BALANCED_TAGS | html.NO_SPECIAL_TAGS,
342 counters)
343 print('XML %r' % s)
344 #print('XML attrs %r' % counters.debug_attrs)
345
346
347class XmlTest(unittest.TestCase):
348
349 def testValid(self):
350 # type: () -> None
351 counters = html.Counters()
352 for h, expected_xml in VALID_LEX + VALID_PARSE:
353 actual = html.ToXml(h)
354 if expected_xml == UNCHANGED: # Unchanged
355 self.assertEqual(h, actual)
356 elif expected_xml == '': # Skip
357 pass
358 else:
359 self.assertEqual(expected_xml, actual)
360
361
362if __name__ == '__main__':
363 unittest.main()