diff --git a/requirements/test/pyproject.toml b/requirements/test/pyproject.toml
index 9f0a41b..74a8d3a 100644
--- a/requirements/test/pyproject.toml
+++ b/requirements/test/pyproject.toml
@@ -6,7 +6,6 @@ name = "dependencies"
requires-python = ">=3.10"
dependencies = [
"coverage[toml]",
- "pyfakefs",
"pytest",
"pytest-randomly",
]
diff --git a/requirements/test/requirements.txt b/requirements/test/requirements.txt
index dded8e8..609b1b8 100644
--- a/requirements/test/requirements.txt
+++ b/requirements/test/requirements.txt
@@ -4,7 +4,6 @@ exceptiongroup==1.3.1 ; python_version == "3.10"
iniconfig==2.3.0 ; python_version >= "3.10"
packaging==26.2 ; python_version >= "3.10"
pluggy==1.6.0 ; python_version >= "3.10"
-pyfakefs==6.2.0 ; python_version >= "3.10"
pygments==2.20.0 ; python_version >= "3.10"
pytest-randomly==4.1.0 ; python_version >= "3.10"
pytest==9.1.1 ; python_version >= "3.10"
diff --git a/src/feedparser/sgmllib/__init__.py b/src/feedparser/sgmllib/__init__.py
index e4988ac..1febf15 100644
--- a/src/feedparser/sgmllib/__init__.py
+++ b/src/feedparser/sgmllib/__init__.py
@@ -100,11 +100,11 @@ def feed(self, data: str) -> None:
"""
self.rawdata = self.rawdata + data
- self.goahead(0)
+ self.goahead(False)
def close(self) -> None:
"""Handle the remaining data."""
- self.goahead(1)
+ self.goahead(True)
def error(self, message: str) -> t.NoReturn:
raise SGMLParseError(message)
@@ -112,7 +112,7 @@ def error(self, message: str) -> t.NoReturn:
# Internal -- handle data as far as reasonable. May leave state
# and data to be processed by a subsequent call. If 'end' is
# true, force handling all data as if followed by EOF marker.
- def goahead(self, end: int) -> None:
+ def goahead(self, end: bool) -> None:
rawdata = self.rawdata
i = 0
n = len(rawdata)
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..97dea4b
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,130 @@
+import re
+
+import pytest
+
+import feedparser.sgmllib as sgmllib
+
+
+@pytest.fixture
+def check_parse_error():
+ def checker(source: str):
+ parser = EventCollector()
+ with pytest.raises(sgmllib.SGMLParseError):
+ parser.feed(source)
+ parser.close()
+
+ yield checker
+
+
+@pytest.fixture
+def event_collector():
+ return EventCollector()
+
+
+@pytest.fixture
+def cdata_event_collector():
+ return CDATAEventCollector()
+
+
+@pytest.fixture
+def html_entity_collector():
+ return HTMLEntityCollector()
+
+
+class EventCollector(sgmllib.SGMLParser):
+ def check_events(self, source, expected_events):
+ self._consume_source(source)
+ self._normalize_events()
+ assert self.events == expected_events
+
+ def _normalize_events(self):
+ # Normalize the list of events so that buffer artifacts don't
+ # separate runs of contiguous characters.
+ normalized_events = []
+ previous_type = None
+ for event in self.events:
+ current_type = event[0]
+ if current_type == previous_type == "data":
+ normalized_events[-1] = ("data", normalized_events[-1][1] + event[1])
+ else:
+ normalized_events.append(event)
+ previous_type = current_type
+ self.events = normalized_events
+
+ def _consume_source(self, source):
+ for s in source:
+ self.feed(s)
+ self.close()
+
+ def __init__(self) -> None:
+ self.events = []
+ super().__init__()
+
+ # structure markup
+
+ def unknown_starttag(self, tag, attrs):
+ self.events.append(("starttag", tag, attrs))
+
+ def unknown_endtag(self, tag):
+ self.events.append(("endtag", tag))
+
+ # all other markup
+
+ def handle_comment(self, data):
+ self.events.append(("comment", data))
+
+ def handle_charref(self, name):
+ self.events.append(("charref", name))
+
+ def handle_data(self, data):
+ self.events.append(("data", data))
+
+ def handle_decl(self, decl):
+ self.events.append(("decl", decl))
+
+ def handle_entityref(self, name):
+ self.events.append(("entityref", name))
+
+ def handle_pi(self, data):
+ self.events.append(("pi", data))
+
+ def unknown_decl(self, data):
+ self.events.append(("unknown decl", data))
+
+
+class CDATAEventCollector(EventCollector):
+ def start_cdata(self, attrs):
+ self.events.append(("starttag", "cdata", attrs))
+ self.setliteral()
+
+
+class HTMLEntityCollector(EventCollector):
+
+ entity_or_charref = re.compile(
+ "(?:&([a-zA-Z][-.a-zA-Z0-9]*)|(x[0-9a-zA-Z]+|[0-9]+))(;?)"
+ )
+
+ def convert_charref(self, name):
+ self.events.append(("charref", "convert", name))
+ if name[0] != "x":
+ return super().convert_charref(name)
+
+ def convert_codepoint(self, codepoint):
+ self.events.append(("codepoint", "convert", codepoint))
+ super().convert_codepoint(codepoint)
+
+ def convert_entityref(self, name):
+ self.events.append(("entityref", "convert", name))
+ return super().convert_entityref(name)
+
+ # These to record that they were called, then pass the call along
+ # to the default implementation so that its actions can be
+ # recorded.
+
+ def handle_charref(self, name):
+ self.events.append(("charref", name))
+ sgmllib.SGMLParser.handle_charref(self, name)
+
+ def handle_entityref(self, name):
+ self.events.append(("entityref", name))
+ sgmllib.SGMLParser.handle_entityref(self, name)
diff --git a/tests/test_sgmllib.py b/tests/test_sgmllib.py
index 8b14b75..8997cfe 100644
--- a/tests/test_sgmllib.py
+++ b/tests/test_sgmllib.py
@@ -1,524 +1,431 @@
+import io
import pathlib
-import pprint
-import re
-import unittest
+
+import pytest
import feedparser.sgmllib as sgmllib
-class EventCollector(sgmllib.SGMLParser):
+def test_doctype_decl_internal(event_collector):
+ inside = """\
+DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01//EN'
+ SYSTEM 'http://www.w3.org/TR/html401/strict.dtd' [
+
+
+
+
+
+
+%paramEntity;
+
+]"""
+ event_collector.check_events(
+ [f""],
+ [
+ ("decl", inside),
+ ],
+ )
- def __init__(self) -> None:
- self.events = []
- self.append = self.events.append
- sgmllib.SGMLParser.__init__(self)
- def get_events(self):
- # Normalize the list of events so that buffer artefacts don't
- # separate runs of contiguous characters.
- L = []
- prevtype = None
- for event in self.events:
- type = event[0]
- if type == prevtype == "data":
- L[-1] = ("data", L[-1][1] + event[1])
- else:
- L.append(event)
- prevtype = type
- self.events = L
- return L
+def test_doctype_decl_external(event_collector):
+ inside = "DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01//EN'"
+ event_collector.check_events(
+ "" % inside,
+ [
+ ("decl", inside),
+ ],
+ )
- # structure markup
- def unknown_starttag(self, tag, attrs):
- self.append(("starttag", tag, attrs))
+def test_underscore_in_attrname(event_collector):
+ # SF bug #436621
+ """Make sure attribute names with underscores are accepted"""
+ event_collector.check_events(
+ "",
+ [
+ ("starttag", "a", [("has_under", "has_under"), ("_under", "_under")]),
+ ],
+ )
- def unknown_endtag(self, tag):
- self.append(("endtag", tag))
- # all other markup
+def test_underscore_in_tagname(event_collector):
+ # SF bug #436621
+ """Make sure tag names with underscores are accepted"""
+ event_collector.check_events(
+ "",
+ [
+ ("starttag", "has_under", []),
+ ("endtag", "has_under"),
+ ],
+ )
- def handle_comment(self, data):
- self.append(("comment", data))
- def handle_charref(self, data):
- self.append(("charref", data))
+def test_quotes_in_unquoted_attrs(event_collector):
+ # SF bug #436621
+ """Be sure quotes in unquoted attributes are made part of the value"""
+ event_collector.check_events(
+ "",
+ [
+ ("starttag", "a", [("href", "foo'bar\"baz")]),
+ ],
+ )
- def handle_data(self, data):
- self.append(("data", data))
- def handle_decl(self, decl):
- self.append(("decl", decl))
+def test_xhtml_empty_tag(event_collector):
+ """Handling of XHTML-style empty start tags"""
+ event_collector.check_events(
+ "
text",
+ [
+ ("starttag", "br", []),
+ ("data", "text"),
+ ("starttag", "i", []),
+ ("endtag", "i"),
+ ],
+ )
- def handle_entityref(self, data):
- self.append(("entityref", data))
- def handle_pi(self, data):
- self.append(("pi", data))
+def test_processing_instruction_only(event_collector):
+ event_collector.check_events(
+ "",
+ [
+ ("pi", "processing instruction"),
+ ],
+ )
- def unknown_decl(self, decl):
- self.append(("unknown decl", decl))
+def test_bad_nesting(event_collector):
+ event_collector.check_events(
+ "",
+ [
+ ("starttag", "a", []),
+ ("starttag", "b", []),
+ ("endtag", "a"),
+ ("endtag", "b"),
+ ],
+ )
-class CDATAEventCollector(EventCollector):
- def start_cdata(self, attrs):
- self.append(("starttag", "cdata", attrs))
- self.setliteral()
+def test_bare_ampersands(event_collector):
+ event_collector.check_events(
+ "this text & contains & ampersands &",
+ [
+ ("data", "this text & contains & ampersands &"),
+ ],
+ )
-class HTMLEntityCollector(EventCollector):
- entity_or_charref = re.compile(
- "(?:&([a-zA-Z][-.a-zA-Z0-9]*)|(x[0-9a-zA-Z]+|[0-9]+))(;?)"
+def test_bare_pointy_brackets(event_collector):
+ event_collector.check_events(
+ "this < text > contains < bare>pointy< brackets",
+ [
+ ("data", "this < text > contains < bare>pointy< brackets"),
+ ],
)
- def convert_charref(self, name):
- self.append(("charref", "convert", name))
- if name[0] != "x":
- return EventCollector.convert_charref(self, name)
- def convert_codepoint(self, codepoint):
- self.append(("codepoint", "convert", codepoint))
- EventCollector.convert_codepoint(self, codepoint)
+@pytest.mark.parametrize(
+ "source",
+ (
+ """""",
+ """""",
+ """""",
+ """""",
+ ),
+)
+def test_attr_syntax(event_collector, source):
+ output = [("starttag", "a", [("b", "v"), ("c", "v"), ("d", "v"), ("e", "e")])]
+ event_collector.check_events(source, output)
+
+
+@pytest.mark.parametrize(
+ "attribute",
+ (
+ "xxx\n\txxx",
+ "yyy\t\nyyy",
+ "\txyz\n",
+ "",
+ ),
+)
+@pytest.mark.parametrize("quote", ('"', "'"))
+def test_attr_values_quoted(event_collector, attribute, quote):
+ event_collector.check_events(
+ f"",
+ [("starttag", "a", [("b", attribute)])],
+ )
- def convert_entityref(self, name):
- self.append(("entityref", "convert", name))
- return EventCollector.convert_entityref(self, name)
- # These to record that they were called, then pass the call along
- # to the default implementation so that it's actions can be
- # recorded.
+def test_attr_values_unquoted_url(event_collector):
+ # URL construction stuff from RFC 1808:
+ safe = "$-_.+"
+ extra = "!*'(),"
+ reserved = ";/?:@&="
+ url = f"https://example.com:8080/path/to/file?{safe}{extra}{reserved}"
+ event_collector.check_events(
+ """""" % url,
+ [
+ ("starttag", "e", [("a", url)]),
+ ],
+ )
- def handle_charref(self, data):
- self.append(("charref", data))
- sgmllib.SGMLParser.handle_charref(self, data)
- def handle_entityref(self, data):
- self.append(("entityref", data))
- sgmllib.SGMLParser.handle_entityref(self, data)
+def test_attr_values_unquoted(event_collector):
+ # Regression test for SF patch #669683.
+ event_collector.check_events(
+ "",
+ [
+ ("starttag", "e", [("a", "rgb(1,2,3)")]),
+ ],
+ )
-class SGMLParserTestCase(unittest.TestCase):
+def test_attr_values_entities(event_collector):
+ """Substitution of entities and charrefs in attribute values"""
+ # SF bug #1452246
+ event_collector.check_events(
+ """""",
+ [
+ (
+ "starttag",
+ "a",
+ [
+ ("b", "<"),
+ ("c", "<>"),
+ ("d", "<->"),
+ ("e", "< "),
+ ("f", "&xxx;"),
+ ("g", " !"),
+ ("h", "Ǵ"),
+ ("i", "x?a=b&c=d;"),
+ ("j", "*"),
+ ("k", "*"),
+ ],
+ )
+ ],
+ )
- collector = EventCollector
- def get_events(self, source):
- parser = self.collector()
- for s in source:
- parser.feed(s)
- parser.close()
- return parser.get_events()
+def test_convert_overrides(html_entity_collector):
+ # This checks that the character and entity reference
+ # conversion helpers are called at the documented times. No
+ # attempt is made to really change what the parser accepts.
+ #
+ html_entity_collector.check_events(
+ 'foo&foobar;*',
+ [
+ ("entityref", "convert", "ldquo"),
+ ("charref", "convert", "x201d"),
+ ("starttag", "a", [("title", "“test”")]),
+ ("data", "foo"),
+ ("endtag", "a"),
+ ("entityref", "foobar"),
+ ("entityref", "convert", "foobar"),
+ ("charref", "42"),
+ ("charref", "convert", "42"),
+ ("codepoint", "convert", 42),
+ ],
+ )
- def check_events(self, source, expected_events):
- events = self.get_events(source)
- if events != expected_events:
- self.fail(
- "received events did not match expected events\n"
- "Expected:\n"
- + pprint.pformat(expected_events)
- + "\nReceived:\n"
- + pprint.pformat(events)
- )
- def check_parse_error(self, source):
- parser = EventCollector()
- try:
- parser.feed(source)
- parser.close()
- except sgmllib.SGMLParseError:
- pass
- else:
- self.fail(
- "expected SGMLParseError for %r\nReceived:\n%s"
- % (source, pprint.pformat(parser.get_events()))
- )
+def test_attr_funky_names(event_collector):
+ event_collector.check_events(
+ """""",
+ [
+ ("starttag", "a", [("a.b", "v"), ("c:d", "v"), ("e-f", "v")]),
+ ],
+ )
- def test_doctype_decl_internal(self):
- inside = """\
-DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01//EN'
- SYSTEM 'http://www.w3.org/TR/html401/strict.dtd' [
-
-
-
-
-
-
- %paramEntity;
-
-]"""
- self.check_events(
- ["" % inside],
- [
- ("decl", inside),
- ],
- )
-
- def test_doctype_decl_external(self):
- inside = "DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01//EN'"
- self.check_events(
- "" % inside,
- [
- ("decl", inside),
- ],
- )
-
- def test_underscore_in_attrname(self):
- # SF bug #436621
- """Make sure attribute names with underscores are accepted"""
- self.check_events(
- "",
- [
- ("starttag", "a", [("has_under", "has_under"), ("_under", "_under")]),
- ],
- )
-
- def test_underscore_in_tagname(self):
- # SF bug #436621
- """Make sure tag names with underscores are accepted"""
- self.check_events(
- "",
- [
- ("starttag", "has_under", []),
- ("endtag", "has_under"),
- ],
- )
-
- def test_quotes_in_unquoted_attrs(self):
- # SF bug #436621
- """Be sure quotes in unquoted attributes are made part of the value"""
- self.check_events(
- "",
- [
- ("starttag", "a", [("href", "foo'bar\"baz")]),
- ],
- )
-
- def test_xhtml_empty_tag(self):
- """Handling of XHTML-style empty start tags"""
- self.check_events(
- "
text",
- [
- ("starttag", "br", []),
- ("data", "text"),
- ("starttag", "i", []),
- ("endtag", "i"),
- ],
- )
-
- def test_processing_instruction_only(self):
- self.check_events(
- "",
- [
- ("pi", "processing instruction"),
- ],
- )
-
- def test_bad_nesting(self):
- self.check_events(
- "",
- [
- ("starttag", "a", []),
- ("starttag", "b", []),
- ("endtag", "a"),
- ("endtag", "b"),
- ],
- )
-
- def test_bare_ampersands(self):
- self.check_events(
- "this text & contains & ampersands &",
- [
- ("data", "this text & contains & ampersands &"),
- ],
- )
-
- def test_bare_pointy_brackets(self):
- self.check_events(
- "this < text > contains < bare>pointy< brackets",
- [
- ("data", "this < text > contains < bare>pointy< brackets"),
- ],
- )
-
- def test_attr_syntax(self):
- output = [("starttag", "a", [("b", "v"), ("c", "v"), ("d", "v"), ("e", "e")])]
- self.check_events("""""", output)
- self.check_events("""""", output)
- self.check_events("""""", output)
- self.check_events("""""", output)
-
- def test_attr_values(self):
- self.check_events(
- """""",
- [
- (
- "starttag",
- "a",
- [("b", "xxx\n\txxx"), ("c", "yyy\t\nyyy"), ("d", "\txyz\n")],
- )
- ],
- )
- self.check_events(
- """""",
- [
- ("starttag", "a", [("b", ""), ("c", "")]),
- ],
- )
- # URL construction stuff from RFC 1808:
- safe = "$-_.+"
- extra = "!*'(),"
- reserved = ";/?:@&="
- url = "http://example.com:8080/path/to/file?{}{}{}".format(
- safe, extra, reserved
- )
- self.check_events(
- """""" % url,
- [
- ("starttag", "e", [("a", url)]),
- ],
- )
- # Regression test for SF patch #669683.
- self.check_events(
- "",
- [
- ("starttag", "e", [("a", "rgb(1,2,3)")]),
- ],
- )
-
- def test_attr_values_entities(self):
- """Substitution of entities and charrefs in attribute values"""
- # SF bug #1452246
- self.check_events(
- """""",
- [
- (
- "starttag",
- "a",
- [
- ("b", "<"),
- ("c", "<>"),
- ("d", "<->"),
- ("e", "< "),
- ("f", "&xxx;"),
- ("g", " !"),
- ("h", "Ǵ"),
- ("i", "x?a=b&c=d;"),
- ("j", "*"),
- ("k", "*"),
- ],
- )
- ],
- )
-
- def test_convert_overrides(self):
- # This checks that the character and entity reference
- # conversion helpers are called at the documented times. No
- # attempt is made to really change what the parser accepts.
- #
- self.collector = HTMLEntityCollector
- self.check_events(
- 'foo&foobar;*',
- [
- ("entityref", "convert", "ldquo"),
- ("charref", "convert", "x201d"),
- ("starttag", "a", [("title", "“test”")]),
- ("data", "foo"),
- ("endtag", "a"),
- ("entityref", "foobar"),
- ("entityref", "convert", "foobar"),
- ("charref", "42"),
- ("charref", "convert", "42"),
- ("codepoint", "convert", 42),
- ],
- )
-
- def test_attr_funky_names(self):
- self.check_events(
- """""",
- [
- ("starttag", "a", [("a.b", "v"), ("c:d", "v"), ("e-f", "v")]),
- ],
- )
-
- def test_attr_value_ip6_url(self):
- # http://www.python.org/sf/853506
- self.check_events(
+
+def test_attr_value_ip6_url(event_collector):
+ # http://www.python.org/sf/853506
+ event_collector.check_events(
+ (
+ ""
+ ""
+ ),
+ [
+ ("starttag", "a", [("href", "http://[1080::8:800:200C:417A]/")]),
+ ("starttag", "a", [("href", "http://[1080::8:800:200C:417A]/")]),
+ ],
+ )
+
+
+@pytest.mark.parametrize(
+ "source, expected",
+ (
+ ("", [("starttag", "a", []), ("starttag", "a", [])]),
+ ("", [("endtag", "a"), ("starttag", "a", [])]),
+ ),
+)
+def test_weird_starttags(event_collector, source, expected):
+ event_collector.check_events(source, expected)
+
+
+def test_declaration_junk_chars(check_parse_error):
+ check_parse_error("")
+
+
+def test_get_starttag_text(event_collector):
+ s = """"""
+ event_collector.check_events(
+ s,
+ [
+ ("starttag", "foobar", [("one", "1"), ("two", "2")]),
+ ],
+ )
+
+
+@pytest.mark.parametrize(
+ "data",
+ (
+ "",
+ "¬-an-entity-ref;",
+ "",
+ ),
+)
+def test_cdata_content(cdata_event_collector, data):
+ s = f" {data} "
+ cdata_event_collector.check_events(
+ s,
+ [
+ ("starttag", "cdata", []),
+ ("data", f" {data} "),
+ ("endtag", "cdata"),
+ ("starttag", "notcdata", []),
+ ("data", " "),
+ ("comment", " comment "),
+ ("data", " "),
+ ("endtag", "notcdata"),
+ ],
+ )
+
+
+def test_illegal_declarations(event_collector):
+ s = 'abcdef'
+ event_collector.check_events(
+ s,
+ [
+ ("data", "abc"),
+ ("unknown decl", 'spacer type="block" height="25"'),
+ ("data", "def"),
+ ],
+ )
+
+
+def test_enumerated_attr_type(event_collector):
+ s = "]>"
+ event_collector.check_events(
+ s,
+ [
+ ("decl", "DOCTYPE doc []"),
+ ],
+ )
+
+
+@pytest.fixture(scope="session")
+def _sgml_input_html():
+ # Read the file exactly once.
+ path = pathlib.Path(__file__).parent / "sgml_input.html"
+ return path.read_text(encoding="ISO-8859-1")
+
+
+@pytest.fixture()
+def sgml_input_html(_sgml_input_html):
+ return io.StringIO(_sgml_input_html)
+
+
+@pytest.mark.parametrize("chunk_size", (1, 1024, 8212))
+def test_read_chunks(chunk_size, sgml_input_html):
+ # SF bug #1541697, this caused sgml parser to hang
+ # Just verify this code doesn't cause a hang.
+ # The problem goes away if the chunk size is 8212.
+
+ fp = sgmllib.SGMLParser()
+ while 1:
+ data = sgml_input_html.read(chunk_size)
+ fp.feed(data)
+ if len(data) != chunk_size:
+ break
+
+
+def test_only_decode_ascii(event_collector):
+ # SF bug #1651995, make sure non-ascii character references are not decoded
+ s = ''
+ event_collector.check_events(
+ s,
+ [
(
- ""
- ""
+ "starttag",
+ "signs",
+ [
+ ("exclamation", "!"),
+ ("copyright", "©"),
+ ("quoteleft", "‘"),
+ ],
),
- [
- ("starttag", "a", [("href", "http://[1080::8:800:200C:417A]/")]),
- ("starttag", "a", [("href", "http://[1080::8:800:200C:417A]/")]),
- ],
- )
-
- def test_weird_starttags(self):
- self.check_events(
- "",
- [
- ("starttag", "a", []),
- ("starttag", "a", []),
- ],
- )
- self.check_events(
- "",
- [
- ("endtag", "a"),
- ("starttag", "a", []),
- ],
- )
-
- def test_declaration_junk_chars(self):
- self.check_parse_error("")
-
- def test_get_starttag_text(self):
- s = """"""
- self.check_events(
- s,
- [
- ("starttag", "foobar", [("one", "1"), ("two", "2")]),
- ],
- )
-
- def test_cdata_content(self):
- s = (
- " ¬-an-entity-ref; "
- " "
- )
- self.collector = CDATAEventCollector
- self.check_events(
- s,
- [
- ("starttag", "cdata", []),
- ("data", " ¬-an-entity-ref; "),
- ("endtag", "cdata"),
- ("starttag", "notcdata", []),
- ("data", " "),
- ("comment", " comment "),
- ("data", " "),
- ("endtag", "notcdata"),
- ],
- )
- s = """ """
- self.check_events(
- s,
- [
- ("starttag", "cdata", []),
- ("data", " "),
- ("endtag", "cdata"),
- ],
- )
-
- def test_illegal_declarations(self):
- s = 'abcdef'
- self.check_events(
- s,
- [
- ("data", "abc"),
- ("unknown decl", 'spacer type="block" height="25"'),
- ("data", "def"),
- ],
- )
-
- def test_enumerated_attr_type(self):
- s = "]>"
- self.check_events(
- s,
- [
- ("decl", "DOCTYPE doc []"),
- ],
- )
-
- def test_read_chunks(self):
- # SF bug #1541697, this caused sgml parser to hang
- # Just verify this code doesn't cause a hang.
- CHUNK = 1024 # increasing this to 8212 makes the problem go away
-
- fp = sgmllib.SGMLParser()
- path = pathlib.Path(__file__).parent / "sgml_input.html"
- with path.open(encoding="ISO-8859-1") as f:
- while 1:
- data = f.read(CHUNK)
- fp.feed(data)
- if len(data) != CHUNK:
- break
-
- def test_only_decode_ascii(self):
- # SF bug #1651995, make sure non-ascii character references are not decoded
- s = ''
- self.check_events(
- s,
- [
- (
- "starttag",
- "signs",
- [
- ("exclamation", "!"),
- ("copyright", "©"),
- ("quoteleft", "‘"),
- ],
- ),
- ],
- )
-
- # XXX These tests have been disabled by prefixing their names with
- # an underscore. The first two exercise outstanding bugs in the
- # sgmllib module, and the third exhibits questionable behavior
- # that needs to be carefully considered before changing it.
-
- def _test_starttag_end_boundary(self):
- self.check_events("", [("starttag", "a", [("b", "<")])])
- self.check_events("", [("starttag", "a", [("b", ">")])])
-
- def _test_buffer_artefacts(self):
- output = [("starttag", "a", [("b", "<")])]
- self.check_events([""], output)
- self.check_events([""], output)
- self.check_events([""], output)
- self.check_events([""], output)
- self.check_events([""], output)
- self.check_events([""], output)
-
- output = [("starttag", "a", [("b", ">")])]
- self.check_events([""], output)
- self.check_events(["'>"], output)
- self.check_events(["'>"], output)
- self.check_events(["'>"], output)
- self.check_events([""], output)
- self.check_events([""], output)
-
- output = [("comment", "abc")]
- self.check_events(["", ""], output)
- self.check_events(["<", "!--abc-->"], output)
- self.check_events([""], output)
- self.check_events([""], output)
- self.check_events([""], output)
- self.check_events([""], output)
- self.check_events([""], output)
- self.check_events([""], output)
- self.check_events(["", ""], output)
-
- def _test_starttag_junk_chars(self):
- self.check_parse_error("<")
- self.check_parse_error("<>")
- self.check_parse_error("$>")
- self.check_parse_error("")
- self.check_parse_error("")
- self.check_parse_error("")
- self.check_parse_error("'")
- self.check_parse_error("", [("starttag", "a", [("b", "<")])])
+ event_collector.check_events("", [("starttag", "a", [("b", ">")])])
+
+
+def _test_buffer_artefacts(event_collector):
+ output = [("starttag", "a", [("b", "<")])]
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+
+ output = [("starttag", "a", [("b", ">")])]
+ event_collector.check_events([""], output)
+ event_collector.check_events(["'>"], output)
+ event_collector.check_events(["'>"], output)
+ event_collector.check_events(["'>"], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+
+ output = [("comment", "abc")]
+ event_collector.check_events(["", ""], output)
+ event_collector.check_events(["<", "!--abc-->"], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events([""], output)
+ event_collector.check_events(["", ""], output)
+
+
+def _test_starttag_junk_chars(check_parse_error):
+ check_parse_error("<")
+ check_parse_error("<>")
+ check_parse_error("$>")
+ check_parse_error("")
+ check_parse_error("")
+ check_parse_error("")
+ check_parse_error("'")
+ check_parse_error("