~ubuntu-branches/ubuntu/trusty/python3.4/trusty-proposed

« back to all changes in this revision

Viewing changes to Lib/test/test_htmlparser.py

  • Committer: Package Import Robot
  • Author(s): Matthias Klose
  • Date: 2013-11-25 09:44:27 UTC
  • Revision ID: package-import@ubuntu.com-20131125094427-lzxj8ap5w01lmo7f
Tags: upstream-3.4~b1
ImportĀ upstreamĀ versionĀ 3.4~b1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Tests for HTMLParser.py."""
 
2
 
 
3
import html.parser
 
4
import pprint
 
5
import unittest
 
6
from test import support
 
7
 
 
8
 
 
9
class EventCollector(html.parser.HTMLParser):
 
10
 
 
11
    def __init__(self, *args, **kw):
 
12
        self.events = []
 
13
        self.append = self.events.append
 
14
        html.parser.HTMLParser.__init__(self, *args, **kw)
 
15
 
 
16
    def get_events(self):
 
17
        # Normalize the list of events so that buffer artefacts don't
 
18
        # separate runs of contiguous characters.
 
19
        L = []
 
20
        prevtype = None
 
21
        for event in self.events:
 
22
            type = event[0]
 
23
            if type == prevtype == "data":
 
24
                L[-1] = ("data", L[-1][1] + event[1])
 
25
            else:
 
26
                L.append(event)
 
27
            prevtype = type
 
28
        self.events = L
 
29
        return L
 
30
 
 
31
    # structure markup
 
32
 
 
33
    def handle_starttag(self, tag, attrs):
 
34
        self.append(("starttag", tag, attrs))
 
35
 
 
36
    def handle_startendtag(self, tag, attrs):
 
37
        self.append(("startendtag", tag, attrs))
 
38
 
 
39
    def handle_endtag(self, tag):
 
40
        self.append(("endtag", tag))
 
41
 
 
42
    # all other markup
 
43
 
 
44
    def handle_comment(self, data):
 
45
        self.append(("comment", data))
 
46
 
 
47
    def handle_charref(self, data):
 
48
        self.append(("charref", data))
 
49
 
 
50
    def handle_data(self, data):
 
51
        self.append(("data", data))
 
52
 
 
53
    def handle_decl(self, data):
 
54
        self.append(("decl", data))
 
55
 
 
56
    def handle_entityref(self, data):
 
57
        self.append(("entityref", data))
 
58
 
 
59
    def handle_pi(self, data):
 
60
        self.append(("pi", data))
 
61
 
 
62
    def unknown_decl(self, decl):
 
63
        self.append(("unknown decl", decl))
 
64
 
 
65
 
 
66
class EventCollectorExtra(EventCollector):
 
67
 
 
68
    def handle_starttag(self, tag, attrs):
 
69
        EventCollector.handle_starttag(self, tag, attrs)
 
70
        self.append(("starttag_text", self.get_starttag_text()))
 
71
 
 
72
 
 
73
class EventCollectorCharrefs(EventCollector):
 
74
 
 
75
    def get_events(self):
 
76
        return self.events
 
77
 
 
78
    def handle_charref(self, data):
 
79
        self.fail('This should never be called with convert_charrefs=True')
 
80
 
 
81
    def handle_entityref(self, data):
 
82
        self.fail('This should never be called with convert_charrefs=True')
 
83
 
 
84
 
 
85
class TestCaseBase(unittest.TestCase):
 
86
 
 
87
    def get_collector(self):
 
88
        raise NotImplementedError
 
89
 
 
90
    def _run_check(self, source, expected_events, collector=None):
 
91
        if collector is None:
 
92
            collector = self.get_collector()
 
93
        parser = collector
 
94
        for s in source:
 
95
            parser.feed(s)
 
96
        parser.close()
 
97
        events = parser.get_events()
 
98
        if events != expected_events:
 
99
            self.fail("received events did not match expected events" +
 
100
                      "\nSource:\n" + repr(source) +
 
101
                      "\nExpected:\n" + pprint.pformat(expected_events) +
 
102
                      "\nReceived:\n" + pprint.pformat(events))
 
103
 
 
104
    def _run_check_extra(self, source, events):
 
105
        self._run_check(source, events,
 
106
                        EventCollectorExtra(convert_charrefs=False))
 
107
 
 
108
    def _parse_error(self, source):
 
109
        def parse(source=source):
 
110
            parser = self.get_collector()
 
111
            parser.feed(source)
 
112
            parser.close()
 
113
        with self.assertRaises(html.parser.HTMLParseError):
 
114
            with self.assertWarns(DeprecationWarning):
 
115
                parse()
 
116
 
 
117
 
 
118
class HTMLParserStrictTestCase(TestCaseBase):
 
119
 
 
120
    def get_collector(self):
 
121
        with support.check_warnings(("", DeprecationWarning), quite=False):
 
122
            return EventCollector(strict=True, convert_charrefs=False)
 
123
 
 
124
    def test_processing_instruction_only(self):
 
125
        self._run_check("<?processing instruction>", [
 
126
            ("pi", "processing instruction"),
 
127
            ])
 
128
        self._run_check("<?processing instruction ?>", [
 
129
            ("pi", "processing instruction ?"),
 
130
            ])
 
131
 
 
132
    def test_simple_html(self):
 
133
        self._run_check("""
 
134
<!DOCTYPE html PUBLIC 'foo'>
 
135
<HTML>&entity;&#32;
 
136
<!--comment1a
 
137
-></foo><bar>&lt;<?pi?></foo<bar
 
138
comment1b-->
 
139
<Img sRc='Bar' isMAP>sample
 
140
text
 
141
&#x201C;
 
142
<!--comment2a-- --comment2b-->
 
143
</Html>
 
144
""", [
 
145
    ("data", "\n"),
 
146
    ("decl", "DOCTYPE html PUBLIC 'foo'"),
 
147
    ("data", "\n"),
 
148
    ("starttag", "html", []),
 
149
    ("entityref", "entity"),
 
150
    ("charref", "32"),
 
151
    ("data", "\n"),
 
152
    ("comment", "comment1a\n-></foo><bar>&lt;<?pi?></foo<bar\ncomment1b"),
 
153
    ("data", "\n"),
 
154
    ("starttag", "img", [("src", "Bar"), ("ismap", None)]),
 
155
    ("data", "sample\ntext\n"),
 
156
    ("charref", "x201C"),
 
157
    ("data", "\n"),
 
158
    ("comment", "comment2a-- --comment2b"),
 
159
    ("data", "\n"),
 
160
    ("endtag", "html"),
 
161
    ("data", "\n"),
 
162
    ])
 
163
 
 
164
    def test_malformatted_charref(self):
 
165
        self._run_check("<p>&#bad;</p>", [
 
166
            ("starttag", "p", []),
 
167
            ("data", "&#bad;"),
 
168
            ("endtag", "p"),
 
169
        ])
 
170
 
 
171
    def test_unclosed_entityref(self):
 
172
        self._run_check("&entityref foo", [
 
173
            ("entityref", "entityref"),
 
174
            ("data", " foo"),
 
175
            ])
 
176
 
 
177
    def test_bad_nesting(self):
 
178
        # Strangely, this *is* supposed to test that overlapping
 
179
        # elements are allowed.  HTMLParser is more geared toward
 
180
        # lexing the input that parsing the structure.
 
181
        self._run_check("<a><b></a></b>", [
 
182
            ("starttag", "a", []),
 
183
            ("starttag", "b", []),
 
184
            ("endtag", "a"),
 
185
            ("endtag", "b"),
 
186
            ])
 
187
 
 
188
    def test_bare_ampersands(self):
 
189
        self._run_check("this text & contains & ampersands &", [
 
190
            ("data", "this text & contains & ampersands &"),
 
191
            ])
 
192
 
 
193
    def test_bare_pointy_brackets(self):
 
194
        self._run_check("this < text > contains < bare>pointy< brackets", [
 
195
            ("data", "this < text > contains < bare>pointy< brackets"),
 
196
            ])
 
197
 
 
198
    def test_illegal_declarations(self):
 
199
        self._parse_error('<!spacer type="block" height="25">')
 
200
 
 
201
    def test_starttag_end_boundary(self):
 
202
        self._run_check("""<a b='<'>""", [("starttag", "a", [("b", "<")])])
 
203
        self._run_check("""<a b='>'>""", [("starttag", "a", [("b", ">")])])
 
204
 
 
205
    def test_buffer_artefacts(self):
 
206
        output = [("starttag", "a", [("b", "<")])]
 
207
        self._run_check(["<a b='<'>"], output)
 
208
        self._run_check(["<a ", "b='<'>"], output)
 
209
        self._run_check(["<a b", "='<'>"], output)
 
210
        self._run_check(["<a b=", "'<'>"], output)
 
211
        self._run_check(["<a b='<", "'>"], output)
 
212
        self._run_check(["<a b='<'", ">"], output)
 
213
 
 
214
        output = [("starttag", "a", [("b", ">")])]
 
215
        self._run_check(["<a b='>'>"], output)
 
216
        self._run_check(["<a ", "b='>'>"], output)
 
217
        self._run_check(["<a b", "='>'>"], output)
 
218
        self._run_check(["<a b=", "'>'>"], output)
 
219
        self._run_check(["<a b='>", "'>"], output)
 
220
        self._run_check(["<a b='>'", ">"], output)
 
221
 
 
222
        output = [("comment", "abc")]
 
223
        self._run_check(["", "<!--abc-->"], output)
 
224
        self._run_check(["<", "!--abc-->"], output)
 
225
        self._run_check(["<!", "--abc-->"], output)
 
226
        self._run_check(["<!-", "-abc-->"], output)
 
227
        self._run_check(["<!--", "abc-->"], output)
 
228
        self._run_check(["<!--a", "bc-->"], output)
 
229
        self._run_check(["<!--ab", "c-->"], output)
 
230
        self._run_check(["<!--abc", "-->"], output)
 
231
        self._run_check(["<!--abc-", "->"], output)
 
232
        self._run_check(["<!--abc--", ">"], output)
 
233
        self._run_check(["<!--abc-->", ""], output)
 
234
 
 
235
    def test_starttag_junk_chars(self):
 
236
        self._parse_error("</>")
 
237
        self._parse_error("</$>")
 
238
        self._parse_error("</")
 
239
        self._parse_error("</a")
 
240
        self._parse_error("<a<a>")
 
241
        self._parse_error("</a<a>")
 
242
        self._parse_error("<!")
 
243
        self._parse_error("<a")
 
244
        self._parse_error("<a foo='bar'")
 
245
        self._parse_error("<a foo='bar")
 
246
        self._parse_error("<a foo='>'")
 
247
        self._parse_error("<a foo='>")
 
248
        self._parse_error("<a$>")
 
249
        self._parse_error("<a$b>")
 
250
        self._parse_error("<a$b/>")
 
251
        self._parse_error("<a$b  >")
 
252
        self._parse_error("<a$b  />")
 
253
 
 
254
    def test_valid_doctypes(self):
 
255
        # from http://www.w3.org/QA/2002/04/valid-dtd-list.html
 
256
        dtds = ['HTML',  # HTML5 doctype
 
257
                ('HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
 
258
                 '"http://www.w3.org/TR/html4/strict.dtd"'),
 
259
                ('HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" '
 
260
                 '"http://www.w3.org/TR/html4/loose.dtd"'),
 
261
                ('html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" '
 
262
                 '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"'),
 
263
                ('html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" '
 
264
                 '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"'),
 
265
                ('math PUBLIC "-//W3C//DTD MathML 2.0//EN" '
 
266
                 '"http://www.w3.org/Math/DTD/mathml2/mathml2.dtd"'),
 
267
                ('html PUBLIC "-//W3C//DTD '
 
268
                 'XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" '
 
269
                 '"http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd"'),
 
270
                ('svg PUBLIC "-//W3C//DTD SVG 1.1//EN" '
 
271
                 '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"'),
 
272
                'html PUBLIC "-//IETF//DTD HTML 2.0//EN"',
 
273
                'html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"']
 
274
        for dtd in dtds:
 
275
            self._run_check("<!DOCTYPE %s>" % dtd,
 
276
                            [('decl', 'DOCTYPE ' + dtd)])
 
277
 
 
278
    def test_declaration_junk_chars(self):
 
279
        self._parse_error("<!DOCTYPE foo $ >")
 
280
 
 
281
    def test_startendtag(self):
 
282
        self._run_check("<p/>", [
 
283
            ("startendtag", "p", []),
 
284
            ])
 
285
        self._run_check("<p></p>", [
 
286
            ("starttag", "p", []),
 
287
            ("endtag", "p"),
 
288
            ])
 
289
        self._run_check("<p><img src='foo' /></p>", [
 
290
            ("starttag", "p", []),
 
291
            ("startendtag", "img", [("src", "foo")]),
 
292
            ("endtag", "p"),
 
293
            ])
 
294
 
 
295
    def test_get_starttag_text(self):
 
296
        s = """<foo:bar   \n   one="1"\ttwo=2   >"""
 
297
        self._run_check_extra(s, [
 
298
            ("starttag", "foo:bar", [("one", "1"), ("two", "2")]),
 
299
            ("starttag_text", s)])
 
300
 
 
301
    def test_cdata_content(self):
 
302
        contents = [
 
303
            '<!-- not a comment --> &not-an-entity-ref;',
 
304
            "<not a='start tag'>",
 
305
            '<a href="" /> <p> <span></span>',
 
306
            'foo = "</scr" + "ipt>";',
 
307
            'foo = "</SCRIPT" + ">";',
 
308
            'foo = <\n/script> ',
 
309
            '<!-- document.write("</scr" + "ipt>"); -->',
 
310
            ('\n//<![CDATA[\n'
 
311
             'document.write(\'<s\'+\'cript type="text/javascript" '
 
312
             'src="http://www.example.org/r=\'+new '
 
313
             'Date().getTime()+\'"><\\/s\'+\'cript>\');\n//]]>'),
 
314
            '\n<!-- //\nvar foo = 3.14;\n// -->\n',
 
315
            'foo = "</sty" + "le>";',
 
316
            '<!-- \u2603 -->',
 
317
            # these two should be invalid according to the HTML 5 spec,
 
318
            # section 8.1.2.2
 
319
            #'foo = </\nscript>',
 
320
            #'foo = </ script>',
 
321
        ]
 
322
        elements = ['script', 'style', 'SCRIPT', 'STYLE', 'Script', 'Style']
 
323
        for content in contents:
 
324
            for element in elements:
 
325
                element_lower = element.lower()
 
326
                s = '<{element}>{content}</{element}>'.format(element=element,
 
327
                                                               content=content)
 
328
                self._run_check(s, [("starttag", element_lower, []),
 
329
                                    ("data", content),
 
330
                                    ("endtag", element_lower)])
 
331
 
 
332
    def test_cdata_with_closing_tags(self):
 
333
        # see issue #13358
 
334
        # make sure that HTMLParser calls handle_data only once for each CDATA.
 
335
        # The normal event collector normalizes  the events in get_events,
 
336
        # so we override it to return the original list of events.
 
337
        class Collector(EventCollector):
 
338
            def get_events(self):
 
339
                return self.events
 
340
 
 
341
        content = """<!-- not a comment --> &not-an-entity-ref;
 
