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

328 lines, 201 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
120class LexerTest(unittest.TestCase):
121
122 def testInvalid(self):
123 # type: () -> None
124 from data_lang.htm8_test import ValidTokenList
125 for s in INVALID_LEX:
126 try:
127 tokens = ValidTokenList(s)
128 except html.LexError as e:
129 print(e)
130 else:
131 self.fail('Expected LexError %r' % s)
132
133 def testValid(self):
134 # type: () -> None
135
136 from data_lang.htm8_test import Lex
137
138 for s, _ in VALID_LEX:
139 tokens = Lex(s)
140 print()
141
142
143INVALID_LEX = [
144 '< >',
145 '<a><',
146 '&amp<',
147 '&<',
148 # Hm > is allowed?
149 #'a > b',
150 'a < b',
151 '<!-- unfinished comment',
152 '<? unfinished processing',
153 '</div bad=attr> <a> <b>',
154
155 # not allowed, but 3 > 4 is allowed
156 '<a> 3 < 4 </a>',
157 # Not a CDATA tag
158 '<STYLEz><</STYLEz>',
159]
160
161SKIP = 0
162UNCHANGED = 1
163
164VALID_LEX = [
165 # TODO: convert these to XML
166 ('<foo></foo>', UNCHANGED),
167 ('<foo x=y></foo>', ''),
168 #('<foo x="&"></foo>', '<foo x="&amp;"></foo>'),
169 ('<foo x="&"></foo>', ''),
170
171 # Allowed with BadAmpersand
172 ('<p> x & y </p>', '<p> x &amp; y </p>'),
173
174 # No ambiguity
175 ('<img src=/ >', ''),
176 ('<img src="/">', ''),
177 ('<img src=foo/ >', ''),
178]
179
180INVALID_PARSE = [
181 '<a></b>',
182 '<a>', # missing closing tag
183 '<meta></meta>', # this is a self-closing tag
184]
185
186INVALID_ATTR_LEX = [
187 # Ambiguous, should be ""
188 '<img src=/>',
189 '<img src= />',
190 '<img src=foo/>',
191 '<img src= foo/>',
192
193 # Quoting
194 '<img src=x"y">',
195 "<img src=j''>",
196]
197
198VALID_PARSE = [
199 ('<!DOCTYPE html>\n', ''),
200 ('<!DOCTYPE>', ''),
201
202 # empty strings
203 ('<p x=""></p>', UNCHANGED),
204 ("<p x=''></p>", UNCHANGED),
205 ('<self-closing a="b" />', UNCHANGED),
206
207 # We could also normalize CDATA?
208 # Note that CDATA has an escaping problem: you need to handle it ]]> with
209 # concatenation. It just "pushes the problem around".
210 # So I think it's better to use ONE kind of escaping, which is &lt;
211 ('<script><![CDATA[ <wtf> >< ]]></script>', UNCHANGED),
212
213 # allowed, but 3 < 4 is not allowed
214 ('<a> 3 > 4 </a>', '<a> 3 &gt; 4 </a>'),
215 # allowed, but 3 > 4 is not allowed
216 ('<p x="3 < 4"></p>', ''),
217 ('<b><a href="foo">link</a></b>', UNCHANGED),
218
219 # TODO: should be self-closing
220 #('<meta><a></a>', '<meta/><a></a>'),
221 ('<meta><a></a>', ''),
222
223 # no attribute
224 ('<button disabled></button>', ''),
225 ('<button disabled=></button>', ''),
226 ('<button disabled= ></button>', ''),
227
228 # single quoted is pretty common
229 ("<a href='single'></a>", ''),
230
231 # Conceding to reality - I used these myself
232 ('<a href=ble.sh></a>', ''),
233 ('<a href=foo.html></a>', ''),
234 ('<foo x="&"></foo>', ''),
235
236 # caps
237 ('<foo></FOO>', ''),
238 ('<Foo></fOO>', ''),
239
240 # capital VOID tag
241 ('<META><a></a>', ''),
242 ('<script><</script>', ''),
243 # matching
244 ('<SCRipt><</SCRipt>', ''),
245 ('<SCRIPT><</SCRIPT>', ''),
246 ('<STYLE><</STYLE>', ''),
247 #'<SCRipt><</script>',
248
249 # Regression test from blog
250 ('<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>',
251 '')
252
253 # Note: Python HTMLParser.py does DYNAMIC compilation of regex with re.I
254 # flag to handle this! Gah I want something faster.
255 #'<script><</SCRIPT>',
256
257 # TODO: Test <svg> and <math> ?
258]
259
260VALID_XML = [
261 '<meta></meta>',
262]
263
264INVALID_TAG_LEX = [
265 # not allowed, but 3 < 4 is allowed
266 '<p x="3 > 4"></p>',
267 # same thing
268 '<a href=">"></a>',
269 '<a foo=bar !></a>', # bad attr
270]
271
272
273class ValidateTest(unittest.TestCase):
274
275 def testInvalid(self):
276 # type: () -> None
277 counters = html.Counters()
278 for s in INVALID_LEX + INVALID_TAG_LEX + INVALID_ATTR_LEX:
279 try:
280 html.Validate(s, html.BALANCED_TAGS, counters)
281 except html.LexError as e:
282 print(e)
283 else:
284 self.fail('Expected LexError %r' % s)
285
286 for s in INVALID_PARSE:
287 try:
288 html.Validate(s, html.BALANCED_TAGS, counters)
289 except html.ParseError as e:
290 print(e)
291 else:
292 self.fail('Expected ParseError')
293
294 def testValid(self):
295 # type: () -> None
296 counters = html.Counters()
297 for s, _ in VALID_PARSE:
298 print('HTML5 %r' % s)
299 html.Validate(s, html.BALANCED_TAGS, counters)
300 #print('HTML5 attrs %r' % counters.debug_attrs)
301
302 def testValidXml(self):
303 # type: () -> None
304 counters = html.Counters()
305 for s in VALID_XML:
306 print('XML %r' % s)
307 html.Validate(s, html.BALANCED_TAGS | html.NO_SPECIAL_TAGS,
308 counters)
309 #print('XML attrs %r' % counters.debug_attrs)
310
311
312class XmlTest(unittest.TestCase):
313
314 def testValid(self):
315 # type: () -> None
316 counters = html.Counters()
317 for h, expected_xml in VALID_LEX + VALID_PARSE:
318 actual = html.ToXml(h)
319 if expected_xml == UNCHANGED: # Unchanged
320 self.assertEqual(h, actual)
321 elif expected_xml == '': # Skip
322 pass
323 else:
324 self.assertEqual(expected_xml, actual)
325
326
327if __name__ == '__main__':
328 unittest.main()