Repository navigation
fix(docreader): keep Markdown block structure in EPUB chapters - #4632
Merged
lyingbug merged 1 commit intoOct 23, 2026
Merged
Conversation
EPUBParser._html_to_markdown (docreader/parser/epub_parser.py:257) stripped
every line of the markdownify output and dropped the blank ones. Markdown
needs those blank lines and that indentation to tell blocks apart, so every
EPUB chapter came out wrong: consecutive paragraphs merged into one, a
paragraph directly above a scene break ("---") became a setext heading, the
paragraph after a quote or a list item joined it, a line after a table was
read as another table row, nested lists flattened, and code lost its
indentation and blank lines.
MHTML had the same line stripping until Tencent#1743 replaced it with
MHTMLParser._normalize_markdown, which static HTML uploads use as well. The
EPUB parser kept its own copy. It now calls the same normalizer, which only
collapses runs of blank lines and leaves fenced code verbatim. The ZIP
fallback for EPUBs that ebooklib cannot read goes through the same helper and
is fixed too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
EPUBParser._html_to_markdownstrips every line of the markdownify output and drops the blank ones (docreader/parser/epub_parser.py:257-259). Markdown relies on those blank lines and that indentation to separate blocks, so every EPUB chapter comes out with broken structure:<hr/>, written as---) becomes a setext<h2>heading;<pre>code loses its indentation and blank lines.The repo's own fixture
internal/infrastructure/docparser/anydoc/testdata/upstream/epub/book.epubshows it. Excerpt ofEPUBParser().parse_into_text(...)onmain:Rendered as CommonMark + GFM tables, the nested bullet splits the ordered list in two, "See Chapter Two…" becomes part of item 3, and "This paragraph…" becomes a third table row. With this change:
The missing blank lines also matter for chunking: the Go splitter's first separator is
"\n\n"(internal/infrastructure/chunker/strategy.go:196), and the body of an EPUB chapter had none.The MHTML parser had the same line stripping until #1743 replaced it with
MHTMLParser._normalize_markdown, which static HTML uploads also use throughMHTMLParser.html_to_markdown. #3978 noted that the EPUB parser keeps its own copy of the conversion. That copy now calls the same normalizer, which only collapses runs of blank lines, keeps indentation and two-space hard breaks, and leaves fenced code verbatim. The ZIP fallback (used when ebooklib cannot read the file) goes through the same helper, so both paths are fixed. Image rewriting, link unwrapping and the rest of the EPUB conversion are unchanged. One visible side effect: a leading ideographic space (U+3000), which some Chinese EPUBs put in the text as a paragraph indent, is no longer stripped, the same as in HTML and MHTML output.EPUBs reach this parser through the builtin (DocReader) engine: by default when the app is built without anydoc, and whenever a knowledge base's parser rules select builtin for
epub.Type of Change
Related Issue
No issue filed. Related: #1743 (same fix for MHTML), #3978 (notes the separate EPUB copy).
Testing
New test
test_chapter_markdown_keeps_block_structureindocreader/tests/test_epub_parser.pyparses a one-chapter EPUB with a scene break, a blockquote, a nested list, a<pre>block and a table, through both the ebooklib path and the ZIP fallback. Python 3.10.22 on macOS arm64, dependencies fromuv sync --project docreader --locked --no-dev.Restoring only
epub_parser.pyfrommainand keeping the new test gives the failure this fixes (both subtests fail the same way):Full DocReader suite (
python -m unittest discover -s docreader/tests -p "test_*.py"): 389 tests, 12 failing (11 failures, 1 error), the same 12 as onmainin this environment and unrelated to EPUB: 2 intest_ssrf(DNS lookups are blocked here), 1 intest_ssrf_proxy(Playwright WebKit not installed) and 9 intest_preprocess_runner(the sandbox stops the worker subprocess). The Go gate (make lint/make test) was not run; no Go file changed.Checklist
git diff --check origin/main...HEADpassesgolangci-lint run --new-from-rev=origin/main ./...)website-docs/, Swagger annotations, etc.)Screenshots / Recordings
None. No UI change.
🤖 Generated with Claude Code