342
                  <a href="" /> </p><p> <span></span></style>
 
343
                  '</script' + '>'"""
 
344
        for element in [' script', 'script ', ' script ',
 
345
                        '\nscript', 'script\n', '\nscript\n']:
 
346
            element_lower = element.lower().strip()
 
347
            s = '<script>{content}</{element}>'.format(element=element,
 
348
                                                       content=content)
 
349
            self._run_check(s, [("starttag", element_lower, []),
 
350
                                ("data", content),
 
351
                                ("endtag", element_lower)],
 
352
                            collector=Collector(convert_charrefs=False))
 
353
 
 
354
    def test_comments(self):
 
355
        html = ("<!-- I'm a valid comment -->"
 
356
                '<!--me too!-->'
 
357
                '<!------>'
 
358
                '<!---->'
 
359
                '<!----I have many hyphens---->'
 
360
                '<!-- I have a > in the middle -->'
 
361
                '<!-- and I have -- in the middle! -->')
 
362
        expected = [('comment', " I'm a valid comment "),
 
363
                    ('comment', 'me too!'),
 
364
                    ('comment', '--'),
 
365
                    ('comment', ''),
 
366
                    ('comment', '--I have many hyphens--'),
 
367
                    ('comment', ' I have a > in the middle '),
 
368
                    ('comment', ' and I have -- in the middle! ')]
 
369
        self._run_check(html, expected)
 
370
 
 
371
    def test_condcoms(self):
 
372
        html = ('<!--[if IE & !(lte IE 8)]>aren\'t<![endif]-->'
 
373
                '<!--[if IE 8]>condcoms<![endif]-->'
 
374
                '<!--[if lte IE 7]>pretty?<![endif]-->')
 
375
        expected = [('comment', "[if IE & !(lte IE 8)]>aren't<![endif]"),
 
376
                    ('comment', '[if IE 8]>condcoms<![endif]'),
 
377
                    ('comment', '[if lte IE 7]>pretty?<![endif]')]
 
378
        self._run_check(html, expected)
 
379
 
 
380
    def test_convert_charrefs(self):
 
381
        collector = lambda: EventCollectorCharrefs(convert_charrefs=True)
 
382
        self.assertTrue(collector().convert_charrefs)
 
383
        charrefs = ['&quot;', '&#34;', '&#x22;', '&quot', '&#34', '&#x22']
 
384
        # check charrefs in the middle of the text/attributes
 
385
        expected = [('starttag', 'a', [('href', 'foo"zar')]),
 
386
                    ('data', 'a"z'), ('endtag', 'a')]
 
387
        for charref in charrefs:
 
388
            self._run_check('<a href="foo{0}zar">a{0}z</a>'.format(charref),
 
389
                            expected, collector=collector())
 
390
        # check charrefs at the beginning/end of the text/attributes
 
391
        expected = [('data', '"'),
 
392
                    ('starttag', 'a', [('x', '"'), ('y', '"X'), ('z', 'X"')]),
 
393
                    ('data', '"'), ('endtag', 'a'), ('data', '"')]
 
394
        for charref in charrefs:
 
395
            self._run_check('{0}<a x="{0}" y="{0}X" z="X{0}">'
 
396
                            '{0}</a>{0}'.format(charref),
 
397
                            expected, collector=collector())
 
398
        # check charrefs in <script>/<style> elements
 
399
        for charref in charrefs:
 
400
            text = 'X'.join([charref]*3)
 
401
            expected = [('data', '"'),
 
402
                        ('starttag', 'script', []), ('data', text),
 
403
                        ('endtag', 'script'), ('data', '"'),
 
404
                        ('starttag', 'style', []), ('data', text),
 
405
                        ('endtag', 'style'), ('data', '"')]
 
406
            self._run_check('{1}<script>{0}</script>{1}'
 
407
                            '<style>{0}</style>{1}'.format(text, charref),
 
408
                            expected, collector=collector())
 
409
        # check truncated charrefs at the end of the file
 
410
        html = '&quo &# &#x'
 
411
        for x in range(1, len(html)):
 
412
            self._run_check(html[:x], [('data', html[:x])],
 
413
                            collector=collector())
 
414
        # check a string with no charrefs
 
415
        self._run_check('no charrefs here', [('data', 'no charrefs here')],
 
416
                        collector=collector())
 
417
 
 
418
 
 
419
class HTMLParserTolerantTestCase(HTMLParserStrictTestCase):
 
420
 
 
421
    def get_collector(self):
 
422
        return EventCollector(convert_charrefs=False)
 
423
 
 
424
    def test_deprecation_warnings(self):
 
425
        with self.assertWarns(DeprecationWarning):
 
426
            EventCollector()  # convert_charrefs not passed explicitly
 
427
        with self.assertWarns(DeprecationWarning):
 
428
            EventCollector(strict=True)
 
429
        with self.assertWarns(DeprecationWarning):
 
430
            EventCollector(strict=False)
 
431
        with self.assertRaises(html.parser.HTMLParseError):
 
432
            with self.assertWarns(DeprecationWarning):
 
433
                EventCollector().error('test')
 
434
 
 
435
    def test_tolerant_parsing(self):
 
436
        self._run_check('<html <html>te>>xt&a<<bc</a></html>\n'
 
437
                        '<img src="URL><//img></html</html>', [
 
438
                            ('starttag', 'html', [('<html', None)]),
 
439
                            ('data', 'te>>xt'),
 
440
                            ('entityref', 'a'),
 
441
                            ('data', '<'),
 
442
                            ('starttag', 'bc<', [('a', None)]),
 
443
                            ('endtag', 'html'),
 
444
                            ('data', '\n<img src="URL>'),
 
445
                            ('comment', '/img'),
 
446
                            ('endtag', 'html<')])
 
447
 
 
448
    def test_starttag_junk_chars(self):
 
449
        self._run_check("</>", [])
 
450
        self._run_check("</$>", [('comment', '$')])
 
451
        self._run_check("</", [('data', '</')])
 
452
        self._run_check("</a", [('data', '</a')])
 
453
        self._run_check("<a<a>", [('starttag', 'a<a', [])])
 
454
        self._run_check("</a<a>", [('endtag', 'a<a')])
 
455
        self._run_check("<!", [('data', '<!')])
 
456
        self._run_check("<a", [('data', '<a')])
 
457
        self._run_check("<a foo='bar'", [('data', "<a foo='bar'")])
 
458
        self._run_check("<a foo='bar", [('data', "<a foo='bar")])
 
459
        self._run_check("<a foo='>'", [('data', "<a foo='>'")])
 
460
        self._run_check("<a foo='>", [('data', "<a foo='>")])
 
461
        self._run_check("<a$>", [('starttag', 'a$', [])])
 
462
        self._run_check("<a$b>", [('starttag', 'a$b', [])])
 
463
        self._run_check("<a$b/>", [('startendtag', 'a$b', [])])
 
464
        self._run_check("<a$b  >", [('starttag', 'a$b', [])])
 
465
        self._run_check("<a$b  />", [('startendtag', 'a$b', [])])
 
466
 
 
467
    def test_slashes_in_starttag(self):
 
468
        self._run_check('<a foo="var"/>', [('startendtag', 'a', [('foo', 'var')])])
 
469
        html = ('<img width=902 height=250px '
 
470
                'src="/sites/default/files/images/homepage/foo.jpg" '
 
471
                '/*what am I doing here*/ />')
 
472
        expected = [(
 
473
            'startendtag', 'img',
 
474
            [('width', '902'), ('height', '250px'),
 
475
             ('src', '/sites/default/files/images/homepage/foo.jpg'),
 
476
             ('*what', None), ('am', None), ('i', None),
 
477
             ('doing', None), ('here*', None)]
 
478
        )]
 
479
        self._run_check(html, expected)
 
480
        html = ('<a / /foo/ / /=/ / /bar/ / />'
 
481
                '<a / /foo/ / /=/ / /bar/ / >')
 
482
        expected = [
 
483
            ('startendtag', 'a', [('foo', None), ('=', None), ('bar', None)]),
 
484
            ('starttag', 'a', [('foo', None), ('=', None), ('bar', None)])
 
485
        ]
 
486
        self._run_check(html, expected)
 
487
        #see issue #14538
 
488
        html = ('<meta><meta / ><meta // ><meta / / >'
 
489
                '<meta/><meta /><meta //><meta//>')
 
490
        expected = [
 
491
            ('starttag', 'meta', []), ('starttag', 'meta', []),
 
492
            ('starttag', 'meta', []), ('starttag', 'meta', []),
 
493
            ('startendtag', 'meta', []), ('startendtag', 'meta', []),
 
494
            ('startendtag', 'meta', []), ('startendtag', 'meta', []),
 
495
        ]
 
496
        self._run_check(html, expected)
 
497
 
 
498
    def test_declaration_junk_chars(self):
 
499
        self._run_check("<!DOCTYPE foo $ >", [('decl', 'DOCTYPE foo $ ')])
 
500
 
 
501
    def test_illegal_declarations(self):
 
502
        self._run_check('<!spacer type="block" height="25">',
 
503
                        [('comment', 'spacer type="block" height="25"')])
 
504
 
 
505
    def test_with_unquoted_attributes(self):
 
506
        # see #12008
 
507
        html = ("<html><body bgcolor=d0ca90 text='181008'>"
 
508
                "<table cellspacing=0 cellpadding=1 width=100% ><tr>"
 
509
                "<td align=left><font size=-1>"
 
510
                "- <a href=/rabota/><span class=en> software-and-i</span></a>"
 
511
                "- <a href='/1/'><span class=en> library</span></a></table>")
 
512
        expected = [
 
513
            ('starttag', 'html', []),
 
514
            ('starttag', 'body', [('bgcolor', 'd0ca90'), ('text', '181008')]),
 
515
            ('starttag', 'table',
 
516
                [('cellspacing', '0'), ('cellpadding', '1'), ('width', '100%')]),
 
517
            ('starttag', 'tr', []),
 
518
            ('starttag', 'td', [('align', 'left')]),
 
519
            ('starttag', 'font', [('size', '-1')]),
 
520
            ('data', '- '), ('starttag', 'a', [('href', '/rabota/')]),
 
521
            ('starttag', 'span', [('class', 'en')]), ('data', ' software-and-i'),
 
522
            ('endtag', 'span'), ('endtag', 'a'),
 
523
            ('data', '- '), ('starttag', 'a', [('href', '/1/')]),
 
524
            ('starttag', 'span', [('class', 'en')]), ('data', ' library'),
 
525
            ('endtag', 'span'), ('endtag', 'a'), ('endtag', 'table')
 
526
        ]
 
527
        self._run_check(html, expected)
 
528
 
 
529
    def test_comma_between_attributes(self):
 
530
        self._run_check('<form action="/xxx.php?a=1&amp;b=2&amp", '
 
531
                        'method="post">', [
 
532
                            ('starttag', 'form',
 
533
                                [('action', '/xxx.php?a=1&b=2&'),
 
534
                                 (',', None), ('method', 'post')])])
 
535
 
 
536
    def test_weird_chars_in_unquoted_attribute_values(self):
 
537
        self._run_check('<form action=bogus|&#()value>', [
 
538
                            ('starttag', 'form',
 
539
                                [('action', 'bogus|&#()value')])])
 
540
 
 
541
    def test_invalid_end_tags(self):
 
542
        # A collection of broken end tags. <br> is used as separator.
 
543
        # see http://www.w3.org/TR/html5/tokenization.html#end-tag-open-state
 
544
        # and #13993
 
545
        html = ('<br></label</p><br></div end tmAd-leaderBoard><br></<h4><br>'
 
546
                '</li class="unit"><br></li\r\n\t\t\t\t\t\t</ul><br></><br>')
 
547
        expected = [('starttag', 'br', []),
 
548
                    # < is part of the name, / is discarded, p is an attribute
 
549
                    ('endtag', 'label<'),
 
550
                    ('starttag', 'br', []),
 
551
                    # text and attributes are discarded
 
552
                    ('endtag', 'div'),
 
553
                    ('starttag', 'br', []),
 
554
                    # comment because the first char after </ is not a-zA-Z
 
555
                    ('comment', '<h4'),
 
556
                    ('starttag', 'br', []),
 
557
                    # attributes are discarded
 
558
                    ('endtag', 'li'),
 
559
                    ('starttag', 'br', []),
 
560
                    # everything till ul (included) is discarded
 
561
                    ('endtag', 'li'),
 
562
                    ('starttag', 'br', []),
 
563
                    # </> is ignored
 
564
                    ('starttag', 'br', [])]
 
565
        self._run_check(html, expected)
 
566
 
 
567
    def test_broken_invalid_end_tag(self):
 
568
        # This is technically wrong (the "> shouldn't be included in the 'data')
 
569
        # but is probably not worth fixing it (in addition to all the cases of
 
570
        # the previous test, it would require a full attribute parsing).
 
571
        # see #13993
 
572
        html = '<b>This</b attr=">"> confuses the parser'
 
573
        expected = [('starttag', 'b', []),
 
574
                    ('data', 'This'),
 
575
                    ('endtag', 'b'),
 
576
                    ('data', '"> confuses the parser')]
 
577
        self._run_check(html, expected)
 
578
 
 
579
    def test_correct_detection_of_start_tags(self):
 
580
        # see #13273
 
581
        html = ('<div style=""    ><b>The <a href="some_url">rain</a> '
 
582
                '<br /> in <span>Spain</span></b></div>')
 
583
        expected = [
 
584
            ('starttag', 'div', [('style', '')]),
 
585
            ('starttag', 'b', []),
 
586
            ('data', 'The '),
 
587
            ('starttag', 'a', [('href', 'some_url')]),
 
588
            ('data', 'rain'),
 
589
            ('endtag', 'a'),
 
590
            ('data', ' '),
 
591
            ('startendtag', 'br', []),
 
592
            ('data', ' in '),
 
593
            ('starttag', 'span', []),
 
594
            ('data', 'Spain'),
 
595
            ('endtag', 'span'),
 
596
            ('endtag', 'b'),
 
597
            ('endtag', 'div')
 
598
        ]
 
599
        self._run_check(html, expected)
 
600
 
 
601
        html = '<div style="", foo = "bar" ><b>The <a href="some_url">rain</a>'
 
602
        expected = [
 
603
            ('starttag', 'div', [('style', ''), (',', None), ('foo', 'bar')]),
 
604
            ('starttag', 'b', []),
 
605
            ('data', 'The '),
 
606
            ('starttag', 'a', [('href', 'some_url')]),
 
607
            ('data', 'rain'),
 
608
            ('endtag', 'a'),
 
609
        ]
 
610
        self._run_check(html, expected)
 
611
 
 
612
    def test_EOF_in_charref(self):
 
613
        # see #17802
 
614
        # This test checks that the UnboundLocalError reported in the issue
 
615
        # is not raised, however I'm not sure the returned values are correct.
 
616
        # Maybe HTMLParser should use self.unescape for these
 
617
        data = [
 
618
            ('a&', [('data', 'a&')]),
 
619
            ('a&b', [('data', 'ab')]),
 
620
            ('a&b ', [('data', 'a'), ('entityref', 'b'), ('data', ' ')]),
 
621
            ('a&b;', [('data', 'a'), ('entityref', 'b')]),
 
622
        ]
 
623
        for html, expected in data:
 
624
            self._run_check(html, expected)
 
625
 
 
626
    def test_unescape_method(self):
 
627
        from html import unescape
 
628
        p = self.get_collector()
 
629
        with self.assertWarns(DeprecationWarning):
 
630
            s = '&quot;&#34;&#x22;&quot&#34&#x22&#bad;'
 
631
            self.assertEqual(p.unescape(s), unescape(s))
 
632
 
 
633
    def test_broken_comments(self):
 
634
        html = ('<! not really a comment >'
 
635
                '<! not a comment either -->'
 
636
                '<! -- close enough -->'
 
637
                '<!><!<-- this was an empty comment>'
 
638
                '<!!! another bogus comment !!!>')
 
639
        expected = [
 
640
            ('comment', ' not really a comment '),
 
641
            ('comment', ' not a comment either --'),
 
642
            ('comment', ' -- close enough --'),
 
643
            ('comment', ''),
 
644
            ('comment', '<-- this was an empty comment'),
 
645
            ('comment', '!! another bogus comment !!!'),
 
646
        ]
 
647
        self._run_check(html, expected)
 
648
 
 
649
    def test_broken_condcoms(self):
 
650
        # these condcoms are missing the '--' after '<!' and before the '>'
 
651
        html = ('<![if !(IE)]>broken condcom<![endif]>'
 
652
                '<![if ! IE]><link href="favicon.tiff"/><![endif]>'
 
653
                '<![if !IE 6]><img src="firefox.png" /><![endif]>'
 
654
                '<![if !ie 6]><b>foo</b><![endif]>'
 
655
                '<![if (!IE)|(lt IE 9)]><img src="mammoth.bmp" /><![endif]>')
 
656
        # According to the HTML5 specs sections "8.2.4.44 Bogus comment state"
 
657
        # and "8.2.4.45 Markup declaration open state", comment tokens should
 
658
        # be emitted instead of 'unknown decl', but calling unknown_decl
 
659
        # provides more flexibility.
 
660
        # See also Lib/_markupbase.py:parse_declaration
 
661
        expected = [
 
662
            ('unknown decl', 'if !(IE)'),
 
663
            ('data', 'broken condcom'),
 
664
            ('unknown decl', 'endif'),
 
665
            ('unknown decl', 'if ! IE'),
 
666
            ('startendtag', 'link', [('href', 'favicon.tiff')]),
 
667
            ('unknown decl', 'endif'),
 
668
            ('unknown decl', 'if !IE 6'),
 
669
            ('startendtag', 'img', [('src', 'firefox.png')]),
 
670
            ('unknown decl', 'endif'),
 
671
            ('unknown decl', 'if !ie 6'),
 
672
            ('starttag', 'b', []),
 
673
            ('data', 'foo'),
 
674
            ('endtag', 'b'),
 
675
            ('unknown decl', 'endif'),
 
676
            ('unknown decl', 'if (!IE)|(lt IE 9)'),
 
677
            ('startendtag', 'img', [('src', 'mammoth.bmp')]),
 
678
            ('unknown decl', 'endif')
 
679
        ]
 
680
        self._run_check(html, expected)
 
681
 
 
682
 
 
683
class AttributesStrictTestCase(TestCaseBase):
 
684
 
 
685
    def get_collector(self):
 
686
        with support.check_warnings(("", DeprecationWarning), quite=False):
 
687
            return EventCollector(strict=True, convert_charrefs=False)
 
688
 
 
689
    def test_attr_syntax(self):
 
690
        output = [
 
691
          ("starttag", "a", [("b", "v"), ("c", "v"), ("d", "v"), ("e", None)])
 
692
        ]
 
693
        self._run_check("""<a b='v' c="v" d=v e>""", output)
 
694
        self._run_check("""<a  b = 'v' c = "v" d = v e>""", output)
 
695
        self._run_check("""<a\nb\n=\n'v'\nc\n=\n"v"\nd\n=\nv\ne>""", output)
 
696
        self._run_check("""<a\tb\t=\t'v'\tc\t=\t"v"\td\t=\tv\te>""", output)
 
697
 
 
698
    def test_attr_values(self):
 
699
        self._run_check("""<a b='xxx\n\txxx' c="yyy\t\nyyy" d='\txyz\n'>""",
 
700
                        [("starttag", "a", [("b", "xxx\n\txxx"),
 
701
                                            ("c", "yyy\t\nyyy"),
 
702
                                            ("d", "\txyz\n")])])
 
703
        self._run_check("""<a b='' c="">""",
 
704
                        [("starttag", "a", [("b", ""), ("c", "")])])
 
705
        # Regression test for SF patch #669683.
 
706
        self._run_check("<e a=rgb(1,2,3)>",
 
707
                        [("starttag", "e", [("a", "rgb(1,2,3)")])])
 
708
        # Regression test for SF bug #921657.
 
709
        self._run_check(
 
710
            "<a href=mailto:xyz@example.com>",
 
711
            [("starttag", "a", [("href", "mailto:xyz@example.com")])])
 
712
 
 
713
    def test_attr_nonascii(self):
 
714
        # see issue 7311
 
715
        self._run_check(
 
716
            "<img src=/foo/bar.png alt=\u4e2d\u6587>",
 
717
            [("starttag", "img", [("src", "/foo/bar.png"),
 
718
                                  ("alt", "\u4e2d\u6587")])])
 
719
        self._run_check(
 
720
            "<a title='\u30c6\u30b9\u30c8' href='\u30c6\u30b9\u30c8.html'>",
 
721
            [("starttag", "a", [("title", "\u30c6\u30b9\u30c8"),
 
722
                                ("href", "\u30c6\u30b9\u30c8.html")])])
 
723
        self._run_check(
 
724
            '<a title="\u30c6\u30b9\u30c8" href="\u30c6\u30b9\u30c8.html">',
 
725
            [("starttag", "a", [("title", "\u30c6\u30b9\u30c8"),
 
726
                                ("href", "\u30c6\u30b9\u30c8.html")])])
 
727
 
 
728
    def test_attr_entity_replacement(self):
 
729
        self._run_check(
 
730
            "<a b='&amp;&gt;&lt;&quot;&apos;'>",
 
731
            [("starttag", "a", [("b", "&><\"'")])])
 
732
 
 
733
    def test_attr_funky_names(self):
 
734
        self._run_check(
 
735
            "<a a.b='v' c:d=v e-f=v>",
 
736
            [("starttag", "a", [("a.b", "v"), ("c:d", "v"), ("e-f", "v")])])
 
737
 
 
738
    def test_entityrefs_in_attributes(self):
 
739
        self._run_check(
 
740
            "<html foo='&euro;&amp;&#97;&#x61;&unsupported;'>",
 
741
            [("starttag", "html", [("foo", "\u20AC&aa&unsupported;")])])
 
742
 
 
743
 
 
744
 
 
745
class AttributesTolerantTestCase(AttributesStrictTestCase):
 
746
 
 
747
    def get_collector(self):
 
748
        return EventCollector(convert_charrefs=False)
 
749
 
 
750
    def test_attr_funky_names2(self):
 
751
        self._run_check(
 
752
            "<a $><b $=%><c \=/>",
 
753
            [("starttag", "a", [("$", None)]),
 
754
             ("starttag", "b", [("$", "%")]),
 
755
             ("starttag", "c", [("\\", "/")])])
 
756
 
 
757
    def test_entities_in_attribute_value(self):
 
758
        # see #1200313
 
759
        for entity in ['&', '&amp;', '&#38;', '&#x26;']:
 
760
            self._run_check('<a href="%s">' % entity,
 
761
                            [("starttag", "a", [("href", "&")])])
 
762
            self._run_check("<a href='%s'>" % entity,
 
763
                            [("starttag", "a", [("href", "&")])])
 
764
            self._run_check("<a href=%s>" % entity,
 
765
                            [("starttag", "a", [("href", "&")])])
 
766
 
 
767
    def test_malformed_attributes(self):
 
768
        # see #13357
 
769
        html = (
 
770
            "<a href=test'style='color:red;bad1'>test - bad1</a>"
 
771
            "<a href=test'+style='color:red;ba2'>test - bad2</a>"
 
772
            "<a href=test'&nbsp;style='color:red;bad3'>test - bad3</a>"
 
773
            "<a href = test'&nbsp;style='color:red;bad4'  >test - bad4</a>"
 
774
        )
 
775
        expected = [
 
776
            ('starttag', 'a', [('href', "test'style='color:red;bad1'")]),
 
777
            ('data', 'test - bad1'), ('endtag', 'a'),
 
778
            ('starttag', 'a', [('href', "test'+style='color:red;ba2'")]),
 
779
            ('data', 'test - bad2'), ('endtag', 'a'),
 
780
            ('starttag', 'a', [('href', "test'\xa0style='color:red;bad3'")]),
 
781
            ('data', 'test - bad3'), ('endtag', 'a'),
 
782
            ('starttag', 'a', [('href', "test'\xa0style='color:red;bad4'")]),
 
783
            ('data', 'test - bad4'), ('endtag', 'a')
 
784
        ]
 
785
        self._run_check(html, expected)
 
786
 
 
787
    def test_malformed_adjacent_attributes(self):
 
788
        # see #12629
 
789
        self._run_check('<x><y z=""o"" /></x>',
 
790
                        [('starttag', 'x', []),
 
791
                            ('startendtag', 'y', [('z', ''), ('o""', None)]),
 
792
                            ('endtag', 'x')])
 
793
        self._run_check('<x><y z="""" /></x>',
 
