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,354 @@
|
|
|
1
|
+
"""把 Excel 97–2003 BIFF 工作簿转换为 DocVortex 分页 model-list。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import collections
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any, BinaryIO
|
|
8
|
+
|
|
9
|
+
from loguru import logger
|
|
10
|
+
from openpyxl import Workbook # type: ignore[reportMissingModuleSource]
|
|
11
|
+
from openpyxl.cell.rich_text import CellRichText, TextBlock # type: ignore[reportMissingModuleSource]
|
|
12
|
+
from openpyxl.cell.text import InlineFont # type: ignore[reportMissingModuleSource]
|
|
13
|
+
from openpyxl.worksheet.worksheet import Worksheet # type: ignore[reportMissingModuleSource]
|
|
14
|
+
|
|
15
|
+
from ..errors import LegacyOfficeEncryptedError, LegacyOfficeResourceLimitError
|
|
16
|
+
from ..limits import MAX_GRID_SLOTS
|
|
17
|
+
from ..equation.mtef import decode_equation_native
|
|
18
|
+
from ..legacy.ole import BoundedOleReader
|
|
19
|
+
from ..spreadsheet.html import render_spreadsheet_table
|
|
20
|
+
from ..spreadsheet.models import AnchoredBlock, ExcelTable, FormulaMap, SheetImage
|
|
21
|
+
from ..spreadsheet.projector import SpreadsheetProjector
|
|
22
|
+
from ..streams import read_stream_bytes_from_start
|
|
23
|
+
from .....schema import BlockType
|
|
24
|
+
|
|
25
|
+
from .models import XlsChart, XlsChartSheet, XlsRichText, XlsSheet, XlsWorkbook
|
|
26
|
+
from .parser import parse_xls_workbook
|
|
27
|
+
|
|
28
|
+
_XLS_EQUATION_STREAM_RE = re.compile(
|
|
29
|
+
r"^(MBD[0-9A-F]{8})/Equation Native$",
|
|
30
|
+
re.IGNORECASE,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _read_embedded_equations(ole: BoundedOleReader) -> dict[str, str]:
|
|
35
|
+
"""读取 XLS embedding storages 中可安全解码的 Equation Native。"""
|
|
36
|
+
|
|
37
|
+
equations: dict[str, str] = {}
|
|
38
|
+
for stream_name in ole.stream_names(prefix="MBD"):
|
|
39
|
+
match = _XLS_EQUATION_STREAM_RE.match(stream_name)
|
|
40
|
+
if match is None:
|
|
41
|
+
continue
|
|
42
|
+
storage = match.group(1)
|
|
43
|
+
latex = decode_equation_native(ole.read_stream(stream_name))
|
|
44
|
+
if latex is None:
|
|
45
|
+
logger.warning(
|
|
46
|
+
"XLS_MTEF_FALLBACK: storage={!r} has an invalid or unsupported Equation Native stream",
|
|
47
|
+
storage,
|
|
48
|
+
)
|
|
49
|
+
continue
|
|
50
|
+
equations.setdefault(storage, latex)
|
|
51
|
+
return equations
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _inline_font(rich_text: XlsRichText, start: int) -> InlineFont | None:
|
|
55
|
+
"""返回覆盖指定字符位置的 openpyxl 行内字体。"""
|
|
56
|
+
|
|
57
|
+
for run in rich_text.runs:
|
|
58
|
+
if run.start <= start < run.end:
|
|
59
|
+
style = run.style
|
|
60
|
+
return InlineFont(
|
|
61
|
+
b=style.bold,
|
|
62
|
+
i=style.italic,
|
|
63
|
+
u="single" if style.underline else None,
|
|
64
|
+
strike=style.strike,
|
|
65
|
+
vertAlign=("superscript" if style.superscript else "subscript" if style.subscript else None),
|
|
66
|
+
)
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _rich_text_boundaries(value: XlsRichText) -> list[int]:
|
|
71
|
+
"""收集富文本的起止边界并裁剪到有效字符范围。"""
|
|
72
|
+
|
|
73
|
+
boundaries = {0, len(value.text)}
|
|
74
|
+
for run in value.runs:
|
|
75
|
+
boundaries.add(max(0, min(run.start, len(value.text))))
|
|
76
|
+
boundaries.add(max(0, min(run.end, len(value.text))))
|
|
77
|
+
return sorted(boundaries)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _to_openpyxl_rich_text(value: XlsRichText) -> str | CellRichText:
|
|
81
|
+
"""把内部富文本转换为 XlsxConverter 已支持的 CellRichText。"""
|
|
82
|
+
|
|
83
|
+
if not value.runs:
|
|
84
|
+
return value.text
|
|
85
|
+
parts: list[str | TextBlock] = []
|
|
86
|
+
boundaries = _rich_text_boundaries(value)
|
|
87
|
+
for start, end in zip(boundaries, boundaries[1:]):
|
|
88
|
+
if start >= end:
|
|
89
|
+
continue
|
|
90
|
+
text = value.text[start:end]
|
|
91
|
+
font = _inline_font(value, start)
|
|
92
|
+
parts.append(TextBlock(font, text) if font is not None else text)
|
|
93
|
+
return CellRichText(parts)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class _XlsPageBuilder(SpreadsheetProjector):
|
|
97
|
+
"""用轻量 openpyxl worksheet 适配器复用现有网格与 HTML 投影。"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, workbook_model: XlsWorkbook) -> None:
|
|
100
|
+
"""初始化解析结果映射和全工作簿网格预算。"""
|
|
101
|
+
|
|
102
|
+
super().__init__(include_hidden_sheets=False)
|
|
103
|
+
self.workbook_model = workbook_model
|
|
104
|
+
self._sheet_by_title: dict[str, XlsSheet] = {}
|
|
105
|
+
self._active_xls_sheet: XlsSheet | None = None
|
|
106
|
+
self._used_cells: set[tuple[int, int]] = set()
|
|
107
|
+
self._grid_slots = 0
|
|
108
|
+
|
|
109
|
+
def _build_openpyxl_workbook(self) -> Workbook:
|
|
110
|
+
"""把 BIFF 语义模型投影成不落盘的 worksheet 对象。"""
|
|
111
|
+
|
|
112
|
+
workbook = Workbook()
|
|
113
|
+
merge_grid_slots = 0
|
|
114
|
+
default_sheet = workbook.active
|
|
115
|
+
if default_sheet is not None:
|
|
116
|
+
workbook.remove(default_sheet)
|
|
117
|
+
for sheet_model in self.workbook_model.sheets:
|
|
118
|
+
worksheet = workbook.create_sheet(sheet_model.name)
|
|
119
|
+
self._sheet_by_title[worksheet.title] = sheet_model
|
|
120
|
+
if not sheet_model.visible:
|
|
121
|
+
worksheet.sheet_state = Worksheet.SHEETSTATE_HIDDEN
|
|
122
|
+
for cell_model in sheet_model.cells.values():
|
|
123
|
+
cell = worksheet.cell(row=cell_model.row + 1, column=cell_model.col + 1)
|
|
124
|
+
cell.value = _to_openpyxl_rich_text(cell_model.value)
|
|
125
|
+
if cell_model.hyperlink:
|
|
126
|
+
cell.hyperlink = cell_model.hyperlink
|
|
127
|
+
for row_first, col_first, row_last, col_last in sheet_model.merges:
|
|
128
|
+
merge_slots = (row_last - row_first + 1) * (col_last - col_first + 1)
|
|
129
|
+
if merge_slots > MAX_GRID_SLOTS - merge_grid_slots:
|
|
130
|
+
raise LegacyOfficeResourceLimitError(f"workbook extent exceeds max_grid_slots={MAX_GRID_SLOTS}")
|
|
131
|
+
merge_grid_slots += merge_slots
|
|
132
|
+
worksheet.merge_cells(
|
|
133
|
+
start_row=row_first + 1,
|
|
134
|
+
start_column=col_first + 1,
|
|
135
|
+
end_row=row_last + 1,
|
|
136
|
+
end_column=col_last + 1,
|
|
137
|
+
)
|
|
138
|
+
return workbook
|
|
139
|
+
|
|
140
|
+
def _ensure_workbook(self) -> Workbook:
|
|
141
|
+
"""惰性构造一次 openpyxl 适配工作簿。"""
|
|
142
|
+
|
|
143
|
+
workbook = getattr(self, "workbook", None)
|
|
144
|
+
if workbook is None:
|
|
145
|
+
workbook = self._build_openpyxl_workbook()
|
|
146
|
+
self.workbook = workbook
|
|
147
|
+
return workbook
|
|
148
|
+
|
|
149
|
+
def render_chart_selection(
|
|
150
|
+
self,
|
|
151
|
+
sheet_name: str,
|
|
152
|
+
rows: tuple[int, ...] | list[int],
|
|
153
|
+
cols: tuple[int, ...] | list[int],
|
|
154
|
+
) -> str | None:
|
|
155
|
+
"""把指定 worksheet 行列选择渲染为共享 chart HTML。"""
|
|
156
|
+
|
|
157
|
+
if not rows or not cols:
|
|
158
|
+
return None
|
|
159
|
+
grid_slots = len(rows) * len(cols)
|
|
160
|
+
self._grid_slots += grid_slots
|
|
161
|
+
if self._grid_slots > MAX_GRID_SLOTS:
|
|
162
|
+
raise LegacyOfficeResourceLimitError(f"workbook extent exceeds max_grid_slots={MAX_GRID_SLOTS}")
|
|
163
|
+
workbook = self._ensure_workbook()
|
|
164
|
+
if sheet_name not in workbook.sheetnames:
|
|
165
|
+
return None
|
|
166
|
+
sheet = workbook[sheet_name]
|
|
167
|
+
table = self._build_synthetic_table_from_sheet_selection(
|
|
168
|
+
sheet,
|
|
169
|
+
list(rows),
|
|
170
|
+
list(cols),
|
|
171
|
+
)
|
|
172
|
+
return render_spreadsheet_table(table)
|
|
173
|
+
|
|
174
|
+
def _chart_sheet_page(self, chart_sheet: XlsChartSheet) -> list[dict[str, Any]]:
|
|
175
|
+
"""把独立 chart sheet 投影为仅含 chart block 的逻辑页。"""
|
|
176
|
+
|
|
177
|
+
if chart_sheet.source_sheet_name is None:
|
|
178
|
+
return []
|
|
179
|
+
content = self.render_chart_selection(
|
|
180
|
+
chart_sheet.source_sheet_name,
|
|
181
|
+
chart_sheet.source_rows,
|
|
182
|
+
chart_sheet.source_cols,
|
|
183
|
+
)
|
|
184
|
+
return [{"type": BlockType.CHART, "content": content}] if content else []
|
|
185
|
+
|
|
186
|
+
def build_pages(self) -> list[list[dict[str, Any]]]:
|
|
187
|
+
"""按原目录顺序生成可见 worksheet/chart sheet 页面。"""
|
|
188
|
+
|
|
189
|
+
self.workbook = self._build_openpyxl_workbook()
|
|
190
|
+
pages_by_name: dict[str, list[dict[str, Any]]] = {}
|
|
191
|
+
for worksheet in self._iter_sheets_to_convert():
|
|
192
|
+
self._active_xls_sheet = self._sheet_by_title.get(worksheet.title)
|
|
193
|
+
self.cur_page = []
|
|
194
|
+
self._convert_sheet(worksheet)
|
|
195
|
+
pages_by_name[worksheet.title] = self.cur_page
|
|
196
|
+
|
|
197
|
+
ordered_pages: list[tuple[int, int, str, list[dict[str, Any]]]] = []
|
|
198
|
+
for index, sheet in enumerate(self.workbook_model.sheets):
|
|
199
|
+
if not sheet.visible:
|
|
200
|
+
continue
|
|
201
|
+
order = sheet.order if sheet.order >= 0 else index
|
|
202
|
+
ordered_pages.append((order, 0, sheet.name, pages_by_name.get(sheet.name, [])))
|
|
203
|
+
for index, chart_sheet in enumerate(self.workbook_model.chart_sheets):
|
|
204
|
+
if not chart_sheet.visible:
|
|
205
|
+
continue
|
|
206
|
+
order = chart_sheet.order if chart_sheet.order >= 0 else len(ordered_pages) + index
|
|
207
|
+
ordered_pages.append((order, 1, chart_sheet.name, self._chart_sheet_page(chart_sheet)))
|
|
208
|
+
ordered_pages.sort(key=lambda item: (item[0], item[1]))
|
|
209
|
+
sheet_pages = [(name, page) for _order, _kind, name, page in ordered_pages]
|
|
210
|
+
if self._should_emit_sheet_titles([page for _, page in sheet_pages]):
|
|
211
|
+
self._prepend_sheet_titles(sheet_pages)
|
|
212
|
+
return [page for _, page in sheet_pages]
|
|
213
|
+
|
|
214
|
+
def _collect_sheet_images(self, sheet: Worksheet) -> list[SheetImage]:
|
|
215
|
+
"""返回解析器已经绑定到当前 sheet 的图片。"""
|
|
216
|
+
|
|
217
|
+
sheet_model = self._sheet_by_title.get(sheet.title)
|
|
218
|
+
if sheet_model is None:
|
|
219
|
+
return []
|
|
220
|
+
return [
|
|
221
|
+
SheetImage(
|
|
222
|
+
anchor=(image.row, image.col),
|
|
223
|
+
image_base64=image.image_base64,
|
|
224
|
+
)
|
|
225
|
+
for image in sheet_model.images
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
def _map_math_formulas_to_cells(self, sheet: Worksheet) -> FormulaMap:
|
|
229
|
+
"""把 legacy Equation Editor 公式映射到表格 cell anchor。"""
|
|
230
|
+
|
|
231
|
+
math_map: dict[tuple[int, int], list[str]] = collections.defaultdict(list)
|
|
232
|
+
sheet_model = self._sheet_by_title.get(sheet.title)
|
|
233
|
+
if sheet_model is None:
|
|
234
|
+
return math_map
|
|
235
|
+
for equation in sheet_model.equations:
|
|
236
|
+
math_map[(equation.row, equation.col)].append(equation.latex)
|
|
237
|
+
return math_map
|
|
238
|
+
|
|
239
|
+
def _find_tables_in_sheet(
|
|
240
|
+
self,
|
|
241
|
+
sheet: Worksheet,
|
|
242
|
+
) -> tuple[set[tuple[int, int]], list[tuple[tuple[int, int], int, dict]]]:
|
|
243
|
+
"""记录表格已吸收坐标,供独立公式去重。"""
|
|
244
|
+
|
|
245
|
+
used_cells, artifacts = super()._find_tables_in_sheet(sheet)
|
|
246
|
+
self._used_cells = used_cells
|
|
247
|
+
return used_cells, artifacts
|
|
248
|
+
|
|
249
|
+
def _find_data_tables(self, sheet: Worksheet) -> list[ExcelTable]:
|
|
250
|
+
"""复用 XLSX 区域发现并累计限制实际物化网格。"""
|
|
251
|
+
|
|
252
|
+
tables = super()._find_data_tables(sheet)
|
|
253
|
+
self._grid_slots += sum(table.num_rows * table.num_cols for table in tables)
|
|
254
|
+
if self._grid_slots > MAX_GRID_SLOTS:
|
|
255
|
+
raise LegacyOfficeResourceLimitError(f"workbook extent exceeds max_grid_slots={MAX_GRID_SLOTS}")
|
|
256
|
+
return tables
|
|
257
|
+
|
|
258
|
+
def _chart_block(self, sheet: Worksheet, chart: XlsChart) -> dict[str, Any] | None:
|
|
259
|
+
"""把简单 chart 引用转换成数据表,并保留可用预览图片。"""
|
|
260
|
+
|
|
261
|
+
if chart.source_rows and chart.source_cols:
|
|
262
|
+
content = self.render_chart_selection(
|
|
263
|
+
sheet.title,
|
|
264
|
+
chart.source_rows,
|
|
265
|
+
chart.source_cols,
|
|
266
|
+
)
|
|
267
|
+
if content is None:
|
|
268
|
+
return None
|
|
269
|
+
block = {
|
|
270
|
+
"type": BlockType.CHART,
|
|
271
|
+
"content": content,
|
|
272
|
+
}
|
|
273
|
+
if chart.image_base64:
|
|
274
|
+
block["image_base64"] = chart.image_base64
|
|
275
|
+
return block
|
|
276
|
+
if chart.image_base64:
|
|
277
|
+
return {
|
|
278
|
+
"type": BlockType.CHART,
|
|
279
|
+
"content": "",
|
|
280
|
+
"image_base64": chart.image_base64,
|
|
281
|
+
}
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
def _find_charts_in_sheet(
|
|
285
|
+
self,
|
|
286
|
+
sheet: Worksheet,
|
|
287
|
+
) -> list[AnchoredBlock]:
|
|
288
|
+
"""按 cell anchor 输出当前工作表的 legacy chart blocks。"""
|
|
289
|
+
|
|
290
|
+
sheet_model = self._sheet_by_title.get(sheet.title)
|
|
291
|
+
if sheet_model is None:
|
|
292
|
+
return []
|
|
293
|
+
artifacts = []
|
|
294
|
+
for order, chart in enumerate(sheet_model.charts):
|
|
295
|
+
block = self._chart_block(sheet, chart)
|
|
296
|
+
if block is None:
|
|
297
|
+
continue
|
|
298
|
+
artifacts.append(((chart.row, chart.col), 10_000 + order, block))
|
|
299
|
+
for order, equation in enumerate(sheet_model.equations):
|
|
300
|
+
anchor = (equation.row, equation.col)
|
|
301
|
+
if anchor in self._used_cells:
|
|
302
|
+
continue
|
|
303
|
+
artifacts.append(
|
|
304
|
+
(
|
|
305
|
+
anchor,
|
|
306
|
+
20_000 + order,
|
|
307
|
+
{"type": BlockType.EQUATION, "content": equation.latex},
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
return artifacts
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def render_xls_chart_html(
|
|
314
|
+
workbook: XlsWorkbook,
|
|
315
|
+
sheet_name: str,
|
|
316
|
+
rows: tuple[int, ...] | list[int],
|
|
317
|
+
cols: tuple[int, ...] | list[int],
|
|
318
|
+
) -> str | None:
|
|
319
|
+
"""为 DOC/PPT 嵌入式 chart 复用 XLS 的稳定 HTML 投影。"""
|
|
320
|
+
|
|
321
|
+
return _XlsPageBuilder(workbook).render_chart_selection(
|
|
322
|
+
sheet_name,
|
|
323
|
+
rows,
|
|
324
|
+
cols,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class XlsConverter:
|
|
329
|
+
"""将 Excel 97–2003 OLE/BIFF 二进制流转换为 model-list。"""
|
|
330
|
+
|
|
331
|
+
def __init__(self) -> None:
|
|
332
|
+
"""初始化空分页输出。"""
|
|
333
|
+
|
|
334
|
+
self.pages: list[list[dict[str, Any]]] = []
|
|
335
|
+
|
|
336
|
+
def convert(self, file_binary: BinaryIO) -> None:
|
|
337
|
+
"""读取 Workbook/Book stream,解析 BIFF 并生成可见 sheet 页面。"""
|
|
338
|
+
|
|
339
|
+
file_bytes = read_stream_bytes_from_start(file_binary)
|
|
340
|
+
with BoundedOleReader(file_bytes) as ole:
|
|
341
|
+
if ole.has_stream("EncryptionInfo") or ole.has_stream("EncryptedPackage"):
|
|
342
|
+
raise LegacyOfficeEncryptedError("password-protected XLS is not supported")
|
|
343
|
+
if ole.has_stream("Workbook"):
|
|
344
|
+
workbook_stream = ole.read_stream("Workbook")
|
|
345
|
+
else:
|
|
346
|
+
workbook_stream = ole.read_stream("Book")
|
|
347
|
+
native_equations = _read_embedded_equations(ole)
|
|
348
|
+
workbook = parse_xls_workbook(
|
|
349
|
+
workbook_stream,
|
|
350
|
+
native_equations=native_equations,
|
|
351
|
+
)
|
|
352
|
+
builder = _XlsPageBuilder(workbook)
|
|
353
|
+
self.pages = builder.build_pages()
|
|
354
|
+
logger.debug("XLS parsing produced {} visible sheet pages", len(self.pages))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Any, BinaryIO
|
|
2
|
+
|
|
3
|
+
from ... import XlsxModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def convert_path(file_path: str) -> list[list[dict[str, Any]]]:
|
|
7
|
+
"""从 XLSX 文件路径调用统一模型入口。"""
|
|
8
|
+
|
|
9
|
+
with open(file_path, "rb") as fh:
|
|
10
|
+
return convert_binary(fh)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def convert_binary(file_binary: BinaryIO) -> list[list[dict[str, Any]]]:
|
|
14
|
+
"""兼容旧二进制转换函数,并转发给 XlsxModel。"""
|
|
15
|
+
|
|
16
|
+
return XlsxModel().predict(file_binary)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
print(convert_path("test_xlsx/xlsx_01.xlsx"))
|