docvortex 0.2.1__py3-none-any.whl

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 (364) hide show
  1. docvortex/__init__.py +21 -0
  2. docvortex/analyzers/__init__.py +3 -0
  3. docvortex/analyzers/native/__init__.py +37 -0
  4. docvortex/analyzers/native/_shared/__init__.py +3 -0
  5. docvortex/analyzers/native/_shared/hyperlink.py +16 -0
  6. docvortex/analyzers/native/_shared/image.py +8 -0
  7. docvortex/analyzers/native/_shared/markup/__init__.py +47 -0
  8. docvortex/analyzers/native/_shared/markup/anchors.py +30 -0
  9. docvortex/analyzers/native/_shared/markup/formula.py +37 -0
  10. docvortex/analyzers/native/_shared/markup/projector.py +57 -0
  11. docvortex/analyzers/native/_shared/markup/styles.py +19 -0
  12. docvortex/analyzers/native/_shared/mathml.py +17 -0
  13. docvortex/analyzers/native/_shared/names.py +7 -0
  14. docvortex/analyzers/native/_shared/xycut.py +414 -0
  15. docvortex/analyzers/native/contracts.py +44 -0
  16. docvortex/analyzers/native/csv.py +351 -0
  17. docvortex/analyzers/native/epub/__init__.py +18 -0
  18. docvortex/analyzers/native/epub/constants.py +45 -0
  19. docvortex/analyzers/native/epub/converter.py +80 -0
  20. docvortex/analyzers/native/epub/errors.py +20 -0
  21. docvortex/analyzers/native/epub/metadata.py +31 -0
  22. docvortex/analyzers/native/epub/package.py +649 -0
  23. docvortex/analyzers/native/epub/xhtml.py +306 -0
  24. docvortex/analyzers/native/html/__init__.py +6 -0
  25. docvortex/analyzers/native/html/anchors.py +271 -0
  26. docvortex/analyzers/native/html/constants.py +25 -0
  27. docvortex/analyzers/native/html/contracts.py +7 -0
  28. docvortex/analyzers/native/html/converter.py +117 -0
  29. docvortex/analyzers/native/html/document.py +389 -0
  30. docvortex/analyzers/native/html/errors.py +12 -0
  31. docvortex/analyzers/native/html/resources.py +365 -0
  32. docvortex/analyzers/native/html/selector.py +421 -0
  33. docvortex/analyzers/native/models.py +209 -0
  34. docvortex/analyzers/native/ofd/__init__.py +14 -0
  35. docvortex/analyzers/native/ofd/constants.py +59 -0
  36. docvortex/analyzers/native/ofd/converter.py +31 -0
  37. docvortex/analyzers/native/ofd/errors.py +16 -0
  38. docvortex/analyzers/native/ofd/geometry.py +215 -0
  39. docvortex/analyzers/native/ofd/images.py +124 -0
  40. docvortex/analyzers/native/ofd/metadata.py +53 -0
  41. docvortex/analyzers/native/ofd/models.py +170 -0
  42. docvortex/analyzers/native/ofd/package.py +346 -0
  43. docvortex/analyzers/native/ofd/path.py +201 -0
  44. docvortex/analyzers/native/ofd/reading_order.py +293 -0
  45. docvortex/analyzers/native/ofd/resources.py +122 -0
  46. docvortex/analyzers/native/ofd/scene.py +347 -0
  47. docvortex/analyzers/native/ofd/table.py +240 -0
  48. docvortex/analyzers/native/ofd/text.py +521 -0
  49. docvortex/analyzers/native/office/__init__.py +3 -0
  50. docvortex/analyzers/native/office/doc/__init__.py +3 -0
  51. docvortex/analyzers/native/office/doc/bookmarks.py +81 -0
  52. docvortex/analyzers/native/office/doc/doc_converter.py +573 -0
  53. docvortex/analyzers/native/office/doc/fib.py +261 -0
  54. docvortex/analyzers/native/office/doc/fields.py +101 -0
  55. docvortex/analyzers/native/office/doc/formatting.py +171 -0
  56. docvortex/analyzers/native/office/doc/images.py +162 -0
  57. docvortex/analyzers/native/office/doc/lists.py +356 -0
  58. docvortex/analyzers/native/office/doc/models.py +164 -0
  59. docvortex/analyzers/native/office/doc/parser.py +851 -0
  60. docvortex/analyzers/native/office/doc/pieces.py +256 -0
  61. docvortex/analyzers/native/office/doc/records.py +40 -0
  62. docvortex/analyzers/native/office/doc/sprm.py +269 -0
  63. docvortex/analyzers/native/office/doc/styles.py +215 -0
  64. docvortex/analyzers/native/office/docx/__init__.py +3 -0
  65. docvortex/analyzers/native/office/docx/context.py +71 -0
  66. docvortex/analyzers/native/office/docx/docx_converter.py +614 -0
  67. docvortex/analyzers/native/office/docx/equationxml.py +119 -0
  68. docvortex/analyzers/native/office/docx/fields.py +816 -0
  69. docvortex/analyzers/native/office/docx/formatting_types.py +23 -0
  70. docvortex/analyzers/native/office/docx/main.py +50 -0
  71. docvortex/analyzers/native/office/docx/numbering.py +491 -0
  72. docvortex/analyzers/native/office/docx/office_xml.py +57 -0
  73. docvortex/analyzers/native/office/docx/package_normalizer.py +248 -0
  74. docvortex/analyzers/native/office/docx/resources.py +526 -0
  75. docvortex/analyzers/native/office/docx/styles.py +693 -0
  76. docvortex/analyzers/native/office/docx/tables.py +515 -0
  77. docvortex/analyzers/native/office/equation/__init__.py +3 -0
  78. docvortex/analyzers/native/office/equation/image.py +470 -0
  79. docvortex/analyzers/native/office/equation/latex_dict.py +324 -0
  80. docvortex/analyzers/native/office/equation/mtef.py +885 -0
  81. docvortex/analyzers/native/office/equation/mtef_v5.py +941 -0
  82. docvortex/analyzers/native/office/equation/omml.py +561 -0
  83. docvortex/analyzers/native/office/equation/ooxml.py +62 -0
  84. docvortex/analyzers/native/office/errors.py +33 -0
  85. docvortex/analyzers/native/office/image.py +307 -0
  86. docvortex/analyzers/native/office/legacy/__init__.py +3 -0
  87. docvortex/analyzers/native/office/legacy/binary.py +43 -0
  88. docvortex/analyzers/native/office/legacy/officeart.py +362 -0
  89. docvortex/analyzers/native/office/legacy/ole.py +110 -0
  90. docvortex/analyzers/native/office/limits.py +12 -0
  91. docvortex/analyzers/native/office/odf/__init__.py +3 -0
  92. docvortex/analyzers/native/office/odf/chart.py +76 -0
  93. docvortex/analyzers/native/office/odf/constants.py +70 -0
  94. docvortex/analyzers/native/office/odf/converters.py +403 -0
  95. docvortex/analyzers/native/office/odf/errors.py +18 -0
  96. docvortex/analyzers/native/office/odf/metadata.py +91 -0
  97. docvortex/analyzers/native/office/odf/models.py +176 -0
  98. docvortex/analyzers/native/office/odf/package.py +270 -0
  99. docvortex/analyzers/native/office/odf/styles.py +329 -0
  100. docvortex/analyzers/native/office/odf/table.py +469 -0
  101. docvortex/analyzers/native/office/odf/text.py +1002 -0
  102. docvortex/analyzers/native/office/ooxml_chart.py +1016 -0
  103. docvortex/analyzers/native/office/opc.py +38 -0
  104. docvortex/analyzers/native/office/ppt/__init__.py +3 -0
  105. docvortex/analyzers/native/office/ppt/models.py +118 -0
  106. docvortex/analyzers/native/office/ppt/parser.py +1895 -0
  107. docvortex/analyzers/native/office/ppt/ppt_converter.py +292 -0
  108. docvortex/analyzers/native/office/ppt/records.py +131 -0
  109. docvortex/analyzers/native/office/ppt/style_text.py +247 -0
  110. docvortex/analyzers/native/office/pptx/__init__.py +3 -0
  111. docvortex/analyzers/native/office/pptx/context.py +113 -0
  112. docvortex/analyzers/native/office/pptx/lists.py +558 -0
  113. docvortex/analyzers/native/office/pptx/main.py +20 -0
  114. docvortex/analyzers/native/office/pptx/package_normalizer.py +321 -0
  115. docvortex/analyzers/native/office/pptx/pptx_converter.py +323 -0
  116. docvortex/analyzers/native/office/pptx/resources.py +329 -0
  117. docvortex/analyzers/native/office/pptx/shapes.py +393 -0
  118. docvortex/analyzers/native/office/pptx/text_styles.py +625 -0
  119. docvortex/analyzers/native/office/pptx/titles.py +178 -0
  120. docvortex/analyzers/native/office/rich_text.py +420 -0
  121. docvortex/analyzers/native/office/rtf/__init__.py +3 -0
  122. docvortex/analyzers/native/office/rtf/converter.py +708 -0
  123. docvortex/analyzers/native/office/rtf/lexer.py +213 -0
  124. docvortex/analyzers/native/office/rtf/math.py +339 -0
  125. docvortex/analyzers/native/office/rtf/models.py +185 -0
  126. docvortex/analyzers/native/office/rtf/parser.py +1553 -0
  127. docvortex/analyzers/native/office/spreadsheet/__init__.py +3 -0
  128. docvortex/analyzers/native/office/spreadsheet/html.py +81 -0
  129. docvortex/analyzers/native/office/spreadsheet/models.py +79 -0
  130. docvortex/analyzers/native/office/spreadsheet/projector.py +928 -0
  131. docvortex/analyzers/native/office/streams.py +18 -0
  132. docvortex/analyzers/native/office/xls/__init__.py +3 -0
  133. docvortex/analyzers/native/office/xls/chart.py +132 -0
  134. docvortex/analyzers/native/office/xls/embedded_chart.py +299 -0
  135. docvortex/analyzers/native/office/xls/models.py +109 -0
  136. docvortex/analyzers/native/office/xls/number_format.py +521 -0
  137. docvortex/analyzers/native/office/xls/parser.py +1145 -0
  138. docvortex/analyzers/native/office/xls/records.py +201 -0
  139. docvortex/analyzers/native/office/xls/strings.py +205 -0
  140. docvortex/analyzers/native/office/xls/xls_converter.py +354 -0
  141. docvortex/analyzers/native/office/xlsx/__init__.py +3 -0
  142. docvortex/analyzers/native/office/xlsx/main.py +20 -0
  143. docvortex/analyzers/native/office/xlsx/ooxml_ole.py +522 -0
  144. docvortex/analyzers/native/office/xlsx/package_normalizer.py +310 -0
  145. docvortex/analyzers/native/office/xlsx/xlsx_converter.py +716 -0
  146. docvortex/analyzers/native/pdf/__init__.py +3 -0
  147. docvortex/analyzers/native/pdf/auxiliary_text.py +1671 -0
  148. docvortex/analyzers/native/pdf/char_geometry.py +1662 -0
  149. docvortex/analyzers/native/pdf/code_blocks.py +535 -0
  150. docvortex/analyzers/native/pdf/formulas.py +1985 -0
  151. docvortex/analyzers/native/pdf/geometry.py +281 -0
  152. docvortex/analyzers/native/pdf/graphics.py +1501 -0
  153. docvortex/analyzers/native/pdf/index_blocks.py +268 -0
  154. docvortex/analyzers/native/pdf/inline/__init__.py +3 -0
  155. docvortex/analyzers/native/pdf/inline/common.py +112 -0
  156. docvortex/analyzers/native/pdf/inline/detection.py +590 -0
  157. docvortex/analyzers/native/pdf/inline/matching.py +1185 -0
  158. docvortex/analyzers/native/pdf/inline/materialize.py +457 -0
  159. docvortex/analyzers/native/pdf/inline/scripts.py +975 -0
  160. docvortex/analyzers/native/pdf/inline/types.py +385 -0
  161. docvortex/analyzers/native/pdf/line_layout.py +1106 -0
  162. docvortex/analyzers/native/pdf/line_merging.py +1223 -0
  163. docvortex/analyzers/native/pdf/models.py +246 -0
  164. docvortex/analyzers/native/pdf/native_text.py +1004 -0
  165. docvortex/analyzers/native/pdf/pipeline.py +1390 -0
  166. docvortex/analyzers/native/pdf/script_geometry.py +636 -0
  167. docvortex/analyzers/native/pdf/shared.py +30 -0
  168. docvortex/analyzers/native/pdf/spatial_text.py +383 -0
  169. docvortex/analyzers/native/pdf/table_annotations.py +446 -0
  170. docvortex/analyzers/native/pdf/table_constants.py +48 -0
  171. docvortex/analyzers/native/pdf/table_detection.py +227 -0
  172. docvortex/analyzers/native/pdf/table_filled_grid.py +218 -0
  173. docvortex/analyzers/native/pdf/table_geometry.py +40 -0
  174. docvortex/analyzers/native/pdf/table_materialization.py +444 -0
  175. docvortex/analyzers/native/pdf/table_recovery/__init__.py +15 -0
  176. docvortex/analyzers/native/pdf/table_recovery/candidate.py +356 -0
  177. docvortex/analyzers/native/pdf/table_recovery/contracts.py +154 -0
  178. docvortex/analyzers/native/pdf/table_recovery/engine.py +609 -0
  179. docvortex/analyzers/native/pdf/table_recovery/geometry.py +164 -0
  180. docvortex/analyzers/native/pdf/table_recovery/sparse_common.py +85 -0
  181. docvortex/analyzers/native/pdf/table_recovery/sparse_hybrid.py +804 -0
  182. docvortex/analyzers/native/pdf/table_recovery/sparse_multiline.py +1122 -0
  183. docvortex/analyzers/native/pdf/table_recovery/text.py +414 -0
  184. docvortex/analyzers/native/pdf/table_recovery/text_grid.py +618 -0
  185. docvortex/analyzers/native/pdf/table_recovery/vector.py +1931 -0
  186. docvortex/analyzers/native/pdf/table_rows.py +34 -0
  187. docvortex/analyzers/native/pdf/table_rules.py +1129 -0
  188. docvortex/analyzers/native/pdf/table_text_styles.py +283 -0
  189. docvortex/analyzers/native/pdf/tables.py +147 -0
  190. docvortex/analyzers/native/pdf/text_assembly/__init__.py +3 -0
  191. docvortex/analyzers/native/pdf/text_assembly/annotations.py +581 -0
  192. docvortex/analyzers/native/pdf/text_assembly/assembly.py +292 -0
  193. docvortex/analyzers/native/pdf/text_assembly/common.py +477 -0
  194. docvortex/analyzers/native/pdf/text_assembly/footnotes.py +394 -0
  195. docvortex/analyzers/native/pdf/text_assembly/merging.py +1274 -0
  196. docvortex/analyzers/native/pdf/text_assembly/rows.py +692 -0
  197. docvortex/analyzers/native/pdf/text_blocks.py +82 -0
  198. docvortex/analyzers/native/pdf/text_styles.py +55 -0
  199. docvortex/analyzers/native/pdf/title_analysis/__init__.py +3 -0
  200. docvortex/analyzers/native/pdf/title_analysis/body_profile.py +215 -0
  201. docvortex/analyzers/native/pdf/title_analysis/common.py +117 -0
  202. docvortex/analyzers/native/pdf/title_analysis/document_profile.py +164 -0
  203. docvortex/analyzers/native/pdf/title_analysis/lane_titles.py +758 -0
  204. docvortex/analyzers/native/pdf/title_analysis/page_titles.py +1024 -0
  205. docvortex/analyzers/native/pdf/title_analysis/prototype.py +194 -0
  206. docvortex/analyzers/native/pdf/title_analysis/structural.py +1081 -0
  207. docvortex/analyzers/native/pdf/titles.py +75 -0
  208. docvortex/analyzers/native/pdf/typography.py +19 -0
  209. docvortex/analyzers/native/pdf/visual_annotations.py +1262 -0
  210. docvortex/api.py +180 -0
  211. docvortex/assets/__init__.py +5 -0
  212. docvortex/assets/store.py +51 -0
  213. docvortex/cli.py +54 -0
  214. docvortex/codecs/__init__.py +3 -0
  215. docvortex/codecs/html/__init__.py +22 -0
  216. docvortex/codecs/html/contracts.py +236 -0
  217. docvortex/codecs/html/materializer.py +331 -0
  218. docvortex/codecs/html/parser.py +763 -0
  219. docvortex/codecs/html/resources.py +34 -0
  220. docvortex/codecs/json.py +17 -0
  221. docvortex/content/__init__.py +5 -0
  222. docvortex/content/inline.py +248 -0
  223. docvortex/content/markup/__init__.py +44 -0
  224. docvortex/content/markup/anchors.py +188 -0
  225. docvortex/content/markup/formula.py +280 -0
  226. docvortex/content/markup/projector.py +1237 -0
  227. docvortex/content/markup/styles.py +327 -0
  228. docvortex/content/mathml.py +167 -0
  229. docvortex/content/normalization.py +188 -0
  230. docvortex/content/spans.py +183 -0
  231. docvortex/content/table/__init__.py +18 -0
  232. docvortex/content/table/blocks.py +152 -0
  233. docvortex/content/table/content.py +425 -0
  234. docvortex/content/table/document.py +104 -0
  235. docvortex/content/table/html.py +399 -0
  236. docvortex/content/table/models.py +76 -0
  237. docvortex/content/table/rules.py +42 -0
  238. docvortex/content/table/structure.py +221 -0
  239. docvortex/content/tree.py +5 -0
  240. docvortex/document/__init__.py +3 -0
  241. docvortex/document/contracts.py +22 -0
  242. docvortex/document/detection.py +389 -0
  243. docvortex/document/filetypes.py +175 -0
  244. docvortex/document/page_range.py +167 -0
  245. docvortex/document/pdf/__init__.py +19 -0
  246. docvortex/document/pdf/classify.py +1138 -0
  247. docvortex/document/pdf/constants.py +132 -0
  248. docvortex/document/pdf/diagnostics.py +350 -0
  249. docvortex/document/pdf/document.py +582 -0
  250. docvortex/document/pdf/font_runtime.py +335 -0
  251. docvortex/document/pdf/geometry.py +31 -0
  252. docvortex/document/pdf/images.py +654 -0
  253. docvortex/document/pdf/native_annotations.py +367 -0
  254. docvortex/document/pdf/native_contracts.py +169 -0
  255. docvortex/document/pdf/native_coordinates.py +216 -0
  256. docvortex/document/pdf/native_lifecycle.py +16 -0
  257. docvortex/document/pdf/native_objects.py +902 -0
  258. docvortex/document/pdf/native_text_geometry.py +315 -0
  259. docvortex/document/pdf/pdfium.py +325 -0
  260. docvortex/document/pdf/raster.py +46 -0
  261. docvortex/document/pdf/text/__init__.py +62 -0
  262. docvortex/document/pdf/text/contracts.py +211 -0
  263. docvortex/document/pdf/text/extract.py +165 -0
  264. docvortex/document/pdf/text/geometry.py +16 -0
  265. docvortex/document/pdf/text/groups.py +162 -0
  266. docvortex/document/pdf/visual_geometry.py +201 -0
  267. docvortex/document/pdf/visuals.py +343 -0
  268. docvortex/document/source.py +92 -0
  269. docvortex/errors.py +21 -0
  270. docvortex/export/__init__.py +3 -0
  271. docvortex/export/bundle.py +98 -0
  272. docvortex/export/files.py +66 -0
  273. docvortex/export/middle.py +208 -0
  274. docvortex/foundation/__init__.py +3 -0
  275. docvortex/foundation/geometry.py +125 -0
  276. docvortex/foundation/hyperlink.py +65 -0
  277. docvortex/foundation/image.py +48 -0
  278. docvortex/foundation/image_encoding.py +30 -0
  279. docvortex/foundation/image_payload.py +280 -0
  280. docvortex/foundation/language.py +92 -0
  281. docvortex/foundation/platform.py +38 -0
  282. docvortex/foundation/text.py +153 -0
  283. docvortex/foundation/type_identity.py +20 -0
  284. docvortex/foundation/xml_names.py +20 -0
  285. docvortex/options.py +30 -0
  286. docvortex/postprocess/__init__.py +3 -0
  287. docvortex/postprocess/content.py +53 -0
  288. docvortex/postprocess/document.py +19 -0
  289. docvortex/postprocess/lists.py +236 -0
  290. docvortex/postprocess/page_blocks.py +214 -0
  291. docvortex/postprocess/pages.py +95 -0
  292. docvortex/postprocess/paragraphs.py +580 -0
  293. docvortex/postprocess/visual.py +715 -0
  294. docvortex/render/__init__.py +48 -0
  295. docvortex/render/_internal/__init__.py +3 -0
  296. docvortex/render/_internal/common/__init__.py +3 -0
  297. docvortex/render/_internal/common/context.py +43 -0
  298. docvortex/render/_internal/common/html_table.py +178 -0
  299. docvortex/render/_internal/common/index.py +33 -0
  300. docvortex/render/_internal/common/list_items.py +158 -0
  301. docvortex/render/_internal/common/planner.py +140 -0
  302. docvortex/render/_internal/docx/__init__.py +3 -0
  303. docvortex/render/_internal/docx/assets.py +202 -0
  304. docvortex/render/_internal/docx/inline.py +434 -0
  305. docvortex/render/_internal/docx/math.py +220 -0
  306. docvortex/render/_internal/docx/renderer.py +905 -0
  307. docvortex/render/_internal/docx/styles.py +195 -0
  308. docvortex/render/_internal/docx/table.py +442 -0
  309. docvortex/render/_internal/epub/__init__.py +5 -0
  310. docvortex/render/_internal/epub/assets.py +173 -0
  311. docvortex/render/_internal/epub/package.py +249 -0
  312. docvortex/render/_internal/epub/renderer.py +1156 -0
  313. docvortex/render/_internal/html/__init__.py +3 -0
  314. docvortex/render/_internal/html/inline.py +346 -0
  315. docvortex/render/_internal/html/renderer.py +1041 -0
  316. docvortex/render/_internal/html/sanitizer.py +478 -0
  317. docvortex/render/_internal/html/table.py +121 -0
  318. docvortex/render/_internal/latex/__init__.py +1 -0
  319. docvortex/render/_internal/latex/assets.py +85 -0
  320. docvortex/render/_internal/latex/inline.py +145 -0
  321. docvortex/render/_internal/latex/renderer.py +506 -0
  322. docvortex/render/_internal/latex/table.py +347 -0
  323. docvortex/render/_internal/markdown/__init__.py +3 -0
  324. docvortex/render/_internal/markdown/assets.py +78 -0
  325. docvortex/render/_internal/markdown/blocks.py +635 -0
  326. docvortex/render/_internal/markdown/escaping.py +51 -0
  327. docvortex/render/_internal/markdown/inline.py +260 -0
  328. docvortex/render/_internal/markdown/renderer.py +93 -0
  329. docvortex/render/_internal/markdown/table.py +281 -0
  330. docvortex/render/_internal/pdf/__init__.py +3 -0
  331. docvortex/render/_internal/pdf/assets.py +197 -0
  332. docvortex/render/_internal/pdf/formula.py +417 -0
  333. docvortex/render/_internal/pdf/inline.py +343 -0
  334. docvortex/render/_internal/pdf/renderer.py +734 -0
  335. docvortex/render/_internal/pdf/styles.py +206 -0
  336. docvortex/render/_internal/pdf/table.py +272 -0
  337. docvortex/render/_internal/structured_content/__init__.py +3 -0
  338. docvortex/render/_internal/structured_content/renderer.py +193 -0
  339. docvortex/render/api.py +199 -0
  340. docvortex/render/contracts.py +205 -0
  341. docvortex/render/docx.py +38 -0
  342. docvortex/render/epub.py +35 -0
  343. docvortex/render/fragments.py +70 -0
  344. docvortex/render/html.py +29 -0
  345. docvortex/render/latex.py +24 -0
  346. docvortex/render/markdown.py +50 -0
  347. docvortex/render/pdf.py +25 -0
  348. docvortex/render/structured_content.py +23 -0
  349. docvortex/resources/epub/docvortex.css +91 -0
  350. docvortex/resources/fasttext-langdetect/lid.176.ftz +0 -0
  351. docvortex/resources/fonts/DroidSansFallbackFull.ttf +0 -0
  352. docvortex/resources/fonts/NOTICE +190 -0
  353. docvortex/resources/fonts/manifest.json +9 -0
  354. docvortex/resources/html/docvortex.css +601 -0
  355. docvortex/resources/html/docvortex.min.css +1 -0
  356. docvortex/result.py +91 -0
  357. docvortex/schema.py +1141 -0
  358. docvortex/version.py +3 -0
  359. docvortex-0.2.1.dist-info/METADATA +193 -0
  360. docvortex-0.2.1.dist-info/RECORD +364 -0
  361. docvortex-0.2.1.dist-info/WHEEL +5 -0
  362. docvortex-0.2.1.dist-info/entry_points.txt +2 -0
  363. docvortex-0.2.1.dist-info/licenses/LICENSE.md +21 -0
  364. docvortex-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,321 @@
