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,561 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Office Math Markup Language (OMML)
|
|
3
|
+
|
|
4
|
+
Adapted from https://github.com/xiilei/dwml/blob/master/dwml/omml.py
|
|
5
|
+
On 23/01/2025
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
import lxml.etree as ET
|
|
11
|
+
from loguru import logger
|
|
12
|
+
from pylatexenc.latexencode import UnicodeToLatexEncoder
|
|
13
|
+
|
|
14
|
+
from .latex_dict import (
|
|
15
|
+
ALN,
|
|
16
|
+
ARR,
|
|
17
|
+
BACKSLASH,
|
|
18
|
+
BLANK,
|
|
19
|
+
BRK,
|
|
20
|
+
CHARS,
|
|
21
|
+
CHR,
|
|
22
|
+
CHR_BO,
|
|
23
|
+
CHR_DEFAULT,
|
|
24
|
+
D_DEFAULT,
|
|
25
|
+
F_DEFAULT,
|
|
26
|
+
FUNC,
|
|
27
|
+
FUNC_PLACE,
|
|
28
|
+
LIM_FUNC,
|
|
29
|
+
LIM_TO,
|
|
30
|
+
LIM_UPP,
|
|
31
|
+
POS,
|
|
32
|
+
POS_DEFAULT,
|
|
33
|
+
RAD,
|
|
34
|
+
RAD_DEFAULT,
|
|
35
|
+
SUB,
|
|
36
|
+
SUP,
|
|
37
|
+
D,
|
|
38
|
+
F,
|
|
39
|
+
M,
|
|
40
|
+
T,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
OMML_NS = "{http://schemas.openxmlformats.org/officeDocument/2006/math}"
|
|
44
|
+
|
|
45
|
+
# Mapping from OMML <m:scr> values to LaTeX math font commands.
|
|
46
|
+
# Used in do_r to convert math script/font style to appropriate LaTeX commands.
|
|
47
|
+
SCR_TO_LATEX = {
|
|
48
|
+
"script": "\\mathscr{{{0}}}", # 手写体/花体 — \mathscr covers both upper and lowercase
|
|
49
|
+
"fraktur": "\\mathfrak{{{0}}}", # 德国哥特体 — \mathfrak for upper and lowercase
|
|
50
|
+
"double-struck": "\\mathbb{{{0}}}", # 双线体/黑板粗体 — \mathbb
|
|
51
|
+
"sans-serif": "\\mathsf{{{0}}}", # 无衬线体
|
|
52
|
+
"monospace": "\\mathtt{{{0}}}", # 等宽字体
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
LOWER_GROUP_LIMITS = ("\\underbrace{", "\\underbracket{", "\\underparen{")
|
|
56
|
+
UPPER_GROUP_LIMITS = ("\\overbrace{", "\\overbracket{", "\\overparen{")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load(stream):
|
|
60
|
+
tree = ET.parse(stream)
|
|
61
|
+
for omath in tree.findall(OMML_NS + "oMath"):
|
|
62
|
+
yield oMath2Latex(omath)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def load_string(string):
|
|
66
|
+
root = ET.fromstring(string)
|
|
67
|
+
for omath in root.findall(OMML_NS + "oMath"):
|
|
68
|
+
yield oMath2Latex(omath)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def escape_latex(strs):
|
|
72
|
+
last = None
|
|
73
|
+
new_chr = []
|
|
74
|
+
strs = strs.replace(r"\\", "\\")
|
|
75
|
+
for c in strs:
|
|
76
|
+
if (c in CHARS) and (last != BACKSLASH):
|
|
77
|
+
new_chr.append(BACKSLASH + c)
|
|
78
|
+
else:
|
|
79
|
+
new_chr.append(c)
|
|
80
|
+
last = c
|
|
81
|
+
return BLANK.join(new_chr)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_val(key, default=None, store=CHR):
|
|
85
|
+
if key is not None:
|
|
86
|
+
return key if not store else store.get(key, key)
|
|
87
|
+
else:
|
|
88
|
+
return default
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _normalize_latex_delimiter(delimiter):
|
|
92
|
+
"""将 Word OMML 定界符字符转换为 LaTeX/KaTeX 可渲染的定界符。"""
|
|
93
|
+
if delimiter in ("\u2225", "\u2016"):
|
|
94
|
+
return r"\|"
|
|
95
|
+
return delimiter
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _ensure_latex_command_boundary(latex_text):
|
|
99
|
+
"""为裸 LaTeX 字母控制词补终止空格,避免后续变量被拼成未知命令。"""
|
|
100
|
+
if re.fullmatch(r"\\[A-Za-z]+", latex_text):
|
|
101
|
+
return f"{latex_text} "
|
|
102
|
+
return latex_text
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Tag2Method:
|
|
106
|
+
def call_method(self, elm, stag=None):
|
|
107
|
+
getmethod = self.tag2meth.get
|
|
108
|
+
if stag is None:
|
|
109
|
+
stag = elm.tag.replace(OMML_NS, "")
|
|
110
|
+
method = getmethod(stag)
|
|
111
|
+
if method:
|
|
112
|
+
return method(self, elm)
|
|
113
|
+
else:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
def process_children_list(self, elm, include=None):
|
|
117
|
+
"""
|
|
118
|
+
process children of the elm,return iterable
|
|
119
|
+
"""
|
|
120
|
+
for _e in list(elm):
|
|
121
|
+
if OMML_NS not in _e.tag:
|
|
122
|
+
continue
|
|
123
|
+
stag = _e.tag.replace(OMML_NS, "")
|
|
124
|
+
if include and (stag not in include):
|
|
125
|
+
continue
|
|
126
|
+
t = self.call_method(_e, stag=stag)
|
|
127
|
+
if t is None:
|
|
128
|
+
t = self.process_unknow(_e, stag)
|
|
129
|
+
if t is None:
|
|
130
|
+
continue
|
|
131
|
+
yield (stag, t, _e)
|
|
132
|
+
|
|
133
|
+
def process_children_dict(self, elm, include=None):
|
|
134
|
+
"""
|
|
135
|
+
process children of the elm,return dict
|
|
136
|
+
"""
|
|
137
|
+
latex_chars = dict()
|
|
138
|
+
for stag, t, e in self.process_children_list(elm, include):
|
|
139
|
+
latex_chars[stag] = t
|
|
140
|
+
return latex_chars
|
|
141
|
+
|
|
142
|
+
def process_children(self, elm, include=None):
|
|
143
|
+
"""
|
|
144
|
+
process children of the elm,return string
|
|
145
|
+
"""
|
|
146
|
+
return BLANK.join(
|
|
147
|
+
(t if not isinstance(t, Tag2Method) else str(t) for stag, t, e in self.process_children_list(elm, include))
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def process_unknow(self, elm, stag):
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Pr(Tag2Method):
|
|
155
|
+
text = ""
|
|
156
|
+
|
|
157
|
+
__val_tags = ("chr", "pos", "begChr", "endChr", "type")
|
|
158
|
+
|
|
159
|
+
__innerdict = None # can't use the __dict__
|
|
160
|
+
|
|
161
|
+
""" common properties of element"""
|
|
162
|
+
|
|
163
|
+
def __init__(self, elm):
|
|
164
|
+
self.__innerdict = {}
|
|
165
|
+
self.text = self.process_children(elm)
|
|
166
|
+
|
|
167
|
+
def __str__(self):
|
|
168
|
+
return self.text
|
|
169
|
+
|
|
170
|
+
def __unicode__(self):
|
|
171
|
+
return self.__str__(self)
|
|
172
|
+
|
|
173
|
+
def __getattr__(self, name):
|
|
174
|
+
return self.__innerdict.get(name, None)
|
|
175
|
+
|
|
176
|
+
def do_brk(self, elm):
|
|
177
|
+
self.__innerdict["brk"] = BRK
|
|
178
|
+
return BRK
|
|
179
|
+
|
|
180
|
+
def do_common(self, elm):
|
|
181
|
+
stag = elm.tag.replace(OMML_NS, "")
|
|
182
|
+
if stag in self.__val_tags:
|
|
183
|
+
t = elm.get(f"{OMML_NS}val")
|
|
184
|
+
self.__innerdict[stag] = t
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
tag2meth = {
|
|
188
|
+
"brk": do_brk,
|
|
189
|
+
"chr": do_common,
|
|
190
|
+
"pos": do_common,
|
|
191
|
+
"begChr": do_common,
|
|
192
|
+
"endChr": do_common,
|
|
193
|
+
"type": do_common,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class oMath2Latex(Tag2Method):
|
|
198
|
+
"""
|
|
199
|
+
Convert oMath element of omml to latex
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
_t_dict = T
|
|
203
|
+
|
|
204
|
+
__direct_tags = ("box", "sSub", "sSup", "sSubSup", "num", "den", "deg", "e")
|
|
205
|
+
u = UnicodeToLatexEncoder(
|
|
206
|
+
replacement_latex_protection="braces-all",
|
|
207
|
+
unknown_char_policy="keep",
|
|
208
|
+
unknown_char_warning=False,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def __init__(self, element):
|
|
212
|
+
self._latex = self.process_children(element)
|
|
213
|
+
|
|
214
|
+
def __str__(self):
|
|
215
|
+
return self.latex.replace(" ", " ")
|
|
216
|
+
|
|
217
|
+
def __unicode__(self):
|
|
218
|
+
return self.__str__(self)
|
|
219
|
+
|
|
220
|
+
def process_unknow(self, elm, stag):
|
|
221
|
+
if stag in self.__direct_tags:
|
|
222
|
+
return self.process_children(elm)
|
|
223
|
+
elif stag[-2:] == "Pr":
|
|
224
|
+
return Pr(elm)
|
|
225
|
+
else:
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def latex(self):
|
|
230
|
+
return self._latex
|
|
231
|
+
|
|
232
|
+
def _apply_limit_marker(self, base_text, limit_text):
|
|
233
|
+
if not isinstance(limit_text, str):
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
latex_template = CHR.get(limit_text)
|
|
237
|
+
if latex_template and "{0}" in latex_template:
|
|
238
|
+
return latex_template.format(base_text)
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
def _format_limit_like(self, base_text, limit_text, *, upper):
|
|
242
|
+
marker_wrapped = self._apply_limit_marker(base_text, limit_text)
|
|
243
|
+
if marker_wrapped is not None:
|
|
244
|
+
return marker_wrapped
|
|
245
|
+
|
|
246
|
+
if upper:
|
|
247
|
+
if isinstance(base_text, str) and base_text.lstrip().startswith(UPPER_GROUP_LIMITS):
|
|
248
|
+
return f"{base_text}{SUP.format(limit_text)}"
|
|
249
|
+
return LIM_UPP.format(lim=limit_text, text=base_text)
|
|
250
|
+
|
|
251
|
+
latex_s = LIM_FUNC.get(base_text)
|
|
252
|
+
if latex_s:
|
|
253
|
+
return latex_s.format(lim=limit_text)
|
|
254
|
+
|
|
255
|
+
if isinstance(base_text, str) and base_text.lstrip().startswith(LOWER_GROUP_LIMITS):
|
|
256
|
+
return f"{base_text}{SUB.format(limit_text)}"
|
|
257
|
+
|
|
258
|
+
return f"\\underset{{{limit_text}}}{{{base_text}}}"
|
|
259
|
+
|
|
260
|
+
def do_acc(self, elm):
|
|
261
|
+
"""
|
|
262
|
+
the accent function
|
|
263
|
+
"""
|
|
264
|
+
c_dict = self.process_children_dict(elm)
|
|
265
|
+
latex_s = get_val(c_dict["accPr"].chr, default=CHR_DEFAULT.get("ACC_VAL"), store=CHR)
|
|
266
|
+
return latex_s.format(c_dict["e"])
|
|
267
|
+
|
|
268
|
+
def do_bar(self, elm):
|
|
269
|
+
"""
|
|
270
|
+
the bar function
|
|
271
|
+
"""
|
|
272
|
+
c_dict = self.process_children_dict(elm)
|
|
273
|
+
pr = c_dict["barPr"]
|
|
274
|
+
latex_s = get_val(pr.pos, default=POS_DEFAULT.get("BAR_VAL"), store=POS)
|
|
275
|
+
return pr.text + latex_s.format(c_dict["e"])
|
|
276
|
+
|
|
277
|
+
def do_d(self, elm):
|
|
278
|
+
"""
|
|
279
|
+
the delimiter object
|
|
280
|
+
"""
|
|
281
|
+
c_dict = self.process_children_dict(elm)
|
|
282
|
+
pr = c_dict["dPr"]
|
|
283
|
+
null = D_DEFAULT.get("null")
|
|
284
|
+
|
|
285
|
+
s_val = _normalize_latex_delimiter(get_val(pr.begChr, default=D_DEFAULT.get("left"), store=T))
|
|
286
|
+
e_val = _normalize_latex_delimiter(get_val(pr.endChr, default=D_DEFAULT.get("right"), store=T))
|
|
287
|
+
delim = pr.text + D.format(
|
|
288
|
+
left=null if not s_val else escape_latex(s_val),
|
|
289
|
+
text=c_dict["e"],
|
|
290
|
+
right=null if not e_val else escape_latex(e_val),
|
|
291
|
+
)
|
|
292
|
+
return delim
|
|
293
|
+
|
|
294
|
+
def do_spre(self, elm):
|
|
295
|
+
"""
|
|
296
|
+
the Pre-Sub-Superscript object -- Not support yet
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def do_sub(self, elm):
|
|
300
|
+
text = self.process_children(elm)
|
|
301
|
+
return SUB.format(text)
|
|
302
|
+
|
|
303
|
+
def do_sup(self, elm):
|
|
304
|
+
text = self.process_children(elm)
|
|
305
|
+
return SUP.format(text)
|
|
306
|
+
|
|
307
|
+
def do_f(self, elm):
|
|
308
|
+
"""
|
|
309
|
+
the fraction object
|
|
310
|
+
"""
|
|
311
|
+
c_dict = self.process_children_dict(elm)
|
|
312
|
+
pr = c_dict.get("fPr")
|
|
313
|
+
if pr is None:
|
|
314
|
+
# Handle missing fPr element gracefully
|
|
315
|
+
logger.debug("Missing fPr element in fraction, using default formatting")
|
|
316
|
+
latex_s = F_DEFAULT
|
|
317
|
+
return latex_s.format(
|
|
318
|
+
num=c_dict.get("num"),
|
|
319
|
+
den=c_dict.get("den"),
|
|
320
|
+
)
|
|
321
|
+
latex_s = get_val(pr.type, default=F_DEFAULT, store=F)
|
|
322
|
+
return pr.text + latex_s.format(num=c_dict.get("num"), den=c_dict.get("den"))
|
|
323
|
+
|
|
324
|
+
def do_func(self, elm):
|
|
325
|
+
"""
|
|
326
|
+
the Function-Apply object (Examples:sin cos)
|
|
327
|
+
"""
|
|
328
|
+
c_dict = self.process_children_dict(elm)
|
|
329
|
+
func_name = c_dict.get("fName")
|
|
330
|
+
return func_name.replace(FUNC_PLACE, c_dict.get("e"))
|
|
331
|
+
|
|
332
|
+
def do_fname(self, elm):
|
|
333
|
+
"""
|
|
334
|
+
the func name
|
|
335
|
+
"""
|
|
336
|
+
latex_chars = []
|
|
337
|
+
for stag, t, e in self.process_children_list(elm):
|
|
338
|
+
if stag == "r":
|
|
339
|
+
if FUNC.get(t):
|
|
340
|
+
latex_chars.append(FUNC[t])
|
|
341
|
+
else:
|
|
342
|
+
logger.warning("Function not supported, will default to text: %s", t)
|
|
343
|
+
if isinstance(t, str):
|
|
344
|
+
latex_chars.append(t)
|
|
345
|
+
elif isinstance(t, str):
|
|
346
|
+
latex_chars.append(t)
|
|
347
|
+
t = BLANK.join(latex_chars)
|
|
348
|
+
return t if FUNC_PLACE in t else t + FUNC_PLACE # do_func will replace this
|
|
349
|
+
|
|
350
|
+
def do_groupchr(self, elm):
|
|
351
|
+
"""
|
|
352
|
+
the Group-Character object
|
|
353
|
+
"""
|
|
354
|
+
c_dict = self.process_children_dict(elm)
|
|
355
|
+
pr = c_dict["groupChrPr"]
|
|
356
|
+
latex_s = get_val(pr.chr)
|
|
357
|
+
return pr.text + latex_s.format(c_dict["e"])
|
|
358
|
+
|
|
359
|
+
def do_rad(self, elm):
|
|
360
|
+
"""
|
|
361
|
+
the radical object
|
|
362
|
+
"""
|
|
363
|
+
c_dict = self.process_children_dict(elm)
|
|
364
|
+
text = c_dict.get("e")
|
|
365
|
+
deg_text = c_dict.get("deg")
|
|
366
|
+
if deg_text:
|
|
367
|
+
return RAD.format(deg=deg_text, text=text)
|
|
368
|
+
else:
|
|
369
|
+
return RAD_DEFAULT.format(text=text)
|
|
370
|
+
|
|
371
|
+
def do_eqarr(self, elm):
|
|
372
|
+
"""
|
|
373
|
+
the Array object.
|
|
374
|
+
|
|
375
|
+
Handles two cases:
|
|
376
|
+
1. Single-row eqArr with a right-aligned equation tag encoded as
|
|
377
|
+
``\\#(n)`` at the end of the row (OMML column-alignment syntax).
|
|
378
|
+
The ``#`` column separator and the equation number ``(n)`` are
|
|
379
|
+
converted to LaTeX ``\\tag{n}`` so KaTeX can render the tag,
|
|
380
|
+
and the unnecessary ``\\begin{array}{c}...\\end{array}`` wrapper
|
|
381
|
+
is omitted.
|
|
382
|
+
2. Single-row eqArr without a tag: content is returned as-is (no
|
|
383
|
+
array wrapper needed).
|
|
384
|
+
3. Multi-row eqArr: kept as ``\\begin{array}{c}...\\end{array}``.
|
|
385
|
+
"""
|
|
386
|
+
rows = [t for stag, t, e in self.process_children_list(elm, include=("e",))]
|
|
387
|
+
|
|
388
|
+
if len(rows) == 1:
|
|
389
|
+
row = rows[0]
|
|
390
|
+
# Detect the OMML equation-tag pattern: the text element "#(n)" is
|
|
391
|
+
# stored verbatim inside the row; do_r converts "#" via pylatexenc
|
|
392
|
+
# to "\# " (escaped hash with surrounding spaces due to brace-
|
|
393
|
+
# protection stripping). Match that at the end of the row,
|
|
394
|
+
# allowing optional whitespace between "\#" and the opening "(".
|
|
395
|
+
tag_match = re.search(r"\\#\s*\(([^)]*)\)\s*$", row)
|
|
396
|
+
if tag_match:
|
|
397
|
+
formula = row[: tag_match.start()].rstrip()
|
|
398
|
+
tag_content = tag_match.group(1)
|
|
399
|
+
return f"{formula}\\tag{{{tag_content}}}"
|
|
400
|
+
# Single row without tag — no array wrapper required.
|
|
401
|
+
return row
|
|
402
|
+
|
|
403
|
+
return ARR.format(text=BRK.join(rows))
|
|
404
|
+
|
|
405
|
+
def do_limlow(self, elm):
|
|
406
|
+
"""
|
|
407
|
+
the Lower-Limit object
|
|
408
|
+
"""
|
|
409
|
+
t_dict = self.process_children_dict(elm, include=("e", "lim"))
|
|
410
|
+
return self._format_limit_like(
|
|
411
|
+
t_dict.get("e", ""),
|
|
412
|
+
t_dict.get("lim", ""),
|
|
413
|
+
upper=False,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
def do_limupp(self, elm):
|
|
417
|
+
"""
|
|
418
|
+
the Upper-Limit object
|
|
419
|
+
"""
|
|
420
|
+
t_dict = self.process_children_dict(elm, include=("e", "lim"))
|
|
421
|
+
return self._format_limit_like(
|
|
422
|
+
t_dict.get("e", ""),
|
|
423
|
+
t_dict.get("lim", ""),
|
|
424
|
+
upper=True,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def do_lim(self, elm):
|
|
428
|
+
"""
|
|
429
|
+
the lower limit of the limLow object and the upper limit of the limUpp function
|
|
430
|
+
"""
|
|
431
|
+
return self.process_children(elm).replace(LIM_TO[0], LIM_TO[1])
|
|
432
|
+
|
|
433
|
+
def do_m(self, elm):
|
|
434
|
+
"""
|
|
435
|
+
the Matrix object
|
|
436
|
+
"""
|
|
437
|
+
rows = []
|
|
438
|
+
for stag, t, e in self.process_children_list(elm):
|
|
439
|
+
if stag == "mPr":
|
|
440
|
+
pass
|
|
441
|
+
elif stag == "mr":
|
|
442
|
+
rows.append(t)
|
|
443
|
+
return M.format(text=BRK.join(rows))
|
|
444
|
+
|
|
445
|
+
def do_mr(self, elm):
|
|
446
|
+
"""
|
|
447
|
+
a single row of the matrix m
|
|
448
|
+
"""
|
|
449
|
+
return ALN.join([t for stag, t, e in self.process_children_list(elm, include=("e",))])
|
|
450
|
+
|
|
451
|
+
def do_nary(self, elm):
|
|
452
|
+
"""
|
|
453
|
+
the n-ary object
|
|
454
|
+
"""
|
|
455
|
+
res = []
|
|
456
|
+
bo = ""
|
|
457
|
+
for stag, t, e in self.process_children_list(elm):
|
|
458
|
+
if stag == "naryPr":
|
|
459
|
+
# if <m:naryPr> contains no <m:chr>, the n-ary represents an integral
|
|
460
|
+
bo = get_val(t.chr, default="\\int", store=CHR_BO)
|
|
461
|
+
else:
|
|
462
|
+
res.append(t)
|
|
463
|
+
return bo + BLANK.join(res)
|
|
464
|
+
|
|
465
|
+
def process_unicode(self, s):
|
|
466
|
+
if s in CHARS:
|
|
467
|
+
return BACKSLASH + s
|
|
468
|
+
|
|
469
|
+
# Check T dictionary first for known math-mode symbols.
|
|
470
|
+
# The T dictionary holds explicit math-mode LaTeX mappings and takes precedence
|
|
471
|
+
# over pylatexenc, which uses text-mode mappings by default and therefore produces
|
|
472
|
+
# text-mode commands like \textperiodcentered (for U+00B7 ·) that are invalid
|
|
473
|
+
# inside math environments.
|
|
474
|
+
t_result = self._t_dict.get(s)
|
|
475
|
+
if t_result is not None:
|
|
476
|
+
return t_result
|
|
477
|
+
|
|
478
|
+
out_latex_str = self.u.unicode_to_latex(s)
|
|
479
|
+
|
|
480
|
+
# pylatexenc常把数学字符包成 {\ensuremath{...}},这里只剥离外层包装,
|
|
481
|
+
# 不能删除内部LaTeX命令的闭合花括号。
|
|
482
|
+
if out_latex_str.startswith(r"{\ensuremath{") and out_latex_str.endswith("}}"):
|
|
483
|
+
out_latex_str = out_latex_str[len(r"{\ensuremath{") : -2]
|
|
484
|
+
out_latex_str = _ensure_latex_command_boundary(out_latex_str)
|
|
485
|
+
elif out_latex_str.startswith(r"\ensuremath{") and out_latex_str.endswith("}"):
|
|
486
|
+
out_latex_str = out_latex_str[len(r"\ensuremath{") : -1]
|
|
487
|
+
out_latex_str = _ensure_latex_command_boundary(out_latex_str)
|
|
488
|
+
elif (
|
|
489
|
+
s.startswith("{") is False
|
|
490
|
+
and out_latex_str.startswith("{")
|
|
491
|
+
and s.endswith("}") is False
|
|
492
|
+
and out_latex_str.endswith("}")
|
|
493
|
+
):
|
|
494
|
+
out_latex_str = f" {out_latex_str[1:-1]} "
|
|
495
|
+
|
|
496
|
+
# Do NOT wrap remaining content in \text{}.
|
|
497
|
+
# Previously this code matched any string starting with "\text" and wrapped it
|
|
498
|
+
# again, producing invalid constructs like \text{ \textperiodcentered } for
|
|
499
|
+
# textcomp symbols. Characters that truly need text mode should be mapped in
|
|
500
|
+
# the T dictionary above; for all others we keep the pylatexenc output as-is.
|
|
501
|
+
|
|
502
|
+
return out_latex_str
|
|
503
|
+
|
|
504
|
+
def do_r(self, elm):
|
|
505
|
+
"""
|
|
506
|
+
Get text from 'r' element,And try convert them to latex symbols
|
|
507
|
+
@todo text style support , (sty)
|
|
508
|
+
@todo \text (latex pure text support)
|
|
509
|
+
"""
|
|
510
|
+
_str = []
|
|
511
|
+
_base_str = []
|
|
512
|
+
found_text = elm.findtext(f"./{OMML_NS}t")
|
|
513
|
+
if found_text:
|
|
514
|
+
for s in found_text:
|
|
515
|
+
out_latex_str = self.process_unicode(s)
|
|
516
|
+
_str.append(out_latex_str)
|
|
517
|
+
_base_str.append(s)
|
|
518
|
+
|
|
519
|
+
proc_str = escape_latex(BLANK.join(_str))
|
|
520
|
+
base_proc_str = BLANK.join(_base_str)
|
|
521
|
+
|
|
522
|
+
if "{" not in base_proc_str and "\\{" in proc_str:
|
|
523
|
+
proc_str = proc_str.replace("\\{", "{")
|
|
524
|
+
|
|
525
|
+
if "}" not in base_proc_str and "\\}" in proc_str:
|
|
526
|
+
proc_str = proc_str.replace("\\}", "}")
|
|
527
|
+
|
|
528
|
+
# Handle <m:scr> math font style (script, fraktur, double-struck, etc.)
|
|
529
|
+
# OMML encodes math alphabets via <m:rPr><m:scr m:val="..."/> rather than
|
|
530
|
+
# Unicode math-alphabet codepoints, so we must apply the LaTeX wrapper here.
|
|
531
|
+
rPr = elm.find(f"{OMML_NS}rPr")
|
|
532
|
+
if rPr is not None:
|
|
533
|
+
scr_elem = rPr.find(f"{OMML_NS}scr")
|
|
534
|
+
if scr_elem is not None:
|
|
535
|
+
scr_val = scr_elem.get(f"{OMML_NS}val")
|
|
536
|
+
latex_template = SCR_TO_LATEX.get(scr_val)
|
|
537
|
+
if latex_template and proc_str.strip():
|
|
538
|
+
proc_str = latex_template.format(proc_str.strip())
|
|
539
|
+
|
|
540
|
+
return proc_str
|
|
541
|
+
|
|
542
|
+
tag2meth = {
|
|
543
|
+
"acc": do_acc,
|
|
544
|
+
"r": do_r,
|
|
545
|
+
"bar": do_bar,
|
|
546
|
+
"sub": do_sub,
|
|
547
|
+
"sup": do_sup,
|
|
548
|
+
"f": do_f,
|
|
549
|
+
"func": do_func,
|
|
550
|
+
"fName": do_fname,
|
|
551
|
+
"groupChr": do_groupchr,
|
|
552
|
+
"d": do_d,
|
|
553
|
+
"rad": do_rad,
|
|
554
|
+
"eqArr": do_eqarr,
|
|
555
|
+
"limLow": do_limlow,
|
|
556
|
+
"limUpp": do_limupp,
|
|
557
|
+
"lim": do_lim,
|
|
558
|
+
"m": do_m,
|
|
559
|
+
"mr": do_mr,
|
|
560
|
+
"nary": do_nary,
|
|
561
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""现代 Office OOXML 包中的 MathType/Equation OLE 公式解码适配器。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
import hashlib
|
|
7
|
+
|
|
8
|
+
from ..errors import LegacyOfficeResourceLimitError
|
|
9
|
+
from ..limits import MAX_ASSET_TOTAL_BYTES, MAX_ENTRY_BYTES
|
|
10
|
+
from .mtef import decode_equation_object
|
|
11
|
+
|
|
12
|
+
CFB_MAGIC = b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1"
|
|
13
|
+
EQUATION_PROG_ID = "equation"
|
|
14
|
+
EQUATION_PROG_ID_PREFIX = "equation."
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_mathtype_equation_prog_id(prog_id: object | None) -> bool:
|
|
18
|
+
"""判断 OLE ProgID 是否为 Equation 或带非空版本后缀的 Equation.*。"""
|
|
19
|
+
|
|
20
|
+
if not isinstance(prog_id, str):
|
|
21
|
+
return False
|
|
22
|
+
normalized = prog_id.strip().casefold()
|
|
23
|
+
return normalized == EQUATION_PROG_ID or (
|
|
24
|
+
normalized.startswith(EQUATION_PROG_ID_PREFIX) and len(normalized) > len(EQUATION_PROG_ID_PREFIX)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(slots=True)
|
|
29
|
+
class OoxmlEquationDecoder:
|
|
30
|
+
"""按共享资源上限缓存并解码 OOXML 中的公式 OLE 对象。"""
|
|
31
|
+
|
|
32
|
+
total_bytes: int = 0
|
|
33
|
+
_cache: dict[bytes, str | None] = field(default_factory=dict)
|
|
34
|
+
|
|
35
|
+
def decode(
|
|
36
|
+
self,
|
|
37
|
+
blob: bytes | None,
|
|
38
|
+
*,
|
|
39
|
+
prog_id: object | None,
|
|
40
|
+
show_as_icon: bool = False,
|
|
41
|
+
) -> str | None:
|
|
42
|
+
"""校验公式 ProgID、图标模式、CFB 头和资源预算后返回 LaTeX。"""
|
|
43
|
+
|
|
44
|
+
if show_as_icon or not is_mathtype_equation_prog_id(prog_id) or blob is None:
|
|
45
|
+
return None
|
|
46
|
+
if not isinstance(blob, bytes):
|
|
47
|
+
return None
|
|
48
|
+
if len(blob) > MAX_ENTRY_BYTES:
|
|
49
|
+
raise LegacyOfficeResourceLimitError(f"OOXML equation object exceeds max_entry_bytes={MAX_ENTRY_BYTES}")
|
|
50
|
+
if not blob.startswith(CFB_MAGIC):
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
digest = hashlib.sha256(blob).digest()
|
|
54
|
+
if digest in self._cache:
|
|
55
|
+
return self._cache[digest]
|
|
56
|
+
if self.total_bytes + len(blob) > MAX_ASSET_TOTAL_BYTES:
|
|
57
|
+
raise LegacyOfficeResourceLimitError(f"OOXML equation objects exceed max_asset_total_bytes={MAX_ASSET_TOTAL_BYTES}")
|
|
58
|
+
|
|
59
|
+
self.total_bytes += len(blob)
|
|
60
|
+
latex = decode_equation_object(blob)
|
|
61
|
+
self._cache[digest] = latex
|
|
62
|
+
return latex
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Flash Office 二进制、嵌入对象与 RTF 解析共享的稳定错误类型。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LegacyOfficeError(ValueError):
|
|
7
|
+
"""旧版 Office 解析错误基类,并携带稳定错误码。"""
|
|
8
|
+
|
|
9
|
+
code = "legacy_office_error"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LegacyOfficeMalformedError(LegacyOfficeError):
|
|
13
|
+
"""输入容器或核心二进制记录无法形成有效文档。"""
|
|
14
|
+
|
|
15
|
+
code = "malformed"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LegacyOfficeMissingPartError(LegacyOfficeError):
|
|
19
|
+
"""缺少完成解析所必需的 OLE stream。"""
|
|
20
|
+
|
|
21
|
+
code = "missing_part"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LegacyOfficeEncryptedError(LegacyOfficeError):
|
|
25
|
+
"""输入使用了当前纯 Python 解析链不支持的加密。"""
|
|
26
|
+
|
|
27
|
+
code = "encrypted"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LegacyOfficeResourceLimitError(LegacyOfficeError):
|
|
31
|
+
"""输入超过固定安全限制。"""
|
|
32
|
+
|
|
33
|
+
code = "resource_limit"
|