Skip to content

url: pages: a quote that runs through a link or bold text fails; cache readable text instead of markup #102

Closed

Description

@caufieldjh

Since #92, URLSource caches a page's body markup without scripts, styles, comments or attributes. That fixed the size and leakage problem. But a quote can still only match text that sits inside one text node: any sentence that runs through inline markup (a link, bold, emphasis) fails, even when it is copied exactly from the page.

Reproduce (0.3.0)

R="url:https://patch.com/michigan/flint/flint-water-pollution-control-division-press-release-10-25-2021"
linkml-reference-validator validate text "This press release was produced by" "$R"               # Valid: True
linkml-reference-validator validate text "This press release was produced by the City of Flint." "$R"  # Valid: False

The cache holds This press release was produced by <a>the City of Flint</a>. …

The same failures, met while curating Mechs made with mechmaker:

  • Wikipedia's lead sentences bold the article's subject (The <b>Flint water crisis</b> was a public health crisis…), so the first sentence of almost any article cannot be quoted. DaTMech works around this with a JSON source that fetches Wikipedia's plain-text extract.
  • ZFIN's terms page puts the license name in a link (licensed under a <a>Creative Commons Attribution 4.0 International License</a>). IngestMech had to quote it in two parts joined with ....
  • Readability. A whole page lands on one line of markup. After URLSource caches raw page HTML: scripts, comments and attributes reach a committed cache #92, ZFIN's page is 19 KB with an 18,304-character line, so a reviewer cannot read a cached reference or a diff of one.

Suggestion

Store a url: page that is HTML as readable text, not markup. HTMLExtractor already flattens with breaks at block-level tags and without welding inline neighbours together, so it may serve as is:

The other way round would keep the markup in the cache but match quotes against the extracted text. That keeps the cache as it is, but leaves it unreadable.

Every quote that matches today lies within one text node, so it should still match the flattened text (whitespace is already normalized). Quotes through inline markup would start to pass.

Companion to monarch-initiative/mechmaker#82, where IngestMech's 1.1 MB ZFIN page (cached by 0.2.1) is now 19 KB with 0.3.0.

🤖 Generated with Claude Code

Activity

  1. github-actions commented on Oct 10, 2026

    @github-actions
    Contributor

    Summary

    Since #92, URLSource caches sanitized body markup (sanitize_html in etl/extract/html.py). A quote that spans inline markup (<a>, <b>, <em>) fails against that markup. The proposal is to cache readable text instead, and bump URL_SOURCE_CACHE_VERSION.

    I read the current code on main. I haven't run the repro URL.

    Related issues

    Check on "HTMLExtractor may serve as is"

    It probably can't, as written. In HTMLExtractor._extract_scope (etl/extract/html.py, around lines 240–290):

    1. <p>-only branch. If the page has any <p>, only paragraph text is returned. Headings, <li> items outside a <p>, table cells and <pre> are dropped. The issue asks for list items and table rows to be kept, so a page with a few <p> tags would silently lose text that matches today.
    2. extract() narrows to <article> or <main>. For a generic url: page, text that matches today in a sidebar or footer would stop matching. This is arguably a feature (Landing-page fallback is stamped full_text_html: a cache entry can claim full text while holding only the abstract #65), but it is a behaviour change that isn't mentioned.
    3. No table-row handling. The issue wants one line per row with separated cells. The extractor only inserts a "\n" after each block-level tag, so <td> cells would be on separate lines, not joined on one row.
    4. <pre> whitespace is not distinguished, though sanitize_html handles it.

    Probably the cleanest option is a new whole-document mode or function, e.g. html_to_text(content). It would run after the existing sanitize step, or fold into it. It would keep the "block tags get a break, inline tags are unwrapped" logic and skip the <p>-only and <article> shortcuts. It would also add the row-per-line rule, and it should be idempotent like sanitize_html. Getting this right matters because the invariant "every quote that matches today still matches" holds only if no text node is dropped.

    Alternative: match against extracted text

    The issue mentions matching quotes against extracted text while keeping markup in the cache. I'd stay with the primary proposal. Matching against derived text means the validator and the cache file can disagree about what was matched, which is a bad property for an audit trail. A cache that reads like the page is also the simpler design.

    Suggested tests, TDD per CLAUDE.md

    • Doctests for the new function covering <a>, <b>, <br>, <li>, a table row, entities, and <pre>.
    • A regression test with the Flint sentence: "This press release was produced by <a>the City of Flint</a>."
    • A property check on fixtures: any quote that matches the old sanitized HTML text-node-wise also matches the flattened text.
    • A test that a page with both <p> and <li> or <td> text keeps all of it.
    • A test that old cache entries (url_source_version = 1) are re-fetched after the bump.
    • A test that title still comes from <meta>.

    Open questions

    • Should table cells be separated by |, a tab, or a space? This affects quotes copied from a rendered table, since normalization collapses whitespace but not pipes.
    • Do cache files from earlier versions need a migration note? Bumping the version re-fetches them, which needs network access and can fail on pages that have since changed.

    I can't tell from the repo whether mechmaker#82 or the DaTMech JSON-source workaround have changed, so I haven't relied on them.

    If you want a PR for this:

    @dragon-ai-agent please implement #102: add a whole-document HTML-to-text function (not reusing HTMLExtractor's <p>-only/<article> shortcuts), use it in URLSource.fetch after title extraction, bump URL_SOURCE_CACHE_VERSION to 2, and add doctests and tests per the list in this comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions