markitdown-pro 2.0.0__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/.github/workflows/test.yml +4 -0
  2. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/.gitignore +1 -0
  3. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/PKG-INFO +1 -1
  4. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/conversion_pipeline.py +21 -2
  5. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/gotenberg_converter.py +40 -11
  6. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/office_handler.py +32 -1
  7. markitdown_pro-2.1.0/tests/data/powerpoint-legacy/geometry-colors.ppt +0 -0
  8. markitdown_pro-2.1.0/tests/data/powerpoint-legacy/lorem-ipsum-only-text.ppt +0 -0
  9. markitdown_pro-2.1.0/tests/data/powerpoint-legacy/lorem-ipsum-scanned-text.ppt +0 -0
  10. markitdown_pro-2.1.0/tests/data/powerpoint-legacy/lorem-ipsum-text-with-image.ppt +0 -0
  11. markitdown_pro-2.1.0/tests/data/powerpoint-legacy/lorem-ipsum-text-with-scanned-text.ppt +0 -0
  12. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/test_expectations.yaml +56 -0
  13. markitdown_pro-2.1.0/tests/data/word-legacy/geometry-colors.doc +0 -0
  14. markitdown_pro-2.1.0/tests/data/word-legacy/geometry-colors.odt +0 -0
  15. markitdown_pro-2.1.0/tests/data/word-legacy/geometry-colors.rtf +2197 -0
  16. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-only-text.doc +0 -0
  17. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-only-text.odt +0 -0
  18. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-only-text.rtf +258 -0
  19. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-scanned-text.doc +0 -0
  20. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-scanned-text.odt +0 -0
  21. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-scanned-text.rtf +18373 -0
  22. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-text-with-image.doc +0 -0
  23. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-text-with-image.odt +0 -0
  24. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-text-with-image.rtf +2204 -0
  25. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.doc +0 -0
  26. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.odt +0 -0
  27. markitdown_pro-2.1.0/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.rtf +7208 -0
  28. markitdown_pro-2.1.0/tests/integration/test_legacy_office_formats.py +108 -0
  29. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_conversion_pipeline.py +74 -2
  30. markitdown_pro-2.1.0/tests/unit/test_gotenberg_converter.py +69 -0
  31. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_office_handler.py +62 -0
  32. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/uv.lock +0 -1
  33. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/.github/workflows/lint.yaml +0 -0
  34. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/.github/workflows/publish.yaml +0 -0
  35. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/.vscode/settings.json +0 -0
  36. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/README.md +0 -0
  37. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/__init__.py +0 -0
  38. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/__init__.py +0 -0
  39. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/common/__init__.py +0 -0
  40. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/common/config.py +0 -0
  41. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/common/isolated_worker.py +0 -0
  42. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/common/logger.py +0 -0
  43. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/common/schemas.py +0 -0
  44. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/common/utils.py +0 -0
  45. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/__init__.py +0 -0
  46. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/base.py +0 -0
  47. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/doc_intel_converter.py +0 -0
  48. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/gpt_vision_converter.py +0 -0
  49. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/markitdown_converter.py +0 -0
  50. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/pymupdf_converter.py +0 -0
  51. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/speech_converter.py +0 -0
  52. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/tabular_converter.py +0 -0
  53. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/unstructured_converter.py +0 -0
  54. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/converters/youtube_converter.py +0 -0
  55. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/__init__.py +0 -0
  56. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/audio_handler.py +0 -0
  57. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/base_handler.py +0 -0
  58. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/email_handler.py +0 -0
  59. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/epub_handler.py +0 -0
  60. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/image_handler.py +0 -0
  61. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/ipynb_handler.py +0 -0
  62. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/markup_handler.py +0 -0
  63. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/page_pdf_converter.py +0 -0
  64. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/pdf_handler.py +0 -0
  65. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/pst_handler.py +0 -0
  66. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/tabular_handler.py +0 -0
  67. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/handlers/text_handler.py +0 -0
  68. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/services/__init__.py +0 -0
  69. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/services/azure_doc_intelligence.py +0 -0
  70. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/services/azure_speech.py +0 -0
  71. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/markitdown_pro/services/openai_services.py +0 -0
  72. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/pyproject.toml +0 -0
  73. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/__init__.py +0 -0
  74. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/common/__init__.py +0 -0
  75. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/common/test_config.py +0 -0
  76. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/conftest.py +0 -0
  77. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/conversion_pipeline/__init__.py +0 -0
  78. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/conversion_pipeline/test_conversion_pipeline.py +0 -0
  79. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/converters/test_azure_doc_intel_wrapper.py +0 -0
  80. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/converters/test_azure_speech_wrapper.py +0 -0
  81. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/converters/test_doc_intel_lifecycle.py +0 -0
  82. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/converters/test_gpt_vision_config.py +0 -0
  83. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/converters/test_gpt_vision_wrapper.py +0 -0
  84. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/converters/test_pymupdf_wrapper.py +0 -0
  85. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/converters/test_pymupdf_wrapper_async.py +0 -0
  86. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/converters/test_unstructured_io_wrapper.py +0 -0
  87. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/audio/the-power-of-small-steps_cn.wav +0 -0
  88. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/audio/the-power-of-small-steps_en.mp3 +0 -0
  89. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/audio/the-power-of-small-steps_en.wav +0 -0
  90. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/audio/the-power-of-small-steps_es.mp3 +0 -0
  91. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/audio/the-power-of-small-steps_es.wav +0 -0
  92. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/audio/the-power-of-small-steps_it.wav +0 -0
  93. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/audio/the-power-of-small-steps_jp.mp3 +0 -0
  94. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/audio/the-power-of-small-steps_jp.wav +0 -0
  95. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/eml/email-with-image.eml +0 -0
  96. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/eml/mime-different-plain-html.eml +0 -0
  97. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/epub/sample1.epub +0 -0
  98. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/images/flower.jpg +0 -0
  99. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/images/geometry_colors.bmp +0 -0
  100. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/images/handwriting.jpg +0 -0
  101. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/images/scanned_text.heic +0 -0
  102. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/images/scanned_text.png +0 -0
  103. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/images/text_with_image.jpg +0 -0
  104. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/markup/blog.html +0 -0
  105. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/markup/factbook.xml +0 -0
  106. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/markup/simple.json +0 -0
  107. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/markup/simple.yaml +0 -0
  108. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/msg/fake-email-multiple-attachments.msg +0 -0
  109. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/msg/fake-email.msg +0 -0
  110. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/pdf/geometry-colors.pdf +0 -0
  111. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/pdf/lorem-ipsum-only-images.pdf +0 -0
  112. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/pdf/lorem-ipsum-only-text.pdf +0 -0
  113. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/pdf/lorem-ipsum-scanned-text.pdf +0 -0
  114. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/pdf/lorem-ipsum-text-with-image.pdf +0 -0
  115. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/powerpoint/geometry-colors.pptx +0 -0
  116. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/powerpoint/lorem-ipsum-only-text.pptx +0 -0
  117. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/powerpoint/lorem-ipsum-scanned-text.pptx +0 -0
  118. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/powerpoint/lorem-ipsum-text-with-image.pptx +0 -0
  119. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/powerpoint/lorem-ipsum-text-with-scanned-text.pptx +0 -0
  120. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/tabular/2023-half-year-analyses-by-segment.xlsx +0 -0
  121. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/tabular/stanley-cups.csv +0 -0
  122. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/tabular/stanley-cups.tsv +0 -0
  123. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/tabular/stanley-cups.xlsx +0 -0
  124. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/text/book-war-and-peace-1p.txt +0 -0
  125. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/text/fake-email.txt +0 -0
  126. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/text/logger.py +0 -0
  127. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/word/geometry-colors.docx +0 -0
  128. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/word/lorem-ipsum-only-text.docx +0 -0
  129. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/word/lorem-ipsum-scanned-text.docx +0 -0
  130. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/word/lorem-ipsum-text-with-image.docx +0 -0
  131. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/data/word/lorem-ipsum-text-with-scanned-text.docx +0 -0
  132. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/fixtures.py +0 -0
  133. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/__init__.py +0 -0
  134. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_email_handler.py +0 -0
  135. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_epub_handler.py +0 -0
  136. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_image_handler.py +0 -0
  137. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_ipynb_handler.py +0 -0
  138. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_markitdown_handler.py +0 -0
  139. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_markitdown_validation.py +0 -0
  140. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_markup_handler.py +0 -0
  141. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_page_pdf_converter.py +0 -0
  142. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_pdf_handler.py +0 -0
  143. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_pst_handler.py +0 -0
  144. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_tabular_handler.py +0 -0
  145. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/handlers/test_text_handler.py +0 -0
  146. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/__init__.py +0 -0
  147. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/cheat_sheet.py +0 -0
  148. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/runner.py +0 -0
  149. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_audio_handler.py +0 -0
  150. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_doc_intelligence_converter.py +0 -0
  151. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_email_handler.py +0 -0
  152. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_gotenberg_converter.py +0 -0
  153. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_gpt_vision_converter.py +0 -0
  154. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_markitdown_converter.py +0 -0
  155. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_markup_handler.py +0 -0
  156. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_pdf_handler.py +0 -0
  157. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_pymupdf_converter.py +0 -0
  158. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_tabular_handler.py +0 -0
  159. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/integration/test_text_handler.py +0 -0
  160. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/page_count/__init__.py +0 -0
  161. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/page_count/test_pdf_page_count.py +0 -0
  162. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/__init__.py +0 -0
  163. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_audio_handler.py +0 -0
  164. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_base_converter.py +0 -0
  165. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_doc_intel_converter.py +0 -0
  166. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_gpt_vision_converter.py +0 -0
  167. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_image_handler.py +0 -0
  168. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_markitdown_converter.py +0 -0
  169. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_page_pdf_converter.py +0 -0
  170. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_pdf_handler.py +0 -0
  171. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_pymupdf_converter.py +0 -0
  172. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/unit/test_speech_converter.py +0 -0
  173. {markitdown_pro-2.0.0 → markitdown_pro-2.1.0}/tests/utils.py +0 -0
@@ -11,6 +11,10 @@ on:
11
11
  pull_request:
12
12
  branches:
13
13
  - main
14
+ paths:
15
+ - 'markitdown_pro/**'
16
+ - 'tests/**'
17
+ - 'pyproject.toml'
14
18
 
15
19
  permissions:
16
20
  contents: read
@@ -24,3 +24,4 @@ docs/
24
24
  !./CLAUDE.md
25
25
  **/CONTINUITY.md
26
26
  **/CHANGELOG.md
27
+ .mcp.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markitdown-pro
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: A package that converts almost any file format to Markdown.
5
5
  Author: Developer
6
6
  License-Expression: MIT
@@ -152,21 +152,40 @@ class ConversionPipeline:
152
152
  ".pdf": self.pdf_handler,
153
153
  ".xls": self.tabular_handler,
154
154
  ".xlsx": self.tabular_handler,
155
- ".docx": self.office_handler,
156
- ".pptx": self.office_handler,
157
155
  },
158
156
  }
159
157
 
158
+ # Register Office extensions dynamically — the handler decides which
159
+ # legacy formats it supports based on Gotenberg availability. This
160
+ # keeps ``handlers_mapping`` (what consumers read) truthful at runtime.
161
+ for ext in self.office_handler.supported_extensions:
162
+ self.handlers_categories[ExtensionCategory.FORMATTED_DOCS][ext] = self.office_handler
163
+
160
164
  # Normalize map of extensions to handlers