794
                        [('starttag', 'x', []),
 
795
                            ('startendtag', 'y', [('z', ''), ('""', None)]),
 
796
                            ('endtag', 'x')])
 
797
 
 
798
    # see #755670 for the following 3 tests
 
799
    def test_adjacent_attributes(self):
 
800
        self._run_check('<a width="100%"cellspacing=0>',
 
801
                        [("starttag", "a",
 
802
                          [("width", "100%"), ("cellspacing","0")])])
 
803
 
 
804
        self._run_check('<a id="foo"class="bar">',
 
805
                        [("starttag", "a",
 
806
                          [("id", "foo"), ("class","bar")])])
 
807
 
 
808
    def test_missing_attribute_value(self):
 
809
        self._run_check('<a v=>',
 
810
                        [("starttag", "a", [("v", "")])])
 
811
 
 
812
    def test_javascript_attribute_value(self):
 
813
        self._run_check("<a href=javascript:popup('/popup/help.html')>",
 
814
                        [("starttag", "a",
 
815
                          [("href", "javascript:popup('/popup/help.html')")])])
 
816
 
 
817
    def test_end_tag_in_attribute_value(self):
 
818
        # see #1745761
 
819
        self._run_check("<a href='http://www.example.org/\">;'>spam</a>",
 
820
                        [("starttag", "a",
 
821
                          [("href", "http://www.example.org/\">;")]),
 
822
                         ("data", "spam"), ("endtag", "a")])
 
823
 
 
824
 
 
825
if __name__ == "__main__":
 
826
    unittest.main()