Skip to content

fix: handle deeply nested HTML that triggers RecursionError#1644

Merged
afourney merged 4 commits into
microsoft:mainfrom
jigangz:fix/large-html-silent-failure
Apr 15, 2026
Merged

fix: handle deeply nested HTML that triggers RecursionError#1644
afourney merged 4 commits into
microsoft:mainfrom
jigangz:fix/large-html-silent-failure

Conversation

@jigangz

@jigangz jigangz commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix large HTML files (>3MB) with deep DOM nesting silently returning unconverted HTML instead of markdown.

Problem

When converting deeply nested HTML documents (e.g., SEC EDGAR filings like Tesla's DEF 14A proxy statement), markdownify's recursive DOM traversal exceeds Python's default recursion limit (~400 nesting levels). The RecursionError is caught by the top-level _convert() dispatcher's except Exception block, and the request falls through to PlainTextConverter which returns the raw HTML as-is — with no error or warning.

Root cause chain:

  1. HtmlConverter.convert()markdownify.convert_soup() (recursive traversal)
  2. Deep nesting (>~400 levels) → RecursionError
  3. _convert() catches it via except Exception, stores in failed_attempts
  4. PlainTextConverter.accepts() matches text/html via text/ prefix → true
  5. PlainTextConverter.convert() returns raw HTML bytes as text
  6. Caller gets "markdown" that is actually unconverted HTML

Fix

Catch RecursionError in HtmlConverter.convert() and fall back to BeautifulSoup's iterative get_text() method, which handles arbitrary nesting depths. A UserWarning is emitted so callers know the output is plain text rather than full markdown.

Changes

  • packages/markitdown/src/markitdown/converters/_html_converter.py: catch RecursionError, fall back to get_text(), emit warning
  • packages/markitdown/tests/test_module_misc.py: add test_deeply_nested_html_fallback verifying the fallback behavior and warning

Fixes #1636

…t#1636)

Large HTML files with deep DOM nesting (e.g., SEC EDGAR filings) cause
markdownify's recursive DOM traversal to exceed Python's default
recursion limit (1000). Previously this RecursionError was caught by
the top-level _convert() dispatcher, which then fell through to
PlainTextConverter — silently returning the raw HTML as 'markdown'
with no warning.

This fix catches RecursionError in HtmlConverter.convert() and falls
back to BeautifulSoup's iterative get_text() method, which handles
arbitrary nesting depths. A warning is emitted so callers know the
output is plain text rather than full markdown.

Root cause chain:
1. HtmlConverter.convert() calls markdownify.convert_soup() (recursive)
2. Deeply nested HTML (>~400 levels) triggers RecursionError
3. _convert() catches all Exceptions, stores in failed_attempts
4. PlainTextConverter.accepts() matches text/html via 'text/' prefix
5. PlainTextConverter.convert() returns raw HTML bytes as text
6. Caller receives 'markdown' that is actually unconverted HTML
@jigangz jigangz marked this pull request as ready for review March 28, 2026 06:00
@jigangz

jigangz commented Mar 29, 2026

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@VANDRANKI VANDRANKI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good fix for a real production issue. SEC EDGAR filings and similar regulatory documents are exactly the kind of deeply nested HTML that hits this in practice, and silently returning raw HTML instead of markdown is a worse outcome than a graceful fallback with a warning. The approach is correct.

Suggestion: move import warnings to the top of the file

import warnings is currently inside the except RecursionError block. Imports inside exception handlers work correctly in Python but are unconventional: the import happens at exception time rather than at module load time, and static analysis tools may flag it. Moving it to the top of the file with the other imports costs nothing and keeps the import layout consistent with the rest of the codebase.

Question: test recursion depth is environment-dependent

The test constructs HTML with 500 levels of nesting and comments that this is "deep enough to trigger RecursionError". Whether 500 levels actually triggers the error depends on:

  • Python's current sys.getrecursionlimit() (default 1000, but varies by environment)
  • The number of stack frames markdownify consumes per DOM level (typically more than 1)

In some environments (PyPy, or Python builds with a raised recursion limit), 500 levels may not trigger the error at all, causing the test to pass the fallback assertion vacuously. A more reliable approach is to temporarily lower the recursion limit in the test itself:

import sys
old_limit = sys.getrecursionlimit()
try:
    sys.setrecursionlimit(100)
    result = markitdown.convert_stream(...)
finally:
    sys.setrecursionlimit(old_limit)

This makes the test deterministic regardless of the environment's default limit.

Suggestion: no way to opt out of the fallback

The fallback to get_text() is always applied when a RecursionError occurs. For callers who need markdown specifically (not plain text) and would prefer an exception over degraded output, there is currently no way to opt out. A strict=False parameter on HtmlConverter that, when set to True, re-raises the RecursionError instead of falling back would give callers the choice. Not blocking for this PR, but worth a follow-up issue.


The core fix is correct and the test covers the main case well. The import placement is a quick cleanup and the recursion depth concern is worth resolving to make the test reliable across environments.

- Move 'import warnings' to module top level (was inside except block)
- Make test environment-independent by temporarily lowering
  sys.setrecursionlimit(200) instead of relying on depth=500 being
  sufficient on all platforms; original limit restored in finally block
- Add strict=True keyword argument to opt out of the plain-text
  fallback and let RecursionError propagate to the caller

@jigangz jigangz left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough review! I've addressed all three points in the latest commit:

  1. import warnings moved to top of file — removed the inline import from inside the except block and added it at the module level.

  2. Environment-independent test — the test now temporarily lowers sys.setrecursionlimit(200) (restoring it in a finally block) so the RecursionError is guaranteed regardless of the host's default limit. This makes it reliable on all platforms and CI environments.

  3. strict parameter addedconvert() now accepts a strict=True keyword argument. When set, the RecursionError is re-raised instead of falling back to plain-text extraction, giving callers explicit opt-out control.

@VANDRANKI

Copy link
Copy Markdown

All three points from the original review are addressed. The implementation looks correct:

  • import warnings moved to module level - clean
  • sys.setrecursionlimit(200) + finally restore - exactly what was needed to make the test deterministic across CI environments
  • strict=True parameter via kwargs.pop before forwarding to _CustomMarkdownify - the right pattern; no risk of markdownify receiving an unexpected keyword
  • warnings.warn(..., stacklevel=2) - correct stacklevel so the warning points at the caller, not the converter internals

One minor nit: the test assertions use result.text_content which is the soft-deprecated alias for result.markdown. New code in the same file uses result.markdown directly. Not a blocker, but worth updating for consistency:

# Current:
assert "Deep content" in result.text_content

# Preferred:
assert "Deep content" in result.markdown

LGTM otherwise. The fallback is safe, the strict opt-out is well-designed, and the test is solid.

@VANDRANKI

Copy link
Copy Markdown

Perfect - result.markdown is now used consistently throughout the test. Everything is clean. Fully LGTM.

@jigangz

jigangz commented Apr 15, 2026

Copy link
Copy Markdown
Contributor Author

Hi @afourney, sorry for the ping — this PR has been sitting for a couple weeks. CI workflows are pending maintainer approval (action_required). The fix addresses a silent-failure bug (#1636) where large, deeply-nested HTML documents like SEC EDGAR filings return raw HTML instead of markdown. Would you mind triggering the workflow run when you have a moment? Thanks!

@afourney afourney merged commit 604bba1 into microsoft:main Apr 15, 2026
3 checks passed
Ahoo-Wang added a commit to Ahoo-Wang/markitdown that referenced this pull request Jun 1, 2026
* Resolved an issue with linked images in docx [mammoth] (microsoft#1405)

* Fixed documentation typos in _base_converter.py (microsoft#1393)

* Ensure safe ExifTool usage: require >= 12.24 (microsoft#1399)

* feat: add version verification for ExifTool to ensure security compliance
* fix: improve ExifTool version verification

---------

* Bump actions/checkout from 4 to 5 (microsoft#1394)

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.

* Add HTML support to DocumentIntelligenceConverter (microsoft#1352)

* fix: correctly pass custom llm prompt parameter (microsoft#1319)

* fix: correctly pass custom llm prompt parameter

* Update README.md (microsoft#1335)

Fix typo in README.md

* Update README.md (microsoft#1350)

ISSUE microsoft#1339

* Update README.md (microsoft#1191)

Fix: Subtle spelling mistake fixed.

* Adding support for data-src Attribute (microsoft#1226)

* supportfordata-src

* docs: correct minor typos (microsoft#1173)

* fix docx parse error(\n in alt) (microsoft#1163)

* Handle PPTX shapes where position is None (microsoft#1161)

* Handle shapes where position is None
* Fixed recursion error, and place no-coord shapes at front

* feat: add checkbox support to Markdown converter (microsoft#1208)

This change introduces functionality to convert HTML checkbox input elements
(<input type=checkbox>) into Markdown checkbox syntax ([ ] or [x]).
Co-authored-by: Meirna Kamal <meirna.kamal@vodafone.com>

* Test if mammoth resolves rlinks. (microsoft#1451)

* Upgrade mammoth to 1.11.0 (microsoft#1452)

* Bump versions of mammoth and pdfminer.six (microsoft#1492)

* Updated pyproject to require a minimum version of pdfminer.six to ensure CVE-2025-64512 is patched.

* [MS] Update PDF table extraction to support aligned Markdown (microsoft#1499)

* Added PDF table extraction feature with aligned Markdown (microsoft#1419)

* Add PDF test files and enhance extraction tests

- Added a medical report scan PDF for testing scanned PDF handling.
- Included a retail purchase receipt PDF to validate receipt extraction functionality.
- Introduced a multipage invoice PDF to test extraction of complex invoice structures.
- Added a borderless table PDF for testing inventory reconciliation report extraction.
- Implemented comprehensive tests for PDF table extraction, ensuring proper structure and data integrity.
- Enhanced existing tests to validate the order and presence of extracted content across various PDF types.

* fix: update dependencies for PDF processing and improve table extraction logic

* Bumped version of pdfminer.six
---------

Authored-by: Ashok <ashh010101@gmail.com>

* Fix: PDF parsing doesn't support partially numbered lists (microsoft#1525)

* Fix: PDF parsing doesn't support partially numbered lists

* Refactor: Move import of PARTIAL_NUMBERING_PATTERN to the top of the test file

* Refactor: Improve assertion formatting in partial numbering tests

* [MS] Extend table support for wide tables (microsoft#1552)

* feat: enhance PDF table extraction to support complex forms and add new test cases
* feat: enhance PDF table extraction with adaptive column clustering and add comprehensive test cases
* fix: correct formatting and improve assertions in PDF table tests

* Add text/markdown to Accept header (microsoft#1554)

* Remove onnxruntime<=1.20.1 Windows pin (microsoft#1551)

* Bump version for release. (microsoft#1564)

* [MS] Add OCR layer service for embedded images and PDF scans (microsoft#1541)

* Add OCR test data and implement tests for various document formats

- Created HTML file with multiple images for testing OCR extraction.
- Added several PDF files with different layouts and image placements to validate OCR functionality.
- Introduced PPTX files with complex layouts and images at various positions for comprehensive testing.
- Included XLSX files with multiple images and complex layouts to ensure accurate OCR extraction.
- Implemented a new test suite in `test_ocr.py` to validate OCR functionality across all document types, ensuring context preservation and accuracy.

* Enhance OCR functionality and validation in document converters

- Refactor image extraction and processing in PDF, PPTX, and XLSX converters for improved readability and consistency.
- Implement detailed validation for OCR text positioning relative to surrounding text in test cases.
- Introduce comprehensive tests for expected OCR results across various document types, ensuring no base64 images are present.
- Improve error handling and logging for better debugging during OCR extraction.

* Add support for scanned PDFs with full-page OCR fallback and implement tests

* Bump version to 0.1.6b1 in __about__.py

* Refactor OCR services to support LLM Vision, update README and tests accordingly

* Add OCR-enabled converters and ensure consistent OCR format across document types

* Refactor converters to improve import organization and enhance OCR functionality across DOCX, PDF, PPTX, and XLSX converters

* Refactor exception imports for consistency across converters and tests

* Fix OCR tests to match MockOCRService output and fix cross-platform file URI handling

* Bump version to 0.1.6b1 in __about__.py

* Skip DOCX/XLSX/PPTX OCR tests when optional dependencies are missing

* Add comprehensive OCR test suite for various document formats

- Introduced multiple test documents for PDF, DOCX, XLSX, and PPTX formats, covering scenarios with images at the start, middle, and end.
- Implemented tests for complex layouts, multi-page documents, and documents with multiple images.
- Created a new test script `test_ocr.py` to validate OCR functionality, ensuring context preservation and accurate text extraction.
- Added expected OCR results for validation against ground truth.
- Included tests for scanned documents to verify OCR fallback mechanisms.

* Remove obsolete HTML test files and refactor test cases for file URIs and OCR format consistency

- Deleted `html_image_start.html` and `html_multiple_images.html` as they are no longer needed.
- Updated `test_file_uris` in `test_module_misc.py` to simplify assertions by removing unnecessary `url2pathname` usage.
- Removed `test_ocr_format_consistency.py` as it is no longer relevant to the current testing framework.

* Refactor OCR processing in PdfConverterWithOCR and enhance unit tests for multipage PDFs

* Revert

* Revert

* Update REDMEs

* Refactor import statements for consistency and improve formatting in converter and test files

* Fix O(n) memory growth in PDF conversion by calling page.close() afte… (microsoft#1612)

* Fix O(n) memory growth in PDF conversion by calling page.close() after each page

* Refactor PDF memory optimization tests for improved readability and consistency

* Add memory benchmarking tests for PDF conversion with page.close() fix

* Remove unnecessary blank lines in PDF memory optimization tests for cleaner code

* Bump version to 0.1.6b2 in __about__.py

* Update PDF conversion tests to include mimetype in StreamInfo

* Updated warning about binding to non-local interfaces. (microsoft#1653)

* fix: handle deeply nested HTML that triggers RecursionError (microsoft#1644)

* fix: handle deeply nested HTML that triggers RecursionError (microsoft#1636)

Large HTML files with deep DOM nesting (e.g., SEC EDGAR filings) cause
markdownify's recursive DOM traversal to exceed Python's default
recursion limit (1000). Previously this RecursionError was caught by
the top-level _convert() dispatcher, which then fell through to
PlainTextConverter — silently returning the raw HTML as 'markdown'
with no warning.

This fix catches RecursionError in HtmlConverter.convert() and falls
back to BeautifulSoup's iterative get_text() method, which handles
arbitrary nesting depths. A warning is emitted so callers know the
output is plain text rather than full markdown.

Root cause chain:
1. HtmlConverter.convert() calls markdownify.convert_soup() (recursive)
2. Deeply nested HTML (>~400 levels) triggers RecursionError
3. _convert() catches all Exceptions, stores in failed_attempts
4. PlainTextConverter.accepts() matches text/html via 'text/' prefix
5. PlainTextConverter.convert() returns raw HTML bytes as text
6. Caller receives 'markdown' that is actually unconverted HTML

* refactor: address review feedback on RecursionError fallback

- Move 'import warnings' to module top level (was inside except block)
- Make test environment-independent by temporarily lowering
  sys.setrecursionlimit(200) instead of relying on depth=500 being
  sufficient on all platforms; original limit restored in finally block
- Add strict=True keyword argument to opt out of the plain-text
  fallback and let RecursionError propagate to the caller

* test: use result.markdown instead of deprecated result.text_content

---------

Co-authored-by: jigangz <jigangz@github.com>

* Clarify security posture in READMEs (microsoft#1807)

* feat: Add Azure Content Understanding converter (microsoft#1865)

* inital version

* improve mime type detection

* prebuilt-image custom analzyer route to image

* enhance cu priority over di

* fix: apply black formatting

* update cache of known prebuilt name and README improvement

* add test cases, run black

* update readme and deriving content_type from the resolved file_type

* update readme

* Bump version to 0.1.6 (microsoft#1914)

---------

Co-authored-by: afourney <adamfo@microsoft.com>
Co-authored-by: JonahDelman <jonah.delman@gmail.com>
Co-authored-by: t3tra <admin@t3tra.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: safen0s <99965118+safen0s@users.noreply.github.com>
Co-authored-by: Stefan Rink <stefan-rink@users.noreply.github.com>
Co-authored-by: [W]DOS_ <40659600+W-DOS0@users.noreply.github.com>
Co-authored-by: Utkarsh kumar <m83610278@gmail.com>
Co-authored-by: Ebrahim Tayabali <47640402+ebrahimHakimuddin@users.noreply.github.com>
Co-authored-by: Noah Zhu <118643158+Noah-Zhuhaotian@users.noreply.github.com>
Co-authored-by: Dmitry <98899785+mdqst@users.noreply.github.com>
Co-authored-by: Yuzhong Zhang <141388234+BetterAndBetterII@users.noreply.github.com>
Co-authored-by: Richard Ye <33409792+richardye101@users.noreply.github.com>
Co-authored-by: Meirna <61427701+Meirna-kamal@users.noreply.github.com>
Co-authored-by: Meirna Kamal <meirna.kamal@vodafone.com>
Co-authored-by: lesyk <lesyk@users.noreply.github.com>
Co-authored-by: Bas Nijholt <basnijholt@gmail.com>
Co-authored-by: jigangz <115519042+jigangz@users.noreply.github.com>
Co-authored-by: jigangz <jigangz@github.com>
Co-authored-by: Chien Yuan Chang <ds.chienyuanchang@gmail.com>
Ahoo-Wang added a commit to Ahoo-Wang/markitdown that referenced this pull request Jun 1, 2026
* Resolved an issue with linked images in docx [mammoth] (microsoft#1405)

* Fixed documentation typos in _base_converter.py (microsoft#1393)

* Ensure safe ExifTool usage: require >= 12.24 (microsoft#1399)

* feat: add version verification for ExifTool to ensure security compliance
* fix: improve ExifTool version verification

---------

* Bump actions/checkout from 4 to 5 (microsoft#1394)

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.

* Add HTML support to DocumentIntelligenceConverter (microsoft#1352)

* fix: correctly pass custom llm prompt parameter (microsoft#1319)

* fix: correctly pass custom llm prompt parameter

* Update README.md (microsoft#1335)

Fix typo in README.md

* Update README.md (microsoft#1350)

ISSUE microsoft#1339

* Update README.md (microsoft#1191)

Fix: Subtle spelling mistake fixed.

* Adding support for data-src Attribute (microsoft#1226)

* supportfordata-src

* docs: correct minor typos (microsoft#1173)

* fix docx parse error(\n in alt) (microsoft#1163)

* Handle PPTX shapes where position is None (microsoft#1161)

* Handle shapes where position is None
* Fixed recursion error, and place no-coord shapes at front

* feat: add checkbox support to Markdown converter (microsoft#1208)

This change introduces functionality to convert HTML checkbox input elements
(<input type=checkbox>) into Markdown checkbox syntax ([ ] or [x]).
Co-authored-by: Meirna Kamal <meirna.kamal@vodafone.com>

* Test if mammoth resolves rlinks. (microsoft#1451)

* Upgrade mammoth to 1.11.0 (microsoft#1452)

* Bump versions of mammoth and pdfminer.six (microsoft#1492)

* Updated pyproject to require a minimum version of pdfminer.six to ensure CVE-2025-64512 is patched.

* [MS] Update PDF table extraction to support aligned Markdown (microsoft#1499)

* Added PDF table extraction feature with aligned Markdown (microsoft#1419)

* Add PDF test files and enhance extraction tests

- Added a medical report scan PDF for testing scanned PDF handling.
- Included a retail purchase receipt PDF to validate receipt extraction functionality.
- Introduced a multipage invoice PDF to test extraction of complex invoice structures.
- Added a borderless table PDF for testing inventory reconciliation report extraction.
- Implemented comprehensive tests for PDF table extraction, ensuring proper structure and data integrity.
- Enhanced existing tests to validate the order and presence of extracted content across various PDF types.

* fix: update dependencies for PDF processing and improve table extraction logic

* Bumped version of pdfminer.six
---------

Authored-by: Ashok <ashh010101@gmail.com>

* Fix: PDF parsing doesn't support partially numbered lists (microsoft#1525)

* Fix: PDF parsing doesn't support partially numbered lists

* Refactor: Move import of PARTIAL_NUMBERING_PATTERN to the top of the test file

* Refactor: Improve assertion formatting in partial numbering tests

* [MS] Extend table support for wide tables (microsoft#1552)

* feat: enhance PDF table extraction to support complex forms and add new test cases
* feat: enhance PDF table extraction with adaptive column clustering and add comprehensive test cases
* fix: correct formatting and improve assertions in PDF table tests

* Add text/markdown to Accept header (microsoft#1554)

* Remove onnxruntime<=1.20.1 Windows pin (microsoft#1551)

* Bump version for release. (microsoft#1564)

* [MS] Add OCR layer service for embedded images and PDF scans (microsoft#1541)

* Add OCR test data and implement tests for various document formats

- Created HTML file with multiple images for testing OCR extraction.
- Added several PDF files with different layouts and image placements to validate OCR functionality.
- Introduced PPTX files with complex layouts and images at various positions for comprehensive testing.
- Included XLSX files with multiple images and complex layouts to ensure accurate OCR extraction.
- Implemented a new test suite in `test_ocr.py` to validate OCR functionality across all document types, ensuring context preservation and accuracy.

* Enhance OCR functionality and validation in document converters

- Refactor image extraction and processing in PDF, PPTX, and XLSX converters for improved readability and consistency.
- Implement detailed validation for OCR text positioning relative to surrounding text in test cases.
- Introduce comprehensive tests for expected OCR results across various document types, ensuring no base64 images are present.
- Improve error handling and logging for better debugging during OCR extraction.

* Add support for scanned PDFs with full-page OCR fallback and implement tests

* Bump version to 0.1.6b1 in __about__.py

* Refactor OCR services to support LLM Vision, update README and tests accordingly

* Add OCR-enabled converters and ensure consistent OCR format across document types

* Refactor converters to improve import organization and enhance OCR functionality across DOCX, PDF, PPTX, and XLSX converters

* Refactor exception imports for consistency across converters and tests

* Fix OCR tests to match MockOCRService output and fix cross-platform file URI handling

* Bump version to 0.1.6b1 in __about__.py

* Skip DOCX/XLSX/PPTX OCR tests when optional dependencies are missing

* Add comprehensive OCR test suite for various document formats

- Introduced multiple test documents for PDF, DOCX, XLSX, and PPTX formats, covering scenarios with images at the start, middle, and end.
- Implemented tests for complex layouts, multi-page documents, and documents with multiple images.
- Created a new test script `test_ocr.py` to validate OCR functionality, ensuring context preservation and accurate text extraction.
- Added expected OCR results for validation against ground truth.
- Included tests for scanned documents to verify OCR fallback mechanisms.

* Remove obsolete HTML test files and refactor test cases for file URIs and OCR format consistency

- Deleted `html_image_start.html` and `html_multiple_images.html` as they are no longer needed.
- Updated `test_file_uris` in `test_module_misc.py` to simplify assertions by removing unnecessary `url2pathname` usage.
- Removed `test_ocr_format_consistency.py` as it is no longer relevant to the current testing framework.

* Refactor OCR processing in PdfConverterWithOCR and enhance unit tests for multipage PDFs

* Revert

* Revert

* Update REDMEs

* Refactor import statements for consistency and improve formatting in converter and test files

* Fix O(n) memory growth in PDF conversion by calling page.close() afte… (microsoft#1612)

* Fix O(n) memory growth in PDF conversion by calling page.close() after each page

* Refactor PDF memory optimization tests for improved readability and consistency

* Add memory benchmarking tests for PDF conversion with page.close() fix

* Remove unnecessary blank lines in PDF memory optimization tests for cleaner code

* Bump version to 0.1.6b2 in __about__.py

* Update PDF conversion tests to include mimetype in StreamInfo

* Updated warning about binding to non-local interfaces. (microsoft#1653)

* fix: handle deeply nested HTML that triggers RecursionError (microsoft#1644)

* fix: handle deeply nested HTML that triggers RecursionError (microsoft#1636)

Large HTML files with deep DOM nesting (e.g., SEC EDGAR filings) cause
markdownify's recursive DOM traversal to exceed Python's default
recursion limit (1000). Previously this RecursionError was caught by
the top-level _convert() dispatcher, which then fell through to
PlainTextConverter — silently returning the raw HTML as 'markdown'
with no warning.

This fix catches RecursionError in HtmlConverter.convert() and falls
back to BeautifulSoup's iterative get_text() method, which handles
arbitrary nesting depths. A warning is emitted so callers know the
output is plain text rather than full markdown.

Root cause chain:
1. HtmlConverter.convert() calls markdownify.convert_soup() (recursive)
2. Deeply nested HTML (>~400 levels) triggers RecursionError
3. _convert() catches all Exceptions, stores in failed_attempts
4. PlainTextConverter.accepts() matches text/html via 'text/' prefix
5. PlainTextConverter.convert() returns raw HTML bytes as text
6. Caller receives 'markdown' that is actually unconverted HTML

* refactor: address review feedback on RecursionError fallback

- Move 'import warnings' to module top level (was inside except block)
- Make test environment-independent by temporarily lowering
  sys.setrecursionlimit(200) instead of relying on depth=500 being
  sufficient on all platforms; original limit restored in finally block
- Add strict=True keyword argument to opt out of the plain-text
  fallback and let RecursionError propagate to the caller

* test: use result.markdown instead of deprecated result.text_content

---------

Co-authored-by: jigangz <jigangz@github.com>

* Clarify security posture in READMEs (microsoft#1807)

* feat: Add Azure Content Understanding converter (microsoft#1865)

* inital version

* improve mime type detection

* prebuilt-image custom analzyer route to image

* enhance cu priority over di

* fix: apply black formatting

* update cache of known prebuilt name and README improvement

* add test cases, run black

* update readme and deriving content_type from the resolved file_type

* update readme

* Bump version to 0.1.6 (microsoft#1914)

* chore(api): remove package metadata

---------

Co-authored-by: afourney <adamfo@microsoft.com>
Co-authored-by: JonahDelman <jonah.delman@gmail.com>
Co-authored-by: t3tra <admin@t3tra.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: safen0s <99965118+safen0s@users.noreply.github.com>
Co-authored-by: Stefan Rink <stefan-rink@users.noreply.github.com>
Co-authored-by: [W]DOS_ <40659600+W-DOS0@users.noreply.github.com>
Co-authored-by: Utkarsh kumar <m83610278@gmail.com>
Co-authored-by: Ebrahim Tayabali <47640402+ebrahimHakimuddin@users.noreply.github.com>
Co-authored-by: Noah Zhu <118643158+Noah-Zhuhaotian@users.noreply.github.com>
Co-authored-by: Dmitry <98899785+mdqst@users.noreply.github.com>
Co-authored-by: Yuzhong Zhang <141388234+BetterAndBetterII@users.noreply.github.com>
Co-authored-by: Richard Ye <33409792+richardye101@users.noreply.github.com>
Co-authored-by: Meirna <61427701+Meirna-kamal@users.noreply.github.com>
Co-authored-by: Meirna Kamal <meirna.kamal@vodafone.com>
Co-authored-by: lesyk <lesyk@users.noreply.github.com>
Co-authored-by: Bas Nijholt <basnijholt@gmail.com>
Co-authored-by: jigangz <115519042+jigangz@users.noreply.github.com>
Co-authored-by: jigangz <jigangz@github.com>
Co-authored-by: Chien Yuan Chang <ds.chienyuanchang@gmail.com>
claymorenjoyer pushed a commit to claymorenjoyer/markitdowngui that referenced this pull request Jun 15, 2026
…t#1644)

* fix: handle deeply nested HTML that triggers RecursionError (microsoft#1636)

Large HTML files with deep DOM nesting (e.g., SEC EDGAR filings) cause
markdownify's recursive DOM traversal to exceed Python's default
recursion limit (1000). Previously this RecursionError was caught by
the top-level _convert() dispatcher, which then fell through to
PlainTextConverter — silently returning the raw HTML as 'markdown'
with no warning.

This fix catches RecursionError in HtmlConverter.convert() and falls
back to BeautifulSoup's iterative get_text() method, which handles
arbitrary nesting depths. A warning is emitted so callers know the
output is plain text rather than full markdown.

Root cause chain:
1. HtmlConverter.convert() calls markdownify.convert_soup() (recursive)
2. Deeply nested HTML (>~400 levels) triggers RecursionError
3. _convert() catches all Exceptions, stores in failed_attempts
4. PlainTextConverter.accepts() matches text/html via 'text/' prefix
5. PlainTextConverter.convert() returns raw HTML bytes as text
6. Caller receives 'markdown' that is actually unconverted HTML

* refactor: address review feedback on RecursionError fallback

- Move 'import warnings' to module top level (was inside except block)
- Make test environment-independent by temporarily lowering
  sys.setrecursionlimit(200) instead of relying on depth=500 being
  sufficient on all platforms; original limit restored in finally block
- Add strict=True keyword argument to opt out of the plain-text
  fallback and let RecursionError propagate to the caller

* test: use result.markdown instead of deprecated result.text_content

---------

Co-authored-by: jigangz <jigangz@github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large HTML files (>3MB) silently return unconverted HTML

3 participants