1
+ import posixpath
2
+ import re
3
+ from io import BytesIO
4
+ from zipfile import BadZipFile, ZipFile, ZipInfo
5
+
6
+ from loguru import logger
7
+ from lxml import etree
8
+
9
+ from ..opc import relationship_source_base_dir, write_zip_package
10
+
11
+ LEGACY_PPT_MAGIC = b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1"
12
+
13
+ WORDPROCESSINGML_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
14
+ MARKUP_COMPATIBILITY_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006"
15
+ PRESENTATIONML_NS = "http://schemas.openxmlformats.org/presentationml/2006/main"
16
+ PACKAGE_RELATIONSHIPS_NS = "http://schemas.openxmlformats.org/package/2006/relationships"
17
+
18
+ CONTENT_PART_TAG = f"{{{PRESENTATIONML_NS}}}contentPart"
19
+ RELATIONSHIP_TAG = f"{{{PACKAGE_RELATIONSHIPS_NS}}}Relationship"
20
+ PPTX_SHAPE_TAGS = {
21
+ f"{{{PRESENTATIONML_NS}}}sp",
22
+ f"{{{PRESENTATIONML_NS}}}grpSp",
23
+ f"{{{PRESENTATIONML_NS}}}graphicFrame",
24
+ f"{{{PRESENTATIONML_NS}}}cxnSp",
25
+ f"{{{PRESENTATIONML_NS}}}pic",
26
+ }
27
+
28
+ ROOT_TAG_PATTERN = re.compile(rb"<(?![?!])(?:[A-Za-z_][\w.-]*:)?[A-Za-z_][\w.-]*(?=\s|/?>)")
29
+
30
+ STRICT_OOXML_REPLACEMENTS = (
31
+ (
32
+ b"http://purl.oclc.org/ooxml/officeDocument/relationships/metadata/thumbnail",
33
+ b"http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail",
34
+ ),
35
+ (
36
+ b"http://purl.oclc.org/ooxml/officeDocument/relationships/customProperties",
37
+ b"http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties",
38
+ ),
39
+ (
40
+ b"http://purl.oclc.org/ooxml/officeDocument/relationships/extendedProperties",
41
+ b"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",
42
+ ),
43
+ (
44
+ b"http://purl.oclc.org/ooxml/officeDocument/relationships",
45
+ b"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
46
+ ),
47
+ (
48
+ b"http://purl.oclc.org/ooxml/drawingml/main",
49
+ b"http://schemas.openxmlformats.org/drawingml/2006/main",
50
+ ),
51
+ (
52
+ b"http://purl.oclc.org/ooxml/drawingml/chart",
53
+ b"http://schemas.openxmlformats.org/drawingml/2006/chart",
54
+ ),
55
+ (
56
+ b"http://purl.oclc.org/ooxml/presentationml/main",
57
+ b"http://schemas.openxmlformats.org/presentationml/2006/main",
58
+ ),
59
+ (
60
+ b"http://purl.oclc.org/ooxml/officeDocument/math",
61
+ b"http://schemas.openxmlformats.org/officeDocument/2006/math",
62
+ ),
63
+ (
64
+ b"http://purl.oclc.org/ooxml/officeDocument/customProperties",
65
+ b"http://schemas.openxmlformats.org/officeDocument/2006/custom-properties",
66
+ ),
67
+ (
68
+ b"http://purl.oclc.org/ooxml/officeDocument/extendedProperties",
69
+ b"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
70
+ ),
71
+ (
72
+ b"http://purl.oclc.org/ooxml/officeDocument/docPropsVTypes",
73
+ b"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
74
+ ),
75
+ (
76
+ b"http://purl.oclc.org/ooxml/officeDocument/oleObject",
77
+ b"http://schemas.openxmlformats.org/officeDocument/2006/oleObject",
78
+ ),
79
+ )
80
+
81
+ KNOWN_NAMESPACE_DECLARATIONS = {
82
+ b"w": f'xmlns:w="{WORDPROCESSINGML_NS}"'.encode("utf-8"),
83
+ }
84
+
85
+
86
+ def normalize_pptx_package(file_bytes: bytes) -> bytes:
87
+ """在进入 python-pptx 前修复常见包级兼容问题,避免修复逻辑散落到形状解析阶段。"""
88
+ if file_bytes.startswith(LEGACY_PPT_MAGIC):
89
+ raise ValueError("Legacy binary PPT files are not supported; convert the file to PPTX before parsing.")
90
+
91
+ try:
92
+ with ZipFile(BytesIO(file_bytes)) as source:
93
+ loaded_members: list[tuple[ZipInfo, bytes]] = []
94
+ rewritten_members: list[tuple[ZipInfo, bytes]] = []
95
+ skipped_members: set[str] = set()
96
+ changed = False
97
+
98
+ for info in source.infolist():
99
+ member_data = _read_member_best_effort(source, info)
100
+ if member_data is None:
101
+ skipped_members.add(info.filename)
102
+ changed = True
103
+ continue
104
+ loaded_members.append((info, member_data))
105
+
106
+ for info, member_data in loaded_members:
107
+ normalized_data = _normalize_member_xml(
108
+ info.filename,
109
+ member_data,
110
+ skipped_members,
111
+ )
112
+ if normalized_data != member_data:
113
+ changed = True
114
+ rewritten_members.append((info, normalized_data))
115
+ except BadZipFile as exc:
116
+ raise ValueError("Invalid PPTX package: file is not a ZIP archive.") from exc
117
+
118
+ if not changed:
119
+ return file_bytes
120
+
121
+ return write_zip_package(rewritten_members)
122
+
123
+
124
+ def _read_member_best_effort(source: ZipFile, info: ZipInfo) -> bytes | None:
125
+ """读取 ZIP 成员;损坏的媒体资源可跳过,关键 XML/关系文件仍保持失败。"""
126
+ try:
127
+ return source.read(info.filename)
128
+ except BadZipFile as exc:
129
+ if _is_skippable_corrupt_member(info.filename):
130
+ logger.warning(f"Skipping corrupt non-critical PPTX media member {info.filename}: {exc}")
131
+ return None
132
+ raise
133
+
134
+
135
+ def _is_skippable_corrupt_member(filename: str) -> bool:
136
+ """判断损坏成员是否属于可降级丢弃的媒体资源。"""
137
+ return filename.startswith("ppt/media/")
138
+
139
+
140
+ def _normalize_member_xml(
141
+ filename: str,
142
+ member_data: bytes,
143
+ skipped_members: set[str] | None = None,
144
+ ) -> bytes:
145
+ """仅对 XML/关系成员做文本级和结构级规范化,二进制资源保持原样。"""
146
+ if not (filename.endswith(".xml") or filename.endswith(".rels")):
147
+ return member_data
148
+
149
+ normalized = _translate_strict_ooxml_uris(member_data)
150
+ if filename.endswith(".rels"):
151
+ normalized = _remove_relationships_to_skipped_members(
152
+ filename,
153
+ normalized,
154
+ skipped_members or set(),
155
+ )
156
+ if filename.endswith(".xml"):
157
+ normalized = _add_missing_known_namespaces(normalized)
158
+ normalized = _replace_content_part_alternate_content_with_fallback(normalized)
159
+ return normalized
160
+
161
+
162
+ def _remove_relationships_to_skipped_members(
163
+ rels_filename: str,
164
+ rels_xml: bytes,
165
+ skipped_members: set[str],
166
+ ) -> bytes:
167
+ """删除指向已跳过媒体成员的内部关系,避免归一化包保留悬空引用。"""
168
+ if not skipped_members:
169
+ return rels_xml
170
+
171
+ try:
172
+ parser = etree.XMLParser(resolve_entities=False, remove_blank_text=False)
173
+ root = etree.fromstring(rels_xml, parser)
174
+ except etree.XMLSyntaxError:
175
+ return rels_xml
176
+
177
+ removed_count = 0
178
+ for relationship in list(root):
179
+ if relationship.tag != RELATIONSHIP_TAG and etree.QName(relationship).localname != "Relationship":
180
+ continue
181
+ if relationship.get("TargetMode") == "External":
182
+ continue
183
+
184
+ target = relationship.get("Target")
185
+ if not target:
186
+ continue
187
+
188
+ resolved_target = _resolve_relationship_target(rels_filename, target)
189
+ if resolved_target in skipped_members:
190
+ root.remove(relationship)
191
+ removed_count += 1
192
+
193
+ if removed_count == 0:
194
+ return rels_xml
195
+
196
+ return etree.tostring(
197
+ root,
198
+ xml_declaration=rels_xml.lstrip().startswith(b"<?xml"),
199
+ encoding="UTF-8",
200
+ standalone=True,
201
+ )
202
+
203
+
204
+ def _resolve_relationship_target(rels_filename: str, target: str) -> str:
205
+ """把关系文件中的 Target 解析成 ZIP 包内的规范成员路径。"""
206
+ target = target.replace("\\", "/")
207
+ if target.startswith("/"):
208
+ return target.lstrip("/")
209
+
210
+ base_dir = _relationship_source_base_dir(rels_filename)
211
+ if not base_dir:
212
+ return posixpath.normpath(target)
213
+ return posixpath.normpath(posixpath.join(base_dir, target))
214
+
215
+
216
+ def _relationship_source_base_dir(rels_filename: str) -> str:
217
+ """根据 .rels 成员路径推导源 part 的基础目录。"""
218
+ shared_base_dir = relationship_source_base_dir(rels_filename)
219
+ if shared_base_dir is not None:
220
+ return shared_base_dir
221
+
222
+ marker = "/_rels/"
223
+ if marker not in rels_filename:
224
+ return posixpath.dirname(rels_filename)
225
+
226
+ prefix, rels_basename = rels_filename.rsplit(marker, 1)
227
+ if not rels_basename.endswith(".rels"):
228
+ return prefix
229
+
230
+ source_part_name = rels_basename[: -len(".rels")]
231
+ source_part_path = posixpath.normpath(posixpath.join(prefix, source_part_name))
232
+ return posixpath.dirname(source_part_path)
233
+
234
+
235
+ def _translate_strict_ooxml_uris(xml_bytes: bytes) -> bytes:
236
+ """把 Strict OOXML URI 转为 python-pptx 能识别的 Transitional URI。"""
237
+ normalized = xml_bytes
238
+ for strict_uri, transitional_uri in STRICT_OOXML_REPLACEMENTS:
239
+ normalized = normalized.replace(strict_uri, transitional_uri)
240
+ return normalized
241
+
242
+
243
+ def _add_missing_known_namespaces(xml_bytes: bytes) -> bytes:
244
+ """为实际使用但未声明的已知前缀补齐命名空间,修复轻微损坏的 XML。"""
245
+ declarations = []
246
+ for prefix, declaration in KNOWN_NAMESPACE_DECLARATIONS.items():
247
+ if prefix + b":" in xml_bytes and b"xmlns:" + prefix + b"=" not in xml_bytes:
248
+ declarations.append(declaration)
249
+
250
+ if not declarations:
251
+ return xml_bytes
252
+
253
+ root_match = ROOT_TAG_PATTERN.search(xml_bytes)
254
+ if root_match is None:
255
+ return xml_bytes
256
+
257
+ close_index = xml_bytes.find(b">", root_match.end())
258
+ if close_index < 0:
259
+ return xml_bytes
260
+
261
+ insert_at = close_index
262
+ if insert_at > 0 and xml_bytes[insert_at - 1 : insert_at] == b"/":
263
+ insert_at -= 1
264
+
265
+ namespace_attrs = b" " + b" ".join(declarations)
266
+ return xml_bytes[:insert_at] + namespace_attrs + xml_bytes[insert_at:]
267
+
268
+
269
+ def _replace_content_part_alternate_content_with_fallback(xml_bytes: bytes) -> bytes:
270
+ """将 python-pptx 不支持的 p:contentPart 优先分支替换为可解析的 fallback 图形。"""
271
+ if b"AlternateContent" not in xml_bytes or b"contentPart" not in xml_bytes:
272
+ return xml_bytes
273
+
274
+ try:
275
+ parser = etree.XMLParser(resolve_entities=False, remove_blank_text=False)
276
+ root = etree.fromstring(xml_bytes, parser)
277
+ except etree.XMLSyntaxError:
278
+ return xml_bytes
279
+
280
+ replaced_count = 0
281
+ alternate_content_nodes = root.findall(f".//{{{MARKUP_COMPATIBILITY_NS}}}AlternateContent")
282
+ for alternate_content in alternate_content_nodes:
283
+ if _replace_single_alternate_content(alternate_content):
284
+ replaced_count += 1
285
+
286
+ if replaced_count == 0:
287
+ return xml_bytes
288
+
289
+ return etree.tostring(
290
+ root,
291
+ xml_declaration=xml_bytes.lstrip().startswith(b"<?xml"),
292
+ encoding="UTF-8",
293
+ standalone=True,
294
+ )
295
+
296
+
297
+ def _replace_single_alternate_content(alternate_content: etree._Element) -> bool:
298
+ """替换单个 AlternateContent 节点,优先保留 fallback 中的可见图形。"""
299
+ choice = alternate_content.find(f"{{{MARKUP_COMPATIBILITY_NS}}}Choice")
300
+ fallback = alternate_content.find(f"{{{MARKUP_COMPATIBILITY_NS}}}Fallback")
301
+ if choice is None or fallback is None:
302
+ return False
303
+
304
+ if not any(child.tag == CONTENT_PART_TAG for child in choice):
305
+ return False
306
+
307
+ fallback_shapes = [child for child in list(fallback) if child.tag in PPTX_SHAPE_TAGS]
308
+ if not fallback_shapes:
309
+ return False
310
+
311
+ parent = alternate_content.getparent()
312
+ if parent is None:
313
+ return False
314
+
315
+ insert_index = parent.index(alternate_content)
316
+ parent.remove(alternate_content)
317
+ for fallback_shape in fallback_shapes:
318
+ fallback.remove(fallback_shape)
319
+ parent.insert(insert_index, fallback_shape)
320
+ insert_index += 1
321
+ return True
@@ -0,0 +1,323 @@
1
+ from io import BytesIO
2
+ from typing import BinaryIO, Optional
3
+
4
+ from loguru import logger
5
+ from pptx import Presentation, presentation
6
+ from pptx.enum.shapes import MSO_SHAPE_TYPE
7
+
8
+ from ..equation.image import OfficeImageEquationDecoder
9
+ from ..equation.ooxml import OoxmlEquationDecoder
10
+ from ..streams import read_stream_bytes_from_start, rewind_stream
11
+ from .package_normalizer import normalize_pptx_package
12
+ from ..._shared.xycut import sort_entries
13
+ from .....schema import BlockType
14
+
15
+ # PPTX_XYCUT_BETA: Final = 0.7
16
+ # PPTX 标题占位符角色仅用于单页内部归一化,返回 model_output 前必须清理。
17
+
18
+
19
+ from .context import (
20
+ IGNORED_NOTES_PLACEHOLDER_TYPES as IGNORED_NOTES_PLACEHOLDER_TYPES,
21
+ MIN_PICTURE_DIMENSION_RATIO as MIN_PICTURE_DIMENSION_RATIO,
22
+ MIN_PICTURE_AREA_RATIO as MIN_PICTURE_AREA_RATIO,
23
+ BACKGROUND_PICTURE_TEXT_COVERAGE_RATIO as BACKGROUND_PICTURE_TEXT_COVERAGE_RATIO,
24
+ PPTX_XYCUT_BETA as PPTX_XYCUT_BETA,
25
+ PPTX_XYCUT_DENSITY_THRESHOLD as PPTX_XYCUT_DENSITY_THRESHOLD,
26
+ DRAWINGML_NS as DRAWINGML_NS,
27
+ RELATIONSHIP_NS as RELATIONSHIP_NS,
28
+ SVG_BLIP_NS as SVG_BLIP_NS,
29
+ A14_DRAWING_NS as A14_DRAWING_NS,
30
+ OMML_NS as OMML_NS,
31
+ _EFFECTIVE_FONT_SIZE_KEY as _EFFECTIVE_FONT_SIZE_KEY,
32
+ _EFFECTIVE_ALL_BOLD_KEY as _EFFECTIVE_ALL_BOLD_KEY,
33
+ _PPTX_TITLE_CANDIDATE_KEY as _PPTX_TITLE_CANDIDATE_KEY,
34
+ _PPTX_TITLE_ROLE_KEY as _PPTX_TITLE_ROLE_KEY,
35
+ _PPTX_TITLE_ROLE_CENTER as _PPTX_TITLE_ROLE_CENTER,
36
+ _PPTX_TITLE_ROLE_TITLE as _PPTX_TITLE_ROLE_TITLE,
37
+ _PPTX_TITLE_ROLE_SUBTITLE as _PPTX_TITLE_ROLE_SUBTITLE,
38
+ _PPTX_TITLE_PLACEHOLDER_ROLES as _PPTX_TITLE_PLACEHOLDER_ROLES,
39
+ _SlideTransform as _SlideTransform,
40
+ _FlattenedShape as _FlattenedShape,
41
+ )
42
+ from .resources import _PptxResources
43
+ from .shapes import _PptxShapes
44
+ from .text_styles import _PptxTextStyles
45
+ from .lists import _PptxLists
46
+ from .titles import _PptxTitles
47
+
48
+
49
+ class PptxConverter(_PptxResources, _PptxShapes, _PptxTextStyles, _PptxLists, _PptxTitles):
50
+ """编排 PPTX 包读取、逐页遍历、重试及职责处理。"""
51
+
52
+ def __init__(self):
53
+ """配置固定命名空间并为当前转换创建独立状态。"""
54
+ self.namespaces = {
55
+ "a": "http://schemas.openxmlformats.org/drawingml/2006/main",
56
+ "c": "http://schemas.openxmlformats.org/drawingml/2006/chart",
57
+ "m": "http://schemas.openxmlformats.org/officeDocument/2006/math",
58
+ "p": "http://schemas.openxmlformats.org/presentationml/2006/main",
59
+ }
60
+ self._reset_state()
61
+
62
+ def convert(
63
+ self,
64
+ file_stream: BinaryIO,
65
+ ):
66
+ if rewind_stream(file_stream):
67
+ try:
68
+ self._convert_package_stream(file_stream)
69
+ return
70
+ except Exception as exc:
71
+ file_bytes = read_stream_bytes_from_start(file_stream)
72
+ self._retry_convert_package_bytes_after_normalization(file_bytes, exc)
73
+ return
74
+
75
+ file_bytes = file_stream.read()
76
+ try:
77
+ self._convert_package_bytes(file_bytes)
78
+ except Exception as exc:
79
+ self._retry_convert_package_bytes_after_normalization(file_bytes, exc)
80
+
81
+ def _reset_state(self) -> None:
82
+ """重置解析状态,确保失败重试时不会残留上一次半解析结果。"""
83
+ self.pages = []
84
+ self.cur_page = []
85
+ self.list_block_stack = []
86
+ self._shape_type_cache = {}
87
+ self.file_stream = None
88
+ self.pptx_obj = None
89
+ self._ooxml_equation_decoder = OoxmlEquationDecoder()
90
+ self._image_equation_decoder = OfficeImageEquationDecoder()
91
+ self._mtef_warned_shapes = set()
92
+
93
+ def _convert_package_bytes(self, file_bytes: bytes) -> None:
94
+ """用独立字节流解析 PPTX 包,便于原始包失败后用规范化包重试。"""
95
+ self._convert_package_stream(BytesIO(file_bytes))
96
+
97
+ def _convert_package_stream(self, file_stream: BinaryIO) -> None:
98
+ """直接使用可复位的 PPTX 流解析正常路径,避免提前复制完整包字节。"""
99
+ self._reset_state()
100
+ rewind_stream(file_stream)
101
+ self.file_stream = file_stream
102
+ self.pptx_obj = Presentation(self.file_stream)
103
+ self.pages.append(self.cur_page)
104
+ if self.pptx_obj:
105
+ self._walk_linear(self.pptx_obj)
106
+ if self.pages and self.pages[-1] == []:
107
+ self.pages.pop()
108
+
109
+ def _retry_convert_package_bytes_after_normalization(
110
+ self,
111
+ file_bytes: bytes,
112
+ exc: Exception,
113
+ ) -> None:
114
+ """首次解析失败后,仅在包规范化确实产生变化时使用规范化字节重试。"""
115
+ normalized_bytes = normalize_pptx_package(file_bytes)
116
+ if normalized_bytes == file_bytes:
117
+ raise exc
118
+ logger.warning(f"Retrying PPTX parsing after package normalization: {exc}")
119
+ self._convert_package_bytes(normalized_bytes)
120
+
121
+ def _walk_linear(self, pptx_obj: presentation.Presentation):
122
+ slide_width = int(pptx_obj.slide_width)
123
+ slide_height = int(pptx_obj.slide_height)
124
+ has_visible_content_slide = False
125
+
126
+ # 遍历每一张幻灯片
127
+ for _, slide in enumerate(pptx_obj.slides):
128
+ linear_shapes = self._flatten_slide_shapes(slide.shapes)
129
+ sortable_shape_entries = []
130
+ tail_blocks = []
131
+
132
+ # 遍历幻灯片中的每一个形状
133
+ for shape_index, shape_entry in enumerate(linear_shapes):
134
+ shape_blocks = self._collect_shape_blocks(
135
+ shape_entry,
136
+ linear_shapes,
137
+ shape_index,
138
+ slide_width,
139
+ slide_height,
140
+ )
141
+ if not shape_blocks:
142
+ continue
143
+
144
+ if shape_entry.bbox is None:
145
+ tail_blocks.extend(shape_blocks)
146
+ continue
147
+
148
+ sortable_shape_entries.append(
149
+ {
150
+ "bbox": shape_entry.bbox,
151
+ "blocks": shape_blocks,
152
+ }
153
+ )
154
+
155
+ sorted_shape_entries = sort_entries(
156
+ sortable_shape_entries,
157
+ beta=PPTX_XYCUT_BETA,
158
+ density_threshold=PPTX_XYCUT_DENSITY_THRESHOLD,
159
+ )
160
+ for entry in sorted_shape_entries:
161
+ self.cur_page.extend(entry["blocks"])
162
+ self.cur_page.extend(tail_blocks)
163
+
164
+ visible_slide_has_content = bool(self.cur_page)
165
+ self._handle_slide_notes(slide)
166
+ self._promote_slide_text_blocks_to_titles(self.cur_page)
167
+ self._finalize_slide_title_types(
168
+ self.cur_page,
169
+ is_first_visible_slide=visible_slide_has_content and not has_visible_content_slide,
170
+ )
171
+ self._cleanup_slide_text_block_metadata(self.cur_page)
172
+ if visible_slide_has_content:
173
+ has_visible_content_slide = True
174
+ self.cur_page = []
175
+ self.pages.append(self.cur_page)
176
+
177
+ def _handle_slide_notes(self, slide) -> None:
178
+ if not slide.has_notes_slide:
179
+ return
180
+
181
+ try:
182
+ notes_slide = slide.notes_slide
183
+ except Exception as e:
184
+ logger.warning(f"Warning: notes slide cannot be loaded: {e}")
185
+ return
186
+
187
+ def handle_notes_shape(shape) -> None:
188
+ shape_type = self._safe_shape_type(shape)
189
+ if shape_type == MSO_SHAPE_TYPE.GROUP:
190
+ for grouped_shape in shape.shapes:
191
+ handle_notes_shape(grouped_shape)
192
+ return
193
+
194
+ if shape_type in {
195
+ MSO_SHAPE_TYPE.EMBEDDED_OLE_OBJECT,
196
+ MSO_SHAPE_TYPE.LINKED_OLE_OBJECT,
197
+ }:
198
+ if self._is_equation_ole_shape(shape):
199
+ omml_equations = self._pptx_ole_shape_omml(shape)
200
+ if omml_equations:
201
+ self.cur_page.extend(
202
+ {
203
+ "type": BlockType.PAGE_FOOTNOTE,
204
+ "content": [{"type": "equation_inline", "content": latex}],
205
+ }
206
+ for latex in omml_equations
207
+ )
208
+ return
209
+ latex = self._decode_pptx_ole_equation(shape)
210
+ if latex:
211
+ self.cur_page.append(
212
+ {
213
+ "type": BlockType.PAGE_FOOTNOTE,
214
+ "content": [{"type": "equation_inline", "content": latex}],
215
+ }
216
+ )
217
+ return
218
+ image_latex = self._decode_pptx_shape_image_equation(shape)
219
+ if image_latex:
220
+ self.cur_page.append(
221
+ {
222
+ "type": BlockType.PAGE_FOOTNOTE,
223
+ "content": [{"type": "equation_inline", "content": image_latex}],
224
+ }
225
+ )
226
+ return
227
+
228
+ if shape_type == MSO_SHAPE_TYPE.PICTURE:
229
+ image_latex = self._decode_pptx_shape_image_equation(shape)
230
+ if image_latex:
231
+ self.cur_page.append(
232
+ {
233
+ "type": BlockType.PAGE_FOOTNOTE,
234
+ "content": [{"type": "equation_inline", "content": image_latex}],
235
+ }
236
+ )
237
+ return
238
+
239
+ if self._should_skip_notes_shape(shape):
240
+ return
241
+
242
+ for paragraph in shape.text_frame.paragraphs:
243
+ note_text = self._normalize_text_block_content(self._build_paragraph_rich_text(paragraph, shape))
244
+ if not note_text:
245
+ continue
246
+ self.cur_page.append(
247
+ {
248
+ "type": BlockType.PAGE_FOOTNOTE,
249
+ "content": note_text,
250
+ }
251
+ )
252
+
253
+ for shape in notes_slide.shapes:
254
+ handle_notes_shape(shape)
255
+
256
+ @staticmethod
257
+ def _should_skip_notes_shape(shape) -> bool:
258
+ if not PptxConverter._shape_has_raw_text(shape):
259
+ return True
260
+
261
+ if not getattr(shape, "is_placeholder", False):
262
+ return False
263
+
264
+ try:
265
+ return shape.placeholder_format.type in IGNORED_NOTES_PLACEHOLDER_TYPES
266
+ except Exception:
267
+ return False
268
+
269
+ def _handle_text_elements(self, shape):
270
+ self.list_block_stack = []
271
+ list_level_base: Optional[int] = None
272
+
273
+ # 遍历段落以构建文本
274
+ for paragraph in shape.text_frame.paragraphs:
275
+ list_info = self._get_paragraph_list_info(shape, paragraph)
276
+
277
+ if list_info["is_list"]:
278
+ rich_text = self._normalize_text_block_content(self._build_paragraph_rich_text(paragraph, shape))
279
+ if rich_text:
280
+ if list_level_base is None:
281
+ list_level_base = list_info["level"]
282
+ self._append_list_item(
283
+ self.list_block_stack,
284
+ self._normalize_contiguous_list_level(
285
+ list_info["level"],
286
+ list_level_base,
287
+ ),
288
+ list_info["attribute"],
289
+ rich_text,
290
+ list_info.get("start"),
291
+ list_info.get("start_is_explicit_restart", False),
292
+ )
293
+ continue
294
+
295
+ # 段落不是列表项,关闭当前 shape 的列表上下文
296
+ self.list_block_stack.clear()
297
+ list_level_base = None
298
+
299
+ p_text = self._normalize_text_block_content(self._build_paragraph_rich_text(paragraph, shape))
300
+ if not p_text:
301
+ continue
302
+
303
+ style_profile = self._build_paragraph_style_profile(shape, paragraph)
304
+
305
+ block = {
306
+ "type": BlockType.TEXT,
307
+ "content": p_text,
308
+ _EFFECTIVE_FONT_SIZE_KEY: style_profile["font_size_pt"],
309
+ _EFFECTIVE_ALL_BOLD_KEY: style_profile["all_bold"],
310
+ }
311
+ if shape.is_placeholder:
312
+ placeholder_type = shape.placeholder_format.type
313
+ title_role = _PPTX_TITLE_PLACEHOLDER_ROLES.get(placeholder_type)
314
+ if title_role is not None:
315
+ block[_PPTX_TITLE_CANDIDATE_KEY] = True
316
+ block["level"] = 2
317
+ block[_PPTX_TITLE_ROLE_KEY] = title_role
318
+
319
+ self.cur_page.append(block)
320
+
321
+ # shape 结束后清理列表上下文,避免跨 shape 污染
322
+ self.list_block_stack.clear()
323
+ return