Skip listing containers whose entries hold user cards or quotes

A thread without DiscussionForumPosting markup, where every poster writes
once, still passed the teaser test: each post opens with a linked name that
no other post repeats. On katzen-forum.net with the JSON-LD removed and one
post per poster, recall took the whole container (3,573 chars, seven user
cards instead of one).

Teasers hold neither a description list nor a quote, while forum posts keep
the poster's user card in one and quote each other, so an entry holding
either now rules the container out.
This commit is contained in:
dualfroz committed 2026-10-10 15:34:44 +02:00
1 parent 77465a50e7
commit 06c7822f79
2 files changed
+23 -11

No files matched your search

+19 -10
View File
@@ -2963,13 +2963,18 @@ def test_listing_page_with_links_keeps_every_entry_heading():
assert text in result
def _forum_thread(posters, linked=True, forum_markup=False):
def _forum_thread(posters, linked=True, forum_markup=False, card_in_dl=False, quoting=False):
"Thread markup: sibling <article> posts, each opening with a user card that names its poster in a heading."
name_markup = '<a href="/members/{0}/">{0}</a>' if linked else "<span>{0}</span>"
card = (
"<dl><dt>Joined</dt><dd>March 2015</dd><dt>Messages</dt><dd>1,447</dd></dl>"
if card_in_dl
else '<div class="user-title">Joined March 2015, 1,447 messages</div>'
)
posts = "".join(
f'<article><div class="poster"><h4>{name_markup.format(name)}</h4>'
"<dl><dt>Joined</dt><dd>March 2015</dd><dt>Messages</dt><dd>1,447</dd></dl></div>"
f"<div><p>Reply {i} from {name} carries enough prose to count as a post, running over a couple "
f'<article><div class="poster"><h4>{name_markup.format(name)}</h4>{card}</div>'
+ (f"<blockquote><p>{posters[i - 1]} wrote: see my reply above.</p></blockquote>" if quoting and i else "")
+ f"<div><p>Reply {i} from {name} carries enough prose to count as a post, running over a couple "
"of clauses so the thread reads as a real discussion between people.</p></div></article>"
for i, name in enumerate(posters)
)
@@ -2986,20 +2991,24 @@ def _forum_thread(posters, linked=True, forum_markup=False):
@pytest.mark.parametrize(
"posters,linked,forum_markup",
"posters,markup",
[
# the same poster comes back along the thread
(["alice", "bob", "alice", "carol", "bob", "alice"], True, False),
(["alice", "bob", "alice", "carol", "bob", "alice"], {}),
# poster names without a link
(["alice", "bob", "carol", "dave", "erin", "frank"], False, False),
(["alice", "bob", "carol", "dave", "erin", "frank"], {"linked": False}),
# every poster different, but the page declares itself a forum thread
(["alice", "bob", "carol", "dave", "erin", "frank"], True, True),
(["alice", "bob", "carol", "dave", "erin", "frank"], {"forum_markup": True}),
# every poster different and no forum markup, but the user card is a description list
(["alice", "bob", "carol", "dave", "erin", "frank"], {"card_in_dl": True}),
# every poster different and no forum markup, but the replies quote the post before them
(["alice", "bob", "carol", "dave", "erin", "frank"], {"quoting": True}),
],
)
@pytest.mark.parametrize("settings", [{"favor_recall": True}, {"favor_recall": True, "fast": True}])
def test_forum_thread_is_not_a_listing(posters, linked, forum_markup, settings):
def test_forum_thread_is_not_a_listing(posters, markup, settings):
"forum posts line up like listing entries, but taking their container brings every user card along"
doc = _forum_thread(posters, linked, forum_markup)
doc = _forum_thread(posters, **markup)
with _without_listing_expression():
expected = extract(doc, output_format="markdown", config=use_config(), **settings)
result = extract(doc, output_format="markdown", config=use_config(), **settings) or ""
+4 -1
View File
@@ -665,9 +665,12 @@ def _holds_most_of_the_text(tree: HtmlElement, expr: XPath) -> bool:
def _entries_link_out(container: _Element) -> bool:
"""Whether every entry of the container is a teaser: its first heading links to a page of its
own, which no other entry links to. Forum posts share the shape but open with the poster's
name, which comes back along the thread or carries no link at all."""
name, which comes back along the thread or carries no link at all, and they hold user cards
(description lists) and quotes, which teasers do not."""
targets: set[str] = set()
for entry in container.iterchildren("article"):
if entry.find(".//list[@rend='dl']") is not None or entry.find(".//quote") is not None:
return False
heading = entry.find(".//head")
link = heading.find(".//ref") if heading is not None else None
if link is None: