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

359 lines, 224 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 # Note: Python HTMLParser.py does DYNAMIC compilation of regex with re.I
249 # flag to handle this! Gah I want something faster.
250 #'<script><</SCRIPT>',
251
252 # TODO: Test <svg> and <math> ?
253]
254
255VALID_XML = [
256 '<meta></meta>',
257]
258
259INVALID_TAG_LEX = [
260 # not allowed, but 3 < 4 is allowed
261 '<p x="3 > 4"></p>',
262 # same thing
263 '<a href=">"></a>',
264 '<a foo=bar !></a>', # bad attr
265]
266
267
268class ValidateTest(unittest.TestCase):
269
270 def testInvalidOld(self):
271 # type: () -> None
272 counters = html.Counters()
273 for s in INVALID_LEX + INVALID_TAG_LEX:
274 try:
275 html.Validate(s, html.BALANCED_TAGS, counters)
276 except html.LexError as e:
277 print(e)
278 else:
279 self.fail('Expected LexError %r' % s)
280
281 for s in INVALID_PARSE:
282 try:
283 html.Validate(s, html.BALANCED_TAGS, counters)
284 except html.ParseError as e:
285 print(e)
286 else:
287 self.fail('Expected ParseError')
288
289 def testValidOld(self):
290 # type: () -> None
291 counters = html.Counters()
292 for s, _ in VALID_PARSE:
293 html.Validate(s, html.BALANCED_TAGS, counters)
294 print('HTML5 %r' % s)
295 #print('HTML5 attrs %r' % counters.debug_attrs)
296
297 def testValidXmlOld(self):
298 # type: () -> None
299 counters = html.Counters()
300 for s in VALID_XML:
301 html.ValidateOld(s, html.BALANCED_TAGS | html.NO_SPECIAL_TAGS,
302 counters)
303 print('XML %r' % s)
304 #print('XML attrs %r' % counters.debug_attrs)
305
306 def testInvalid(self):
307 # type: () -> None
308 counters = html.Counters()
309 for s in INVALID_LEX + INVALID_TAG_LEX:
310 try:
311 html.Validate(s, html.BALANCED_TAGS, counters)
312 except html.LexError as e:
313 print(e)
314 else:
315 self.fail('Expected LexError %r' % s)
316
317 for s in INVALID_PARSE:
318 try:
319 html.Validate(s, html.BALANCED_TAGS, counters)
320 except html.ParseError as e:
321 print(e)
322 else:
323 self.fail('Expected ParseError')
324
325 def testValid(self):
326 # type: () -> None
327 counters = html.Counters()
328 for s, _ in VALID_PARSE:
329 html.Validate(s, html.BALANCED_TAGS, counters)
330 print('HTML5 %r' % s)
331 #print('HTML5 attrs %r' % counters.debug_attrs)
332
333 def testValidXml(self):
334 # type: () -> None
335 counters = html.Counters()
336 for s in VALID_XML:
337 html.Validate(s, html.BALANCED_TAGS | html.NO_SPECIAL_TAGS,
338 counters)
339 print('XML %r' % s)
340 #print('XML attrs %r' % counters.debug_attrs)
341
342
343class XmlTest(unittest.TestCase):
344
345 def testValid(self):
346 # type: () -> None
347 counters = html.Counters()
348 for h, expected_xml in VALID_LEX + VALID_PARSE:
349 actual = html.ToXml(h)
350 if expected_xml == UNCHANGED: # Unchanged
351 self.assertEqual(h, actual)
352 elif expected_xml == '': # Skip
353 pass
354 else:
355 self.assertEqual(expected_xml, actual)
356
357
358if __name__ == '__main__':
359 unittest.main()