mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Update html module from Python 3.14.2 (#6855)
This commit is contained in:
1 parent
f8d4d991f1
commit
185f360fea
2 files changed
+246
-134
No files matched your search
Vendored
+37
-16
@@ -24,6 +24,7 @@ incomplete = re.compile('&[a-zA-Z#]')
|
||||
|
||||
entityref = re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]')
|
||||
charref = re.compile('&#(?:[0-9]+|[xX][0-9a-fA-F]+)[^0-9a-fA-F]')
|
||||
incomplete_charref = re.compile('&#(?:[0-9]|[xX][0-9a-fA-F])')
|
||||
attr_charref = re.compile(r'&(#[0-9]+|#[xX][0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*)[;=]?')
|
||||
|
||||
starttagopen = re.compile('<[a-zA-Z]')
|
||||
@@ -127,17 +128,25 @@ class HTMLParser(_markupbase.ParserBase):
|
||||
argument.
|
||||
"""
|
||||
|
||||
CDATA_CONTENT_ELEMENTS = ("script", "style")
|
||||
# See the HTML5 specs section "13.4 Parsing HTML fragments".
|
||||
# https://html.spec.whatwg.org/multipage/parsing.html#parsing-html-fragments
|
||||
# CDATA_CONTENT_ELEMENTS are parsed in RAWTEXT mode
|
||||
CDATA_CONTENT_ELEMENTS = ("script", "style", "xmp", "iframe", "noembed", "noframes")
|
||||
RCDATA_CONTENT_ELEMENTS = ("textarea", "title")
|
||||
|
||||
def __init__(self, *, convert_charrefs=True):
|
||||
def __init__(self, *, convert_charrefs=True, scripting=False):
|
||||
"""Initialize and reset this instance.
|
||||
|
||||
If convert_charrefs is True (the default), all character references
|
||||
If convert_charrefs is true (the default), all character references
|
||||
are automatically converted to the corresponding Unicode characters.
|
||||
|
||||
If *scripting* is false (the default), the content of the
|
||||
``noscript`` element is parsed normally; if it's true,
|
||||
it's returned as is without being parsed.
|
||||
"""
|
||||
super().__init__()
|
||||
self.convert_charrefs = convert_charrefs
|
||||
self.scripting = scripting
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
@@ -172,7 +181,9 @@ class HTMLParser(_markupbase.ParserBase):
|
||||
def set_cdata_mode(self, elem, *, escapable=False):
|
||||
self.cdata_elem = elem.lower()
|
||||
self._escapable = escapable
|
||||
if escapable and not self.convert_charrefs:
|
||||
if self.cdata_elem == 'plaintext':
|
||||
self.interesting = re.compile(r'\z')
|
||||
elif escapable and not self.convert_charrefs:
|
||||
self.interesting = re.compile(r'&|</%s(?=[\t\n\r\f />])' % self.cdata_elem,
|
||||
re.IGNORECASE|re.ASCII)
|
||||
else:
|
||||
@@ -294,10 +305,20 @@ class HTMLParser(_markupbase.ParserBase):
|
||||
k = k - 1
|
||||
i = self.updatepos(i, k)
|
||||
continue
|
||||
match = incomplete_charref.match(rawdata, i)
|
||||
if match:
|
||||
if end:
|
||||
self.handle_charref(rawdata[i+2:])
|
||||
i = self.updatepos(i, n)
|
||||
break
|
||||
# incomplete
|
||||
break
|
||||
elif i + 3 < n: # larger than "&#x"
|
||||
# not the end of the buffer, and can't be confused
|
||||
# with some other construct
|
||||
self.handle_data("&#")
|
||||
i = self.updatepos(i, i + 2)
|
||||
else:
|
||||
if ";" in rawdata[i:]: # bail by consuming &#
|
||||
self.handle_data(rawdata[i:i+2])
|
||||
i = self.updatepos(i, i+2)
|
||||
break
|
||||
elif startswith('&', i):
|
||||
match = entityref.match(rawdata, i)
|
||||
@@ -311,15 +332,13 @@ class HTMLParser(_markupbase.ParserBase):
|
||||
continue
|
||||
match = incomplete.match(rawdata, i)
|
||||
if match:
|
||||
# match.group() will contain at least 2 chars
|
||||
if end and match.group() == rawdata[i:]:
|
||||
k = match.end()
|
||||
if k <= i:
|
||||
k = n
|
||||
i = self.updatepos(i, i + 1)
|
||||
if end:
|
||||
self.handle_entityref(rawdata[i+1:])
|
||||
i = self.updatepos(i, n)
|
||||
break
|
||||
# incomplete
|
||||
break
|
||||
elif (i + 1) < n:
|
||||
elif i + 1 < n:
|
||||
# not the end of the buffer, and can't be confused
|
||||
# with some other construct
|
||||
self.handle_data("&")
|
||||
@@ -444,8 +463,10 @@ class HTMLParser(_markupbase.ParserBase):
|
||||
self.handle_startendtag(tag, attrs)
|
||||
else:
|
||||
self.handle_starttag(tag, attrs)
|
||||
if tag in self.CDATA_CONTENT_ELEMENTS:
|
||||
self.set_cdata_mode(tag)
|
||||
if (tag in self.CDATA_CONTENT_ELEMENTS or
|
||||
(self.scripting and tag == "noscript") or
|
||||
tag == "plaintext"):
|
||||
self.set_cdata_mode(tag, escapable=False)
|
||||
elif tag in self.RCDATA_CONTENT_ELEMENTS:
|
||||
self.set_cdata_mode(tag, escapable=True)
|
||||
return endpos
|
||||
|
||||
Vendored
+209
-118
@@ -8,6 +8,18 @@ from unittest.mock import patch
|
||||
from test import support
|
||||
|
||||
|
||||
SAMPLE_RCDATA = (
|
||||
'<!-- not a comment -->'
|
||||
"<not a='start tag'>"
|
||||
'<![CDATA[not a cdata]]>'
|
||||
'<!not a bogus comment>'
|
||||
'</not a bogus comment>'
|
||||
'\u2603'
|
||||
)
|
||||
|
||||
SAMPLE_RAWTEXT = SAMPLE_RCDATA + '&☺'
|
||||
|
||||
|
||||
class EventCollector(html.parser.HTMLParser):
|
||||
|
||||
def __init__(self, *args, autocdata=False, **kw):
|
||||
@@ -97,12 +109,13 @@ class EventCollectorNoNormalize(EventCollector):
|
||||
|
||||
class TestCaseBase(unittest.TestCase):
|
||||
|
||||
def get_collector(self):
|
||||
return EventCollector(convert_charrefs=False)
|
||||
def get_collector(self, convert_charrefs=False):
|
||||
return EventCollector(convert_charrefs=convert_charrefs)
|
||||
|
||||
def _run_check(self, source, expected_events, collector=None):
|
||||
def _run_check(self, source, expected_events,
|
||||
*, collector=None, convert_charrefs=False):
|
||||
if collector is None:
|
||||
collector = self.get_collector()
|
||||
collector = self.get_collector(convert_charrefs=convert_charrefs)
|
||||
parser = collector
|
||||
for s in source:
|
||||
parser.feed(s)
|
||||
@@ -116,7 +129,7 @@ class TestCaseBase(unittest.TestCase):
|
||||
|
||||
def _run_check_extra(self, source, events):
|
||||
self._run_check(source, events,
|
||||
EventCollectorExtra(convert_charrefs=False))
|
||||
collector=EventCollectorExtra(convert_charrefs=False))
|
||||
|
||||
|
||||
class HTMLParserTestCase(TestCaseBase):
|
||||
@@ -175,10 +188,87 @@ text
|
||||
])
|
||||
|
||||
def test_unclosed_entityref(self):
|
||||
self._run_check("&entityref foo", [
|
||||
("entityref", "entityref"),
|
||||
("data", " foo"),
|
||||
])
|
||||
self._run_check('> <', [('entityref', 'gt'), ('data', ' '), ('entityref', 'lt')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('> <', [('data', '> <')], convert_charrefs=True)
|
||||
|
||||
self._run_check('&undefined <',
|
||||
[('entityref', 'undefined'), ('data', ' '), ('entityref', 'lt')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('&undefined <', [('data', '&undefined <')],
|
||||
convert_charrefs=True)
|
||||
|
||||
self._run_check('>undefined <',
|
||||
[('entityref', 'gtundefined'), ('data', ' '), ('entityref', 'lt')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('>undefined <', [('data', '>undefined <')],
|
||||
convert_charrefs=True)
|
||||
|
||||
self._run_check('& <', [('data', '& '), ('entityref', 'lt')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('& <', [('data', '& <')], convert_charrefs=True)
|
||||
|
||||
def test_eof_in_entityref(self):
|
||||
self._run_check('>', [('entityref', 'gt')], convert_charrefs=False)
|
||||
self._run_check('>', [('data', '>')], convert_charrefs=True)
|
||||
|
||||
self._run_check('&g', [('entityref', 'g')], convert_charrefs=False)
|
||||
self._run_check('&g', [('data', '&g')], convert_charrefs=True)
|
||||
|
||||
self._run_check('&undefined', [('entityref', 'undefined')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('&undefined', [('data', '&undefined')],
|
||||
convert_charrefs=True)
|
||||
|
||||
self._run_check('>undefined', [('entityref', 'gtundefined')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('>undefined', [('data', '>undefined')],
|
||||
convert_charrefs=True)
|
||||
|
||||
self._run_check('&', [('data', '&')], convert_charrefs=False)
|
||||
self._run_check('&', [('data', '&')], convert_charrefs=True)
|
||||
|
||||
def test_unclosed_charref(self):
|
||||
self._run_check('{ <', [('charref', '123'), ('data', ' '), ('entityref', 'lt')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('{ <', [('data', '{ <')], convert_charrefs=True)
|
||||
self._run_check('« <', [('charref', 'xab'), ('data', ' '), ('entityref', 'lt')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('« <', [('data', '\xab <')], convert_charrefs=True)
|
||||
|
||||
self._run_check('� <',
|
||||
[('charref', '123456789'), ('data', ' '), ('entityref', 'lt')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('� <', [('data', '\ufffd <')],
|
||||
convert_charrefs=True)
|
||||
self._run_check('� <',
|
||||
[('charref', 'x123456789'), ('data', ' '), ('entityref', 'lt')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('� <', [('data', '\ufffd <')],
|
||||
convert_charrefs=True)
|
||||
|
||||
self._run_check('&# <', [('data', '&# '), ('entityref', 'lt')], convert_charrefs=False)
|
||||
self._run_check('&# <', [('data', '&# <')], convert_charrefs=True)
|
||||
self._run_check('&#x <', [('data', '&#x '), ('entityref', 'lt')], convert_charrefs=False)
|
||||
self._run_check('&#x <', [('data', '&#x <')], convert_charrefs=True)
|
||||
|
||||
def test_eof_in_charref(self):
|
||||
self._run_check('{', [('charref', '123')], convert_charrefs=False)
|
||||
self._run_check('{', [('data', '{')], convert_charrefs=True)
|
||||
self._run_check('«', [('charref', 'xab')], convert_charrefs=False)
|
||||
self._run_check('«', [('data', '\xab')], convert_charrefs=True)
|
||||
|
||||
self._run_check('�', [('charref', '123456789')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('�', [('data', '\ufffd')], convert_charrefs=True)
|
||||
self._run_check('�', [('charref', 'x123456789')],
|
||||
convert_charrefs=False)
|
||||
self._run_check('�', [('data', '\ufffd')], convert_charrefs=True)
|
||||
|
||||
self._run_check('&#', [('data', '&#')], convert_charrefs=False)
|
||||
self._run_check('&#', [('data', '&#')], convert_charrefs=True)
|
||||
self._run_check('&#x', [('data', '&#x')], convert_charrefs=False)
|
||||
self._run_check('&#x', [('data', '&#x')], convert_charrefs=True)
|
||||
|
||||
def test_bad_nesting(self):
|
||||
# Strangely, this *is* supposed to test that overlapping
|
||||
@@ -293,30 +383,20 @@ text
|
||||
'Date().getTime()+\'"><\\/s\'+\'cript>\');\n//]]>'),
|
||||
'\n<!-- //\nvar foo = 3.14;\n// -->\n',
|
||||
'<!-- \u2603 -->',
|
||||
'foo = "</ script>"',
|
||||
'foo = "</scripture>"',
|
||||
'foo = "</script\v>"',
|
||||
'foo = "</script\xa0>"',
|
||||
'foo = "</ſcript>"',
|
||||
'foo = "</scrıpt>"',
|
||||
])
|
||||
def test_script_content(self, content):
|
||||
s = f'<script>{content}</script>'
|
||||
self._run_check(s, [("starttag", "script", []),
|
||||
("data", content),
|
||||
("endtag", "script")])
|
||||
self._run_check(s, [
|
||||
("starttag", "script", []),
|
||||
("data", content),
|
||||
("endtag", "script"),
|
||||
])
|
||||
|
||||
@support.subTests('content', [
|
||||
'a::before { content: "<!-- not a comment -->"; }',
|
||||
'a::before { content: "¬-an-entity-ref;"; }',
|
||||
'a::before { content: "<not a=\'start tag\'>"; }',
|
||||
'a::before { content: "\u2603"; }',
|
||||
'a::before { content: "< /style>"; }',
|
||||
'a::before { content: "</ style>"; }',
|
||||
'a::before { content: "</styled>"; }',
|
||||
'a::before { content: "</style\v>"; }',
|
||||
'a::before { content: "</style\xa0>"; }',
|
||||
'a::before { content: "</ſtyle>"; }',
|
||||
])
|
||||
def test_style_content(self, content):
|
||||
s = f'<style>{content}</style>'
|
||||
@@ -324,47 +404,59 @@ text
|
||||
("data", content),
|
||||
("endtag", "style")])
|
||||
|
||||
@support.subTests('content', [
|
||||
'<!-- not a comment -->',
|
||||
"<not a='start tag'>",
|
||||
'<![CDATA[not a cdata]]>',
|
||||
'<!not a bogus comment>',
|
||||
'</not a bogus comment>',
|
||||
'\u2603',
|
||||
'< /title>',
|
||||
'</ title>',
|
||||
'</titled>',
|
||||
'</title\v>',
|
||||
'</title\xa0>',
|
||||
'</tıtle>',
|
||||
])
|
||||
def test_title_content(self, content):
|
||||
source = f"<title>{content}</title>"
|
||||
@support.subTests('tag', ['title', 'textarea'])
|
||||
def test_rcdata_content(self, tag):
|
||||
source = f"<{tag}>{SAMPLE_RCDATA}</{tag}>"
|
||||
self._run_check(source, [
|
||||
("starttag", "title", []),
|
||||
("data", content),
|
||||
("endtag", "title"),
|
||||
("starttag", tag, []),
|
||||
("data", SAMPLE_RCDATA),
|
||||
("endtag", tag),
|
||||
])
|
||||
source = f"<{tag}>&</{tag}>"
|
||||
self._run_check(source, [
|
||||
("starttag", tag, []),
|
||||
('entityref', 'amp'),
|
||||
("endtag", tag),
|
||||
])
|
||||
|
||||
@support.subTests('content', [
|
||||
'<!-- not a comment -->',
|
||||
"<not a='start tag'>",
|
||||
'<![CDATA[not a cdata]]>',
|
||||
'<!not a bogus comment>',
|
||||
'</not a bogus comment>',
|
||||
'\u2603',
|
||||
'< /textarea>',
|
||||
'</ textarea>',
|
||||
'</textareable>',
|
||||
'</textarea\v>',
|
||||
'</textarea\xa0>',
|
||||
])
|
||||
def test_textarea_content(self, content):
|
||||
source = f"<textarea>{content}</textarea>"
|
||||
@support.subTests('tag',
|
||||
['style', 'xmp', 'iframe', 'noembed', 'noframes', 'script'])
|
||||
def test_rawtext_content(self, tag):
|
||||
source = f"<{tag}>{SAMPLE_RAWTEXT}</{tag}>"
|
||||
self._run_check(source, [
|
||||
("starttag", "textarea", []),
|
||||
("starttag", tag, []),
|
||||
("data", SAMPLE_RAWTEXT),
|
||||
("endtag", tag),
|
||||
])
|
||||
|
||||
def test_noscript_content(self):
|
||||
source = f"<noscript>{SAMPLE_RAWTEXT}</noscript>"
|
||||
# scripting=False -- normal mode
|
||||
self._run_check(source, [
|
||||
('starttag', 'noscript', []),
|
||||
('comment', ' not a comment '),
|
||||
('starttag', 'not', [('a', 'start tag')]),
|
||||
('unknown decl', 'CDATA[not a cdata'),
|
||||
('comment', 'not a bogus comment'),
|
||||
('endtag', 'not'),
|
||||
('data', '☃'),
|
||||
('entityref', 'amp'),
|
||||
('charref', '9786'),
|
||||
('endtag', 'noscript'),
|
||||
])
|
||||
# scripting=True -- RAWTEXT mode
|
||||
self._run_check(source, [
|
||||
("starttag", "noscript", []),
|
||||
("data", SAMPLE_RAWTEXT),
|
||||
("endtag", "noscript"),
|
||||
], collector=EventCollector(scripting=True))
|
||||
|
||||
def test_plaintext_content(self):
|
||||
content = SAMPLE_RAWTEXT + '</plaintext>' # not closing
|
||||
source = f"<plaintext>{content}"
|
||||
self._run_check(source, [
|
||||
("starttag", "plaintext", []),
|
||||
("data", content),
|
||||
("endtag", "textarea"),
|
||||
])
|
||||
|
||||
@support.subTests('endtag', ['script', 'SCRIPT', 'script ', 'script\n',
|
||||
@@ -381,52 +473,65 @@ text
|
||||
("endtag", "script")],
|
||||
collector=EventCollectorNoNormalize(convert_charrefs=False))
|
||||
|
||||
@support.subTests('endtag', ['style', 'STYLE', 'style ', 'style\n',
|
||||
'style/', 'style foo=bar', 'style foo=">"'])
|
||||
def test_style_closing_tag(self, endtag):
|
||||
content = """
|
||||
b::before { content: "<!-- not a comment -->"; }
|
||||
p::before { content: "¬-an-entity-ref;"; }
|
||||
a::before { content: "<i>"; }
|
||||
a::after { content: "</i>"; }
|
||||
"""
|
||||
s = f'<StyLE>{content}</{endtag}>'
|
||||
self._run_check(s, [("starttag", "style", []),
|
||||
("data", content),
|
||||
("endtag", "style")],
|
||||
collector=EventCollectorNoNormalize(convert_charrefs=False))
|
||||
@support.subTests('tag', [
|
||||
'script', 'style', 'xmp', 'iframe', 'noembed', 'noframes',
|
||||
'textarea', 'title', 'noscript',
|
||||
])
|
||||
def test_closing_tag(self, tag):
|
||||
for endtag in [tag, tag.upper(), f'{tag} ', f'{tag}\n',
|
||||
f'{tag}/', f'{tag} foo=bar', f'{tag} foo=">"']:
|
||||
content = "<!-- not a comment --><i>Spam</i>"
|
||||
s = f'<{tag.upper()}>{content}</{endtag}>'
|
||||
self._run_check(s, [
|
||||
("starttag", tag, []),
|
||||
('data', content),
|
||||
("endtag", tag),
|
||||
], collector=EventCollectorNoNormalize(convert_charrefs=False, scripting=True))
|
||||
|
||||
@support.subTests('endtag', ['title', 'TITLE', 'title ', 'title\n',
|
||||
'title/', 'title foo=bar', 'title foo=">"'])
|
||||
def test_title_closing_tag(self, endtag):
|
||||
content = "<!-- not a comment --><i>Egg & Spam</i>"
|
||||
s = f'<TitLe>{content}</{endtag}>'
|
||||
self._run_check(s, [("starttag", "title", []),
|
||||
('data', '<!-- not a comment --><i>Egg & Spam</i>'),
|
||||
("endtag", "title")],
|
||||
collector=EventCollectorNoNormalize(convert_charrefs=True))
|
||||
self._run_check(s, [("starttag", "title", []),
|
||||
('data', '<!-- not a comment --><i>Egg '),
|
||||
('entityref', 'amp'),
|
||||
('data', ' Spam</i>'),
|
||||
("endtag", "title")],
|
||||
collector=EventCollectorNoNormalize(convert_charrefs=False))
|
||||
@support.subTests('tag', [
|
||||
'script', 'style', 'xmp', 'iframe', 'noembed', 'noframes',
|
||||
'textarea', 'title', 'noscript',
|
||||
])
|
||||
def test_invalid_closing_tag(self, tag):
|
||||
content = (
|
||||
f'< /{tag}>'
|
||||
f'</ {tag}>'
|
||||
f'</{tag}x>'
|
||||
f'</{tag}\v>'
|
||||
f'</{tag}\xa0>'
|
||||
)
|
||||
source = f"<{tag}>{content}</{tag}>"
|
||||
self._run_check(source, [
|
||||
("starttag", tag, []),
|
||||
("data", content),
|
||||
("endtag", tag),
|
||||
], collector=EventCollector(convert_charrefs=False, scripting=True))
|
||||
|
||||
@support.subTests('endtag', ['textarea', 'TEXTAREA', 'textarea ', 'textarea\n',
|
||||
'textarea/', 'textarea foo=bar', 'textarea foo=">"'])
|
||||
def test_textarea_closing_tag(self, endtag):
|
||||
content = "<!-- not a comment --><i>Egg & Spam</i>"
|
||||
s = f'<TexTarEa>{content}</{endtag}>'
|
||||
self._run_check(s, [("starttag", "textarea", []),
|
||||
('data', '<!-- not a comment --><i>Egg & Spam</i>'),
|
||||
("endtag", "textarea")],
|
||||
collector=EventCollectorNoNormalize(convert_charrefs=True))
|
||||
self._run_check(s, [("starttag", "textarea", []),
|
||||
('data', '<!-- not a comment --><i>Egg '),
|
||||
('entityref', 'amp'),
|
||||
('data', ' Spam</i>'),
|
||||
("endtag", "textarea")],
|
||||
collector=EventCollectorNoNormalize(convert_charrefs=False))
|
||||
@support.subTests('tag,endtag', [
|
||||
('title', 'tıtle'),
|
||||
('style', 'ſtyle'),
|
||||
('style', 'ſtyle'),
|
||||
('style', 'style'),
|
||||
('iframe', 'ıframe'),
|
||||
('noframes', 'noframeſ'),
|
||||
('noscript', 'noſcript'),
|
||||
('noscript', 'noscrıpt'),
|
||||
('script', 'ſcript'),
|
||||
('script', 'scrıpt'),
|
||||
])
|
||||
def test_invalid_nonascii_closing_tag(self, tag, endtag):
|
||||
content = f"<br></{endtag}>"
|
||||
source = f"<{tag}>{content}"
|
||||
self._run_check(source, [
|
||||
("starttag", tag, []),
|
||||
("data", content),
|
||||
], collector=EventCollector(convert_charrefs=False, scripting=True))
|
||||
source = f"<{tag}>{content}</{tag}>"
|
||||
self._run_check(source, [
|
||||
("starttag", tag, []),
|
||||
("data", content),
|
||||
("endtag", tag),
|
||||
], collector=EventCollector(convert_charrefs=False, scripting=True))
|
||||
|
||||
@support.subTests('tail,end', [
|
||||
('', False),
|
||||
@@ -735,20 +840,6 @@ text
|
||||
]
|
||||
self._run_check(html, expected)
|
||||
|
||||
def test_EOF_in_charref(self):
|
||||
# see #17802
|
||||
# This test checks that the UnboundLocalError reported in the issue
|
||||
# is not raised, however I'm not sure the returned values are correct.
|
||||
# Maybe HTMLParser should use self.unescape for these
|
||||
data = [
|
||||
('a&', [('data', 'a&')]),
|
||||
('a&b', [('data', 'ab')]),
|
||||
('a&b ', [('data', 'a'), ('entityref', 'b'), ('data', ' ')]),
|
||||
('a&b;', [('data', 'a'), ('entityref', 'b')]),
|
||||
]
|
||||
for html, expected in data:
|
||||
self._run_check(html, expected)
|
||||
|
||||
def test_eof_in_comments(self):
|
||||
data = [
|
||||
('<!--', [('comment', '')]),
|
||||
|
||||
Reference in new issue
Block a user