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.
- docvortex/__init__.py +21 -0
- docvortex/analyzers/__init__.py +3 -0
- docvortex/analyzers/native/__init__.py +37 -0
- docvortex/analyzers/native/_shared/__init__.py +3 -0
- docvortex/analyzers/native/_shared/hyperlink.py +16 -0
- docvortex/analyzers/native/_shared/image.py +8 -0
- docvortex/analyzers/native/_shared/markup/__init__.py +47 -0
- docvortex/analyzers/native/_shared/markup/anchors.py +30 -0
- docvortex/analyzers/native/_shared/markup/formula.py +37 -0
- docvortex/analyzers/native/_shared/markup/projector.py +57 -0
- docvortex/analyzers/native/_shared/markup/styles.py +19 -0
- docvortex/analyzers/native/_shared/mathml.py +17 -0
- docvortex/analyzers/native/_shared/names.py +7 -0
- docvortex/analyzers/native/_shared/xycut.py +414 -0
- docvortex/analyzers/native/contracts.py +44 -0
- docvortex/analyzers/native/csv.py +351 -0
- docvortex/analyzers/native/epub/__init__.py +18 -0
- docvortex/analyzers/native/epub/constants.py +45 -0
- docvortex/analyzers/native/epub/converter.py +80 -0
- docvortex/analyzers/native/epub/errors.py +20 -0
- docvortex/analyzers/native/epub/metadata.py +31 -0
- docvortex/analyzers/native/epub/package.py +649 -0
- docvortex/analyzers/native/epub/xhtml.py +306 -0
- docvortex/analyzers/native/html/__init__.py +6 -0
- docvortex/analyzers/native/html/anchors.py +271 -0
- docvortex/analyzers/native/html/constants.py +25 -0
- docvortex/analyzers/native/html/contracts.py +7 -0
- docvortex/analyzers/native/html/converter.py +117 -0
- docvortex/analyzers/native/html/document.py +389 -0
- docvortex/analyzers/native/html/errors.py +12 -0
- docvortex/analyzers/native/html/resources.py +365 -0
- docvortex/analyzers/native/html/selector.py +421 -0
- docvortex/analyzers/native/models.py +209 -0
- docvortex/analyzers/native/ofd/__init__.py +14 -0
- docvortex/analyzers/native/ofd/constants.py +59 -0
- docvortex/analyzers/native/ofd/converter.py +31 -0
- docvortex/analyzers/native/ofd/errors.py +16 -0
- docvortex/analyzers/native/ofd/geometry.py +215 -0
- docvortex/analyzers/native/ofd/images.py +124 -0
- docvortex/analyzers/native/ofd/metadata.py +53 -0
- docvortex/analyzers/native/ofd/models.py +170 -0
- docvortex/analyzers/native/ofd/package.py +346 -0
- docvortex/analyzers/native/ofd/path.py +201 -0
- docvortex/analyzers/native/ofd/reading_order.py +293 -0
- docvortex/analyzers/native/ofd/resources.py +122 -0
- docvortex/analyzers/native/ofd/scene.py +347 -0
- docvortex/analyzers/native/ofd/table.py +240 -0
- docvortex/analyzers/native/ofd/text.py +521 -0
- docvortex/analyzers/native/office/__init__.py +3 -0
- docvortex/analyzers/native/office/doc/__init__.py +3 -0
- docvortex/analyzers/native/office/doc/bookmarks.py +81 -0
- docvortex/analyzers/native/office/doc/doc_converter.py +573 -0
- docvortex/analyzers/native/office/doc/fib.py +261 -0
- docvortex/analyzers/native/office/doc/fields.py +101 -0
- docvortex/analyzers/native/office/doc/formatting.py +171 -0
- docvortex/analyzers/native/office/doc/images.py +162 -0
- docvortex/analyzers/native/office/doc/lists.py +356 -0
- docvortex/analyzers/native/office/doc/models.py +164 -0
- docvortex/analyzers/native/office/doc/parser.py +851 -0
- docvortex/analyzers/native/office/doc/pieces.py +256 -0
- docvortex/analyzers/native/office/doc/records.py +40 -0
- docvortex/analyzers/native/office/doc/sprm.py +269 -0
- docvortex/analyzers/native/office/doc/styles.py +215 -0
- docvortex/analyzers/native/office/docx/__init__.py +3 -0
- docvortex/analyzers/native/office/docx/context.py +71 -0
- docvortex/analyzers/native/office/docx/docx_converter.py +614 -0
- docvortex/analyzers/native/office/docx/equationxml.py +119 -0
- docvortex/analyzers/native/office/docx/fields.py +816 -0
- docvortex/analyzers/native/office/docx/formatting_types.py +23 -0
- docvortex/analyzers/native/office/docx/main.py +50 -0
- docvortex/analyzers/native/office/docx/numbering.py +491 -0
- docvortex/analyzers/native/office/docx/office_xml.py +57 -0
- docvortex/analyzers/native/office/docx/package_normalizer.py +248 -0
- docvortex/analyzers/native/office/docx/resources.py +526 -0
- docvortex/analyzers/native/office/docx/styles.py +693 -0
- docvortex/analyzers/native/office/docx/tables.py +515 -0
- docvortex/analyzers/native/office/equation/__init__.py +3 -0
- docvortex/analyzers/native/office/equation/image.py +470 -0
- docvortex/analyzers/native/office/equation/latex_dict.py +324 -0
- docvortex/analyzers/native/office/equation/mtef.py +885 -0
- docvortex/analyzers/native/office/equation/mtef_v5.py +941 -0
- docvortex/analyzers/native/office/equation/omml.py +561 -0
- docvortex/analyzers/native/office/equation/ooxml.py +62 -0
- docvortex/analyzers/native/office/errors.py +33 -0
- docvortex/analyzers/native/office/image.py +307 -0
- docvortex/analyzers/native/office/legacy/__init__.py +3 -0
- docvortex/analyzers/native/office/legacy/binary.py +43 -0
- docvortex/analyzers/native/office/legacy/officeart.py +362 -0
- docvortex/analyzers/native/office/legacy/ole.py +110 -0
- docvortex/analyzers/native/office/limits.py +12 -0
- docvortex/analyzers/native/office/odf/__init__.py +3 -0
- docvortex/analyzers/native/office/odf/chart.py +76 -0
- docvortex/analyzers/native/office/odf/constants.py +70 -0
- docvortex/analyzers/native/office/odf/converters.py +403 -0
- docvortex/analyzers/native/office/odf/errors.py +18 -0
- docvortex/analyzers/native/office/odf/metadata.py +91 -0
- docvortex/analyzers/native/office/odf/models.py +176 -0
- docvortex/analyzers/native/office/odf/package.py +270 -0
- docvortex/analyzers/native/office/odf/styles.py +329 -0
- docvortex/analyzers/native/office/odf/table.py +469 -0
- docvortex/analyzers/native/office/odf/text.py +1002 -0
- docvortex/analyzers/native/office/ooxml_chart.py +1016 -0
- docvortex/analyzers/native/office/opc.py +38 -0
- docvortex/analyzers/native/office/ppt/__init__.py +3 -0
- docvortex/analyzers/native/office/ppt/models.py +118 -0
- docvortex/analyzers/native/office/ppt/parser.py +1895 -0
- docvortex/analyzers/native/office/ppt/ppt_converter.py +292 -0
- docvortex/analyzers/native/office/ppt/records.py +131 -0
- docvortex/analyzers/native/office/ppt/style_text.py +247 -0
- docvortex/analyzers/native/office/pptx/__init__.py +3 -0
- docvortex/analyzers/native/office/pptx/context.py +113 -0
- docvortex/analyzers/native/office/pptx/lists.py +558 -0
- docvortex/analyzers/native/office/pptx/main.py +20 -0
- docvortex/analyzers/native/office/pptx/package_normalizer.py +321 -0
- docvortex/analyzers/native/office/pptx/pptx_converter.py +323 -0
- docvortex/analyzers/native/office/pptx/resources.py +329 -0
- docvortex/analyzers/native/office/pptx/shapes.py +393 -0
- docvortex/analyzers/native/office/pptx/text_styles.py +625 -0
- docvortex/analyzers/native/office/pptx/titles.py +178 -0
- docvortex/analyzers/native/office/rich_text.py +420 -0
- docvortex/analyzers/native/office/rtf/__init__.py +3 -0
- docvortex/analyzers/native/office/rtf/converter.py +708 -0
- docvortex/analyzers/native/office/rtf/lexer.py +213 -0
- docvortex/analyzers/native/office/rtf/math.py +339 -0
- docvortex/analyzers/native/office/rtf/models.py +185 -0
- docvortex/analyzers/native/office/rtf/parser.py +1553 -0
- docvortex/analyzers/native/office/spreadsheet/__init__.py +3 -0
- docvortex/analyzers/native/office/spreadsheet/html.py +81 -0
- docvortex/analyzers/native/office/spreadsheet/models.py +79 -0
- docvortex/analyzers/native/office/spreadsheet/projector.py +928 -0
- docvortex/analyzers/native/office/streams.py +18 -0
- docvortex/analyzers/native/office/xls/__init__.py +3 -0
- docvortex/analyzers/native/office/xls/chart.py +132 -0
- docvortex/analyzers/native/office/xls/embedded_chart.py +299 -0
- docvortex/analyzers/native/office/xls/models.py +109 -0
- docvortex/analyzers/native/office/xls/number_format.py +521 -0
- docvortex/analyzers/native/office/xls/parser.py +1145 -0
- docvortex/analyzers/native/office/xls/records.py +201 -0
- docvortex/analyzers/native/office/xls/strings.py +205 -0
- docvortex/analyzers/native/office/xls/xls_converter.py +354 -0
- docvortex/analyzers/native/office/xlsx/__init__.py +3 -0
- docvortex/analyzers/native/office/xlsx/main.py +20 -0
- docvortex/analyzers/native/office/xlsx/ooxml_ole.py +522 -0
- docvortex/analyzers/native/office/xlsx/package_normalizer.py +310 -0
- docvortex/analyzers/native/office/xlsx/xlsx_converter.py +716 -0
- docvortex/analyzers/native/pdf/__init__.py +3 -0
- docvortex/analyzers/native/pdf/auxiliary_text.py +1671 -0
- docvortex/analyzers/native/pdf/char_geometry.py +1662 -0
- docvortex/analyzers/native/pdf/code_blocks.py +535 -0
- docvortex/analyzers/native/pdf/formulas.py +1985 -0
- docvortex/analyzers/native/pdf/geometry.py +281 -0
- docvortex/analyzers/native/pdf/graphics.py +1501 -0
- docvortex/analyzers/native/pdf/index_blocks.py +268 -0
- docvortex/analyzers/native/pdf/inline/__init__.py +3 -0
- docvortex/analyzers/native/pdf/inline/common.py +112 -0
- docvortex/analyzers/native/pdf/inline/detection.py +590 -0
- docvortex/analyzers/native/pdf/inline/matching.py +1185 -0
- docvortex/analyzers/native/pdf/inline/materialize.py +457 -0
- docvortex/analyzers/native/pdf/inline/scripts.py +975 -0
- docvortex/analyzers/native/pdf/inline/types.py +385 -0
- docvortex/analyzers/native/pdf/line_layout.py +1106 -0
- docvortex/analyzers/native/pdf/line_merging.py +1223 -0
- docvortex/analyzers/native/pdf/models.py +246 -0
- docvortex/analyzers/native/pdf/native_text.py +1004 -0
- docvortex/analyzers/native/pdf/pipeline.py +1390 -0
- docvortex/analyzers/native/pdf/script_geometry.py +636 -0
- docvortex/analyzers/native/pdf/shared.py +30 -0
- docvortex/analyzers/native/pdf/spatial_text.py +383 -0
- docvortex/analyzers/native/pdf/table_annotations.py +446 -0
- docvortex/analyzers/native/pdf/table_constants.py +48 -0
- docvortex/analyzers/native/pdf/table_detection.py +227 -0
- docvortex/analyzers/native/pdf/table_filled_grid.py +218 -0
- docvortex/analyzers/native/pdf/table_geometry.py +40 -0
- docvortex/analyzers/native/pdf/table_materialization.py +444 -0
- docvortex/analyzers/native/pdf/table_recovery/__init__.py +15 -0
- docvortex/analyzers/native/pdf/table_recovery/candidate.py +356 -0
- docvortex/analyzers/native/pdf/table_recovery/contracts.py +154 -0
- docvortex/analyzers/native/pdf/table_recovery/engine.py +609 -0
- docvortex/analyzers/native/pdf/table_recovery/geometry.py +164 -0
- docvortex/analyzers/native/pdf/table_recovery/sparse_common.py +85 -0
- docvortex/analyzers/native/pdf/table_recovery/sparse_hybrid.py +804 -0
- docvortex/analyzers/native/pdf/table_recovery/sparse_multiline.py +1122 -0
- docvortex/analyzers/native/pdf/table_recovery/text.py +414 -0
- docvortex/analyzers/native/pdf/table_recovery/text_grid.py +618 -0
- docvortex/analyzers/native/pdf/table_recovery/vector.py +1931 -0
- docvortex/analyzers/native/pdf/table_rows.py +34 -0
- docvortex/analyzers/native/pdf/table_rules.py +1129 -0
- docvortex/analyzers/native/pdf/table_text_styles.py +283 -0
- docvortex/analyzers/native/pdf/tables.py +147 -0
- docvortex/analyzers/native/pdf/text_assembly/__init__.py +3 -0
- docvortex/analyzers/native/pdf/text_assembly/annotations.py +581 -0
- docvortex/analyzers/native/pdf/text_assembly/assembly.py +292 -0
- docvortex/analyzers/native/pdf/text_assembly/common.py +477 -0
- docvortex/analyzers/native/pdf/text_assembly/footnotes.py +394 -0
- docvortex/analyzers/native/pdf/text_assembly/merging.py +1274 -0
- docvortex/analyzers/native/pdf/text_assembly/rows.py +692 -0
- docvortex/analyzers/native/pdf/text_blocks.py +82 -0
- docvortex/analyzers/native/pdf/text_styles.py +55 -0
- docvortex/analyzers/native/pdf/title_analysis/__init__.py +3 -0
- docvortex/analyzers/native/pdf/title_analysis/body_profile.py +215 -0
- docvortex/analyzers/native/pdf/title_analysis/common.py +117 -0
- docvortex/analyzers/native/pdf/title_analysis/document_profile.py +164 -0
- docvortex/analyzers/native/pdf/title_analysis/lane_titles.py +758 -0
- docvortex/analyzers/native/pdf/title_analysis/page_titles.py +1024 -0
- docvortex/analyzers/native/pdf/title_analysis/prototype.py +194 -0
- docvortex/analyzers/native/pdf/title_analysis/structural.py +1081 -0
- docvortex/analyzers/native/pdf/titles.py +75 -0
- docvortex/analyzers/native/pdf/typography.py +19 -0
- docvortex/analyzers/native/pdf/visual_annotations.py +1262 -0
- docvortex/api.py +180 -0
- docvortex/assets/__init__.py +5 -0
- docvortex/assets/store.py +51 -0
- docvortex/cli.py +54 -0
- docvortex/codecs/__init__.py +3 -0
- docvortex/codecs/html/__init__.py +22 -0
- docvortex/codecs/html/contracts.py +236 -0
- docvortex/codecs/html/materializer.py +331 -0
- docvortex/codecs/html/parser.py +763 -0
- docvortex/codecs/html/resources.py +34 -0
- docvortex/codecs/json.py +17 -0
- docvortex/content/__init__.py +5 -0
- docvortex/content/inline.py +248 -0
- docvortex/content/markup/__init__.py +44 -0
- docvortex/content/markup/anchors.py +188 -0
- docvortex/content/markup/formula.py +280 -0
- docvortex/content/markup/projector.py +1237 -0
- docvortex/content/markup/styles.py +327 -0
- docvortex/content/mathml.py +167 -0
- docvortex/content/normalization.py +188 -0
- docvortex/content/spans.py +183 -0
- docvortex/content/table/__init__.py +18 -0
- docvortex/content/table/blocks.py +152 -0
- docvortex/content/table/content.py +425 -0
- docvortex/content/table/document.py +104 -0
- docvortex/content/table/html.py +399 -0
- docvortex/content/table/models.py +76 -0
- docvortex/content/table/rules.py +42 -0
- docvortex/content/table/structure.py +221 -0
- docvortex/content/tree.py +5 -0
- docvortex/document/__init__.py +3 -0
- docvortex/document/contracts.py +22 -0
- docvortex/document/detection.py +389 -0
- docvortex/document/filetypes.py +175 -0
- docvortex/document/page_range.py +167 -0
- docvortex/document/pdf/__init__.py +19 -0
- docvortex/document/pdf/classify.py +1138 -0
- docvortex/document/pdf/constants.py +132 -0
- docvortex/document/pdf/diagnostics.py +350 -0
- docvortex/document/pdf/document.py +582 -0
- docvortex/document/pdf/font_runtime.py +335 -0
- docvortex/document/pdf/geometry.py +31 -0
- docvortex/document/pdf/images.py +654 -0
- docvortex/document/pdf/native_annotations.py +367 -0
- docvortex/document/pdf/native_contracts.py +169 -0
- docvortex/document/pdf/native_coordinates.py +216 -0
- docvortex/document/pdf/native_lifecycle.py +16 -0
- docvortex/document/pdf/native_objects.py +902 -0
- docvortex/document/pdf/native_text_geometry.py +315 -0
- docvortex/document/pdf/pdfium.py +325 -0
- docvortex/document/pdf/raster.py +46 -0
- docvortex/document/pdf/text/__init__.py +62 -0
- docvortex/document/pdf/text/contracts.py +211 -0
- docvortex/document/pdf/text/extract.py +165 -0
- docvortex/document/pdf/text/geometry.py +16 -0
- docvortex/document/pdf/text/groups.py +162 -0
- docvortex/document/pdf/visual_geometry.py +201 -0
- docvortex/document/pdf/visuals.py +343 -0
- docvortex/document/source.py +92 -0
- docvortex/errors.py +21 -0
- docvortex/export/__init__.py +3 -0
- docvortex/export/bundle.py +98 -0
- docvortex/export/files.py +66 -0
- docvortex/export/middle.py +208 -0
- docvortex/foundation/__init__.py +3 -0
- docvortex/foundation/geometry.py +125 -0
- docvortex/foundation/hyperlink.py +65 -0
- docvortex/foundation/image.py +48 -0
- docvortex/foundation/image_encoding.py +30 -0
- docvortex/foundation/image_payload.py +280 -0
- docvortex/foundation/language.py +92 -0
- docvortex/foundation/platform.py +38 -0
- docvortex/foundation/text.py +153 -0
- docvortex/foundation/type_identity.py +20 -0
- docvortex/foundation/xml_names.py +20 -0
- docvortex/options.py +30 -0
- docvortex/postprocess/__init__.py +3 -0
- docvortex/postprocess/content.py +53 -0
- docvortex/postprocess/document.py +19 -0
- docvortex/postprocess/lists.py +236 -0
- docvortex/postprocess/page_blocks.py +214 -0
- docvortex/postprocess/pages.py +95 -0
- docvortex/postprocess/paragraphs.py +580 -0
- docvortex/postprocess/visual.py +715 -0
- docvortex/render/__init__.py +48 -0
- docvortex/render/_internal/__init__.py +3 -0
- docvortex/render/_internal/common/__init__.py +3 -0
- docvortex/render/_internal/common/context.py +43 -0
- docvortex/render/_internal/common/html_table.py +178 -0
- docvortex/render/_internal/common/index.py +33 -0
- docvortex/render/_internal/common/list_items.py +158 -0
- docvortex/render/_internal/common/planner.py +140 -0
- docvortex/render/_internal/docx/__init__.py +3 -0
- docvortex/render/_internal/docx/assets.py +202 -0
- docvortex/render/_internal/docx/inline.py +434 -0
- docvortex/render/_internal/docx/math.py +220 -0
- docvortex/render/_internal/docx/renderer.py +905 -0
- docvortex/render/_internal/docx/styles.py +195 -0
- docvortex/render/_internal/docx/table.py +442 -0
- docvortex/render/_internal/epub/__init__.py +5 -0
- docvortex/render/_internal/epub/assets.py +173 -0
- docvortex/render/_internal/epub/package.py +249 -0
- docvortex/render/_internal/epub/renderer.py +1156 -0
- docvortex/render/_internal/html/__init__.py +3 -0
- docvortex/render/_internal/html/inline.py +346 -0
- docvortex/render/_internal/html/renderer.py +1041 -0
- docvortex/render/_internal/html/sanitizer.py +478 -0
- docvortex/render/_internal/html/table.py +121 -0
- docvortex/render/_internal/latex/__init__.py +1 -0
- docvortex/render/_internal/latex/assets.py +85 -0
- docvortex/render/_internal/latex/inline.py +145 -0
- docvortex/render/_internal/latex/renderer.py +506 -0
- docvortex/render/_internal/latex/table.py +347 -0
- docvortex/render/_internal/markdown/__init__.py +3 -0
- docvortex/render/_internal/markdown/assets.py +78 -0
- docvortex/render/_internal/markdown/blocks.py +635 -0
- docvortex/render/_internal/markdown/escaping.py +51 -0
- docvortex/render/_internal/markdown/inline.py +260 -0
- docvortex/render/_internal/markdown/renderer.py +93 -0
- docvortex/render/_internal/markdown/table.py +281 -0
- docvortex/render/_internal/pdf/__init__.py +3 -0
- docvortex/render/_internal/pdf/assets.py +197 -0
- docvortex/render/_internal/pdf/formula.py +417 -0
- docvortex/render/_internal/pdf/inline.py +343 -0
- docvortex/render/_internal/pdf/renderer.py +734 -0
- docvortex/render/_internal/pdf/styles.py +206 -0
- docvortex/render/_internal/pdf/table.py +272 -0
- docvortex/render/_internal/structured_content/__init__.py +3 -0
- docvortex/render/_internal/structured_content/renderer.py +193 -0
- docvortex/render/api.py +199 -0
- docvortex/render/contracts.py +205 -0
- docvortex/render/docx.py +38 -0
- docvortex/render/epub.py +35 -0
- docvortex/render/fragments.py +70 -0
- docvortex/render/html.py +29 -0
- docvortex/render/latex.py +24 -0
- docvortex/render/markdown.py +50 -0
- docvortex/render/pdf.py +25 -0
- docvortex/render/structured_content.py +23 -0
- docvortex/resources/epub/docvortex.css +91 -0
- docvortex/resources/fasttext-langdetect/lid.176.ftz +0 -0
- docvortex/resources/fonts/DroidSansFallbackFull.ttf +0 -0
- docvortex/resources/fonts/NOTICE +190 -0
- docvortex/resources/fonts/manifest.json +9 -0
- docvortex/resources/html/docvortex.css +601 -0
- docvortex/resources/html/docvortex.min.css +1 -0
- docvortex/result.py +91 -0
- docvortex/schema.py +1141 -0
- docvortex/version.py +3 -0
- docvortex-0.2.1.dist-info/METADATA +193 -0
- docvortex-0.2.1.dist-info/RECORD +364 -0
- docvortex-0.2.1.dist-info/WHEEL +5 -0
- docvortex-0.2.1.dist-info/entry_points.txt +2 -0
- docvortex-0.2.1.dist-info/licenses/LICENSE.md +21 -0
- 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
|