161
165
  self.handlers_mapping: dict[str, BaseHandler] = {}
162
166
  for handlers in self.handlers_categories.values():
163
167
  for ext, handler in handlers.items():
164
168
  self.handlers_mapping[ext] = handler
165
169
 
170
+ # Cache the read-only view so consumers get a stable object (and we
171
+ # don't rebuild the frozenset on every ``.supported_extensions`` access).
172
+ # ``handlers_mapping`` is read-only by convention.
173
+ self._supported_extensions: frozenset[str] = frozenset(self.handlers_mapping)
174
+
166
175
  # ---------------------------------------------------------------------
167
176
  # Public APIs
168
177
  # ---------------------------------------------------------------------
169
178
 
179
+ @property
180
+ def supported_extensions(self) -> frozenset[str]:
181
+ """Canonical set of extensions this pipeline can route.
182
+
183
+ This is the source of truth for consumers advertising supported
184
+ formats (e.g., a ``/extensions`` endpoint). ``handlers_mapping`` is
185
+ the routing table; ``supported_extensions`` is its read-only view.
186
+ """
187
+ return self._supported_extensions
188
+
170
189
  async def convert_document_to_md(
171
190
  self,
172
191
  file_path: str | Path,
@@ -5,11 +5,14 @@ Gotenberg (https://gotenberg.dev) is a Docker-based API that wraps LibreOffice
5
5
  for document-to-PDF conversion. It handles concurrency internally and scales
6
6
  horizontally via container replicas.
7
7
 
8
- Configure via environment variable:
9
- GOTENBERG_URL=http://gotenberg:3000 (default)
10
-
11
- If the Gotenberg service is unreachable, convert() returns None and the caller
12
- should fall through to the next converter.
8
+ Configure via the ``GOTENBERG_URL`` environment variable or pass
9
+ ``gotenberg_url=...`` explicitly. If neither is provided, the converter is
10
+ disabled and ``convert()`` returns ``None`` without any network I/O — this lets
11
+ downstream consumers advertise supported formats accurately.
12
+
13
+ If the Gotenberg service is configured but unreachable at call time,
14
+ ``convert()`` returns ``None`` and the caller should fall through to the next
15
+ converter.
13
16
  """
14
17
 
15
18
  import contextlib
@@ -23,7 +26,6 @@ from ..common.logger import logger
23
26
  from .base import Converter
24
27
 
25
28
  _COMPONENT = "GotenbergConverter"
26
- _DEFAULT_URL = "http://gotenberg:3000"
27
29
  _CONVERT_ENDPOINT = "/forms/libreoffice/convert"
28
30
 
29
31
 
@@ -36,21 +38,48 @@ class GotenbergConverter(Converter):
36
38
  DocIntelligence can extract from Office files directly.
37
39
 
38
40
  Requires a running Gotenberg instance. Gracefully returns None if
39
- the service is unavailable, allowing fallback to other converters.
41
+ the service is unavailable or if the converter is disabled, allowing
42
+ fallback to other converters.
40
43
  """
41
44
 
42
45
  SUPPORTED_EXTENSIONS = frozenset({".docx", ".pptx", ".doc", ".ppt", ".odt", ".rtf"})
43
46
 
44
47
  def __init__(self, gotenberg_url: str | None = None) -> None:
45
48
  super().__init__(_COMPONENT)
46
- self._url = gotenberg_url or os.getenv("GOTENBERG_URL", _DEFAULT_URL)
47
- self._convert_url = f"{self._url.rstrip('/')}{_CONVERT_ENDPOINT}"
49
+ # gotenberg_url is the single source of truth; ``enabled`` derives from it.
50
+ # Treat empty/whitespace values as unconfigured to catch a common
51
+ # ``docker-compose`` misconfiguration (``environment: GOTENBERG_URL=``).
52
+ raw = (gotenberg_url if gotenberg_url is not None else os.getenv("GOTENBERG_URL")) or ""
53
+ self.gotenberg_url: str | None = raw.strip() or None
54
+
55
+ @property
56
+ def enabled(self) -> bool:
57
+ return bool(self.gotenberg_url)
58
+
59
+ @property
60
+ def _convert_url(self) -> str:
61
+ """The fully-qualified Gotenberg endpoint URL.
62
+
63
+ Only safe to access when ``self.enabled`` is True. Callers in this
64
+ class are already guarded by the ``if not self.enabled`` short-circuit
65
+ in ``convert()``; raising here makes the invariant explicit instead
66
+ of letting a ``None`` propagate into ``httpx`` or the format string.
67
+ """
68
+ if not self.gotenberg_url:
69
+ raise RuntimeError(
70
+ "GotenbergConverter is not configured (set GOTENBERG_URL "
71
+ "or pass gotenberg_url=...). Check ``enabled`` before calling."
72
+ )
73
+ return f"{self.gotenberg_url.rstrip('/')}{_CONVERT_ENDPOINT}"
48
74
 
49
75
  async def convert(self, file_path: str) -> str | None:
76
+ if not self.enabled:
77
+ return None
78
+
50
79
  file_name = Path(file_path).name
51
80
  tag = f"{_COMPONENT} | {file_name}"
52
81
 
53
- logger.info(f"{tag} | converting to PDF via Gotenberg ({self._url})")
82
+ logger.info(f"{tag} | converting to PDF via Gotenberg ({self.gotenberg_url})")
54
83
 
55
84
  pdf_bytes = await self._convert_to_pdf(file_path, tag)
56
85
  if not pdf_bytes:
@@ -108,7 +137,7 @@ class GotenbergConverter(Converter):
108
137
  return None
109
138
 
110
139
  except httpx.ConnectError:
111
- logger.info(f"{tag} | Gotenberg not reachable at {self._url}, skipping")
140
+ logger.info(f"{tag} | Gotenberg not reachable at {self.gotenberg_url}, skipping")
112
141
  return None
113
142
  except httpx.TimeoutException:
114
143
  logger.warning(f"{tag} | Gotenberg timed out after 120s")
@@ -38,11 +38,22 @@ class OfficeHandler(BaseHandler):
38
38
  the pipeline order when initializing the handler.
39
39
 
40
40
  Supported formats: .docx, .pptx (modern Office XML formats).
41
- Old binary formats (.doc, .ppt, .odt, .rtf) supported via Gotenberg only.
41
+ Legacy binary formats (.doc, .ppt, .odt, .rtf) are supported at runtime
42
+ only when the configured pipeline contains an enabled ``GotenbergConverter``.
43
+ Callers should inspect the instance attribute ``supported_extensions`` (not
44
+ the class constant) to learn which formats this handler will actually
45
+ process.
42
46
  """
43
47
 
48
+ # Class constant stays narrow (the always-supported modern formats) so that
49
+ # ``BaseHandler.is_valid`` and any class-level introspection never falsely
50
+ # advertise legacy formats that would fail without Gotenberg. Dynamic
51
+ # runtime capability lives on the instance attribute ``supported_extensions``
52
+ # set in ``__init__``.
44
53
  SUPPORTED_EXTENSIONS = frozenset({".docx", ".pptx"})
45
54
 
55
+ _GOTENBERG_ONLY_EXTS = frozenset({".doc", ".ppt", ".odt", ".rtf"})
56
+
46
57
  def __init__(
47
58
  self,
48
59
  pipeline: list[tuple[Converter, str]] | None = None,
@@ -62,6 +73,26 @@ class OfficeHandler(BaseHandler):
62
73
  (self.markitdown, "MarkItDown"),
63
74
  ]
64
75
 
76
+ # isinstance catches subclasses/proxies users might pass in custom
77
+ # pipelines. We explicitly check that the imported ``GotenbergConverter``
78
+ # is still a real class before using it as an isinstance second arg —
79
+ # if tests patch the name to a MagicMock *instance* (not a class),
80
+ # we fall back to ``gotenberg_enabled = False`` instead of wrapping
81
+ # the whole generator in try/except TypeError, which would otherwise
82
+ # silently swallow a real bug inside an ``enabled`` property.
83
+ if isinstance(GotenbergConverter, type):
84
+ gotenberg_enabled = any(
85
+ isinstance(c, GotenbergConverter) and getattr(c, "enabled", False)
86
+ for c, _ in self._pipeline
87
+ )
88
+ else:
89
+ gotenberg_enabled = False
90
+ self.supported_extensions: frozenset[str] = (
91
+ self.SUPPORTED_EXTENSIONS | self._GOTENBERG_ONLY_EXTS
92
+ if gotenberg_enabled
93
+ else self.SUPPORTED_EXTENSIONS
94
+ )
95
+
65
96
  async def handle(self, file_path: str, *args, **kwargs) -> str | None:
66
97
  """
67
98
  Convert an Office document to Markdown by trying the configured pipeline in order.
@@ -74,6 +74,62 @@ gotenberg_converter:
74
74
  expect: ["what is lorem ipsum?", "where does it come from"]
75
75
 
76
76
 
77
+ # =============================================================================
78
+ # LEGACY OFFICE FORMATS — Gotenberg-gated (.doc / .ppt / .odt / .rtf)
79
+ # Same fixtures as above re-saved / pandoc-converted into legacy formats.
80
+ # Routed through OfficeHandler → Gotenberg → PDF → PDFHandler per-page OCR.
81
+ # Only available iff GOTENBERG_URL is configured on the pipeline.
82
+ # =============================================================================
83
+ legacy_office_formats:
84
+ # .doc (re-saved from .docx via Word)
85
+ - file: tests/data/word-legacy/lorem-ipsum-only-text.doc
86
+ expect: ["what is lorem ipsum?", "where does it come from"]
87
+ - file: tests/data/word-legacy/lorem-ipsum-text-with-image.doc
88
+ expect: ["what is lorem ipsum?", "red", "green", "blue"]
89
+ - file: tests/data/word-legacy/geometry-colors.doc
90
+ expect: ["red", "green", "blue"]
91
+ - file: tests/data/word-legacy/lorem-ipsum-scanned-text.doc
92
+ expect: ["what is lorem ipsum?", "why do we use it?"]
93
+ - file: tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.doc
94
+ expect: ["what is lorem ipsum?", "where does it come from"]
95
+
96
+ # .ppt (re-saved from .pptx via PowerPoint)
97
+ - file: tests/data/powerpoint-legacy/lorem-ipsum-only-text.ppt
98
+ expect: ["what is lorem ipsum?", "where does it come from"]
99
+ - file: tests/data/powerpoint-legacy/lorem-ipsum-text-with-image.ppt
100
+ expect: ["what is lorem ipsum", "red", "green", "blue"]
101
+ - file: tests/data/powerpoint-legacy/geometry-colors.ppt
102
+ expect: ["red", "green", "blue"]
103
+ - file: tests/data/powerpoint-legacy/lorem-ipsum-scanned-text.ppt
104
+ expect: ["what is lorem ipsum", "why do we use it"]
105
+ - file: tests/data/powerpoint-legacy/lorem-ipsum-text-with-scanned-text.ppt
106
+ expect: ["what is lorem ipsum?", "where does it come from"]
107
+
108
+ # .odt (re-saved from .docx via Word)
109
+ - file: tests/data/word-legacy/lorem-ipsum-only-text.odt
110
+ expect: ["what is lorem ipsum?", "where does it come from"]
111
+ - file: tests/data/word-legacy/lorem-ipsum-text-with-image.odt
112
+ expect: ["what is lorem ipsum?", "red", "green", "blue"]
113
+ - file: tests/data/word-legacy/geometry-colors.odt
114
+ expect: ["red", "green", "blue"]
115
+ - file: tests/data/word-legacy/lorem-ipsum-scanned-text.odt
116
+ expect: ["what is lorem ipsum?", "why do we use it?"]
117
+ - file: tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.odt
118
+ expect: ["what is lorem ipsum?", "where does it come from"]
119
+
120
+ # .rtf (re-saved from .docx via Word)
121
+ - file: tests/data/word-legacy/lorem-ipsum-only-text.rtf
122
+ expect: ["what is lorem ipsum?", "where does it come from"]
123
+ - file: tests/data/word-legacy/lorem-ipsum-text-with-image.rtf
124
+ expect: ["what is lorem ipsum?", "red", "green", "blue"]
125
+ - file: tests/data/word-legacy/geometry-colors.rtf
126
+ expect: ["red", "green", "blue"]
127
+ - file: tests/data/word-legacy/lorem-ipsum-scanned-text.rtf
128
+ expect: ["what is lorem ipsum?", "why do we use it?"]
129
+ - file: tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.rtf
130
+ expect: ["what is lorem ipsum?", "where does it come from"]
131
+
132
+
77
133
  # =============================================================================
78
134
  # MARKITDOWN CONVERTER — text extraction only, NO OCR, NO image content
79
135
  # =============================================================================