docvortex 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. docvortex/__init__.py +21 -0
  2. docvortex/analyzers/__init__.py +3 -0
  3. docvortex/analyzers/native/__init__.py +37 -0
  4. docvortex/analyzers/native/_shared/__init__.py +3 -0
  5. docvortex/analyzers/native/_shared/hyperlink.py +16 -0
  6. docvortex/analyzers/native/_shared/image.py +8 -0
  7. docvortex/analyzers/native/_shared/markup/__init__.py +47 -0
  8. docvortex/analyzers/native/_shared/markup/anchors.py +30 -0
  9. docvortex/analyzers/native/_shared/markup/formula.py +37 -0
  10. docvortex/analyzers/native/_shared/markup/projector.py +57 -0
  11. docvortex/analyzers/native/_shared/markup/styles.py +19 -0
  12. docvortex/analyzers/native/_shared/mathml.py +17 -0
  13. docvortex/analyzers/native/_shared/names.py +7 -0
  14. docvortex/analyzers/native/_shared/xycut.py +414 -0
  15. docvortex/analyzers/native/contracts.py +44 -0
  16. docvortex/analyzers/native/csv.py +351 -0
  17. docvortex/analyzers/native/epub/__init__.py +18 -0
  18. docvortex/analyzers/native/epub/constants.py +45 -0
  19. docvortex/analyzers/native/epub/converter.py +80 -0
  20. docvortex/analyzers/native/epub/errors.py +20 -0
  21. docvortex/analyzers/native/epub/metadata.py +31 -0
  22. docvortex/analyzers/native/epub/package.py +649 -0
  23. docvortex/analyzers/native/epub/xhtml.py +306 -0
  24. docvortex/analyzers/native/html/__init__.py +6 -0
  25. docvortex/analyzers/native/html/anchors.py +271 -0
  26. docvortex/analyzers/native/html/constants.py +25 -0
  27. docvortex/analyzers/native/html/contracts.py +7 -0
  28. docvortex/analyzers/native/html/converter.py +117 -0
  29. docvortex/analyzers/native/html/document.py +389 -0
  30. docvortex/analyzers/native/html/errors.py +12 -0
  31. docvortex/analyzers/native/html/resources.py +365 -0
  32. docvortex/analyzers/native/html/selector.py +421 -0
  33. docvortex/analyzers/native/models.py +209 -0
  34. docvortex/analyzers/native/ofd/__init__.py +14 -0
  35. docvortex/analyzers/native/ofd/constants.py +59 -0
  36. docvortex/analyzers/native/ofd/converter.py +31 -0
  37. docvortex/analyzers/native/ofd/errors.py +16 -0
  38. docvortex/analyzers/native/ofd/geometry.py +215 -0
  39. docvortex/analyzers/native/ofd/images.py +124 -0
  40. docvortex/analyzers/native/ofd/metadata.py +53 -0
  41. docvortex/analyzers/native/ofd/models.py +170 -0
  42. docvortex/analyzers/native/ofd/package.py +346 -0
  43. docvortex/analyzers/native/ofd/path.py +201 -0
  44. docvortex/analyzers/native/ofd/reading_order.py +293 -0
  45. docvortex/analyzers/native/ofd/resources.py +122 -0
  46. docvortex/analyzers/native/ofd/scene.py +347 -0
  47. docvortex/analyzers/native/ofd/table.py +240 -0
  48. docvortex/analyzers/native/ofd/text.py +521 -0
  49. docvortex/analyzers/native/office/__init__.py +3 -0
  50. docvortex/analyzers/native/office/doc/__init__.py +3 -0
  51. docvortex/analyzers/native/office/doc/bookmarks.py +81 -0
  52. docvortex/analyzers/native/office/doc/doc_converter.py +573 -0
  53. docvortex/analyzers/native/office/doc/fib.py +261 -0
  54. docvortex/analyzers/native/office/doc/fields.py +101 -0
  55. docvortex/analyzers/native/office/doc/formatting.py +171 -0
  56. docvortex/analyzers/native/office/doc/images.py +162 -0
  57. docvortex/analyzers/native/office/doc/lists.py +356 -0
  58. docvortex/analyzers/native/office/doc/models.py +164 -0
  59. docvortex/analyzers/native/office/doc/parser.py +851 -0
  60. docvortex/analyzers/native/office/doc/pieces.py +256 -0
  61. docvortex/analyzers/native/office/doc/records.py +40 -0
  62. docvortex/analyzers/native/office/doc/sprm.py +269 -0
  63. docvortex/analyzers/native/office/doc/styles.py +215 -0
  64. docvortex/analyzers/native/office/docx/__init__.py +3 -0
  65. docvortex/analyzers/native/office/docx/context.py +71 -0
  66. docvortex/analyzers/native/office/docx/docx_converter.py +614 -0
  67. docvortex/analyzers/native/office/docx/equationxml.py +119 -0
  68. docvortex/analyzers/native/office/docx/fields.py +816 -0
  69. docvortex/analyzers/native/office/docx/formatting_types.py +23 -0
  70. docvortex/analyzers/native/office/docx/main.py +50 -0
  71. docvortex/analyzers/native/office/docx/numbering.py +491 -0
  72. docvortex/analyzers/native/office/docx/office_xml.py +57 -0
  73. docvortex/analyzers/native/office/docx/package_normalizer.py +248 -0
  74. docvortex/analyzers/native/office/docx/resources.py +526 -0
  75. docvortex/analyzers/native/office/docx/styles.py +693 -0
  76. docvortex/analyzers/native/office/docx/tables.py +515 -0
  77. docvortex/analyzers/native/office/equation/__init__.py +3 -0
  78. docvortex/analyzers/native/office/equation/image.py +470 -0
  79. docvortex/analyzers/native/office/equation/latex_dict.py +324 -0
  80. docvortex/analyzers/native/office/equation/mtef.py +885 -0
  81. docvortex/analyzers/native/office/equation/mtef_v5.py +941 -0
  82. docvortex/analyzers/native/office/equation/omml.py +561 -0
  83. docvortex/analyzers/native/office/equation/ooxml.py +62 -0
  84. docvortex/analyzers/native/office/errors.py +33 -0
  85. docvortex/analyzers/native/office/image.py +307 -0
  86. docvortex/analyzers/native/office/legacy/__init__.py +3 -0
  87. docvortex/analyzers/native/office/legacy/binary.py +43 -0
  88. docvortex/analyzers/native/office/legacy/officeart.py +362 -0
  89. docvortex/analyzers/native/office/legacy/ole.py +110 -0
  90. docvortex/analyzers/native/office/limits.py +12 -0
  91. docvortex/analyzers/native/office/odf/__init__.py +3 -0
  92. docvortex/analyzers/native/office/odf/chart.py +76 -0
  93. docvortex/analyzers/native/office/odf/constants.py +70 -0
  94. docvortex/analyzers/native/office/odf/converters.py +403 -0
  95. docvortex/analyzers/native/office/odf/errors.py +18 -0
  96. docvortex/analyzers/native/office/odf/metadata.py +91 -0
  97. docvortex/analyzers/native/office/odf/models.py +176 -0
  98. docvortex/analyzers/native/office/odf/package.py +270 -0
  99. docvortex/analyzers/native/office/odf/styles.py +329 -0
  100. docvortex/analyzers/native/office/odf/table.py +469 -0
  101. docvortex/analyzers/native/office/odf/text.py +1002 -0
  102. docvortex/analyzers/native/office/ooxml_chart.py +1016 -0
  103. docvortex/analyzers/native/office/opc.py +38 -0
  104. docvortex/analyzers/native/office/ppt/__init__.py +3 -0
  105. docvortex/analyzers/native/office/ppt/models.py +118 -0
  106. docvortex/analyzers/native/office/ppt/parser.py +1895 -0
  107. docvortex/analyzers/native/office/ppt/ppt_converter.py +292 -0
  108. docvortex/analyzers/native/office/ppt/records.py +131 -0
  109. docvortex/analyzers/native/office/ppt/style_text.py +247 -0
  110. docvortex/analyzers/native/office/pptx/__init__.py +3 -0
  111. docvortex/analyzers/native/office/pptx/context.py +113 -0
  112. docvortex/analyzers/native/office/pptx/lists.py +558 -0
  113. docvortex/analyzers/native/office/pptx/main.py +20 -0
  114. docvortex/analyzers/native/office/pptx/package_normalizer.py +321 -0
  115. docvortex/analyzers/native/office/pptx/pptx_converter.py +323 -0
  116. docvortex/analyzers/native/office/pptx/resources.py +329 -0
  117. docvortex/analyzers/native/office/pptx/shapes.py +393 -0
  118. docvortex/analyzers/native/office/pptx/text_styles.py +625 -0
  119. docvortex/analyzers/native/office/pptx/titles.py +178 -0
  120. docvortex/analyzers/native/office/rich_text.py +420 -0
  121. docvortex/analyzers/native/office/rtf/__init__.py +3 -0
  122. docvortex/analyzers/native/office/rtf/converter.py +708 -0
  123. docvortex/analyzers/native/office/rtf/lexer.py +213 -0
  124. docvortex/analyzers/native/office/rtf/math.py +339 -0
  125. docvortex/analyzers/native/office/rtf/models.py +185 -0
  126. docvortex/analyzers/native/office/rtf/parser.py +1553 -0
  127. docvortex/analyzers/native/office/spreadsheet/__init__.py +3 -0
  128. docvortex/analyzers/native/office/spreadsheet/html.py +81 -0
  129. docvortex/analyzers/native/office/spreadsheet/models.py +79 -0
  130. docvortex/analyzers/native/office/spreadsheet/projector.py +928 -0
  131. docvortex/analyzers/native/office/streams.py +18 -0
  132. docvortex/analyzers/native/office/xls/__init__.py +3 -0
  133. docvortex/analyzers/native/office/xls/chart.py +132 -0
  134. docvortex/analyzers/native/office/xls/embedded_chart.py +299 -0
  135. docvortex/analyzers/native/office/xls/models.py +109 -0
  136. docvortex/analyzers/native/office/xls/number_format.py +521 -0
  137. docvortex/analyzers/native/office/xls/parser.py +1145 -0
  138. docvortex/analyzers/native/office/xls/records.py +201 -0
  139. docvortex/analyzers/native/office/xls/strings.py +205 -0
  140. docvortex/analyzers/native/office/xls/xls_converter.py +354 -0
  141. docvortex/analyzers/native/office/xlsx/__init__.py +3 -0
  142. docvortex/analyzers/native/office/xlsx/main.py +20 -0
  143. docvortex/analyzers/native/office/xlsx/ooxml_ole.py +522 -0
  144. docvortex/analyzers/native/office/xlsx/package_normalizer.py +310 -0
  145. docvortex/analyzers/native/office/xlsx/xlsx_converter.py +716 -0
  146. docvortex/analyzers/native/pdf/__init__.py +3 -0
  147. docvortex/analyzers/native/pdf/auxiliary_text.py +1671 -0
  148. docvortex/analyzers/native/pdf/char_geometry.py +1662 -0
  149. docvortex/analyzers/native/pdf/code_blocks.py +535 -0
  150. docvortex/analyzers/native/pdf/formulas.py +1985 -0
  151. docvortex/analyzers/native/pdf/geometry.py +281 -0
  152. docvortex/analyzers/native/pdf/graphics.py +1501 -0
  153. docvortex/analyzers/native/pdf/index_blocks.py +268 -0
  154. docvortex/analyzers/native/pdf/inline/__init__.py +3 -0
  155. docvortex/analyzers/native/pdf/inline/common.py +112 -0
  156. docvortex/analyzers/native/pdf/inline/detection.py +590 -0
  157. docvortex/analyzers/native/pdf/inline/matching.py +1185 -0
  158. docvortex/analyzers/native/pdf/inline/materialize.py +457 -0
  159. docvortex/analyzers/native/pdf/inline/scripts.py +975 -0
  160. docvortex/analyzers/native/pdf/inline/types.py +385 -0
  161. docvortex/analyzers/native/pdf/line_layout.py +1106 -0
  162. docvortex/analyzers/native/pdf/line_merging.py +1223 -0
  163. docvortex/analyzers/native/pdf/models.py +246 -0
  164. docvortex/analyzers/native/pdf/native_text.py +1004 -0
  165. docvortex/analyzers/native/pdf/pipeline.py +1390 -0
  166. docvortex/analyzers/native/pdf/script_geometry.py +636 -0
  167. docvortex/analyzers/native/pdf/shared.py +30 -0
  168. docvortex/analyzers/native/pdf/spatial_text.py +383 -0
  169. docvortex/analyzers/native/pdf/table_annotations.py +446 -0
  170. docvortex/analyzers/native/pdf/table_constants.py +48 -0
  171. docvortex/analyzers/native/pdf/table_detection.py +227 -0
  172. docvortex/analyzers/native/pdf/table_filled_grid.py +218 -0
  173. docvortex/analyzers/native/pdf/table_geometry.py +40 -0
  174. docvortex/analyzers/native/pdf/table_materialization.py +444 -0
  175. docvortex/analyzers/native/pdf/table_recovery/__init__.py +15 -0
  176. docvortex/analyzers/native/pdf/table_recovery/candidate.py +356 -0
  177. docvortex/analyzers/native/pdf/table_recovery/contracts.py +154 -0
  178. docvortex/analyzers/native/pdf/table_recovery/engine.py +609 -0
  179. docvortex/analyzers/native/pdf/table_recovery/geometry.py +164 -0
  180. docvortex/analyzers/native/pdf/table_recovery/sparse_common.py +85 -0
  181. docvortex/analyzers/native/pdf/table_recovery/sparse_hybrid.py +804 -0
  182. docvortex/analyzers/native/pdf/table_recovery/sparse_multiline.py +1122 -0
  183. docvortex/analyzers/native/pdf/table_recovery/text.py +414 -0
  184. docvortex/analyzers/native/pdf/table_recovery/text_grid.py +618 -0
  185. docvortex/analyzers/native/pdf/table_recovery/vector.py +1931 -0
  186. docvortex/analyzers/native/pdf/table_rows.py +34 -0
  187. docvortex/analyzers/native/pdf/table_rules.py +1129 -0
  188. docvortex/analyzers/native/pdf/table_text_styles.py +283 -0
  189. docvortex/analyzers/native/pdf/tables.py +147 -0
  190. docvortex/analyzers/native/pdf/text_assembly/__init__.py +3 -0
  191. docvortex/analyzers/native/pdf/text_assembly/annotations.py +581 -0
  192. docvortex/analyzers/native/pdf/text_assembly/assembly.py +292 -0
  193. docvortex/analyzers/native/pdf/text_assembly/common.py +477 -0
  194. docvortex/analyzers/native/pdf/text_assembly/footnotes.py +394 -0
  195. docvortex/analyzers/native/pdf/text_assembly/merging.py +1274 -0
  196. docvortex/analyzers/native/pdf/text_assembly/rows.py +692 -0
  197. docvortex/analyzers/native/pdf/text_blocks.py +82 -0
  198. docvortex/analyzers/native/pdf/text_styles.py +55 -0
  199. docvortex/analyzers/native/pdf/title_analysis/__init__.py +3 -0
  200. docvortex/analyzers/native/pdf/title_analysis/body_profile.py +215 -0
  201. docvortex/analyzers/native/pdf/title_analysis/common.py +117 -0
  202. docvortex/analyzers/native/pdf/title_analysis/document_profile.py +164 -0
  203. docvortex/analyzers/native/pdf/title_analysis/lane_titles.py +758 -0
  204. docvortex/analyzers/native/pdf/title_analysis/page_titles.py +1024 -0
  205. docvortex/analyzers/native/pdf/title_analysis/prototype.py +194 -0
  206. docvortex/analyzers/native/pdf/title_analysis/structural.py +1081 -0
  207. docvortex/analyzers/native/pdf/titles.py +75 -0
  208. docvortex/analyzers/native/pdf/typography.py +19 -0
  209. docvortex/analyzers/native/pdf/visual_annotations.py +1262 -0
  210. docvortex/api.py +180 -0
  211. docvortex/assets/__init__.py +5 -0
  212. docvortex/assets/store.py +51 -0
  213. docvortex/cli.py +54 -0
  214. docvortex/codecs/__init__.py +3 -0
  215. docvortex/codecs/html/__init__.py +22 -0
  216. docvortex/codecs/html/contracts.py +236 -0
  217. docvortex/codecs/html/materializer.py +331 -0
  218. docvortex/codecs/html/parser.py +763 -0
  219. docvortex/codecs/html/resources.py +34 -0
  220. docvortex/codecs/json.py +17 -0
  221. docvortex/content/__init__.py +5 -0
  222. docvortex/content/inline.py +248 -0
  223. docvortex/content/markup/__init__.py +44 -0
  224. docvortex/content/markup/anchors.py +188 -0
  225. docvortex/content/markup/formula.py +280 -0
  226. docvortex/content/markup/projector.py +1237 -0
  227. docvortex/content/markup/styles.py +327 -0
  228. docvortex/content/mathml.py +167 -0
  229. docvortex/content/normalization.py +188 -0
  230. docvortex/content/spans.py +183 -0
  231. docvortex/content/table/__init__.py +18 -0
  232. docvortex/content/table/blocks.py +152 -0
  233. docvortex/content/table/content.py +425 -0
  234. docvortex/content/table/document.py +104 -0
  235. docvortex/content/table/html.py +399 -0
  236. docvortex/content/table/models.py +76 -0
  237. docvortex/content/table/rules.py +42 -0
  238. docvortex/content/table/structure.py +221 -0
  239. docvortex/content/tree.py +5 -0
  240. docvortex/document/__init__.py +3 -0
  241. docvortex/document/contracts.py +22 -0
  242. docvortex/document/detection.py +389 -0
  243. docvortex/document/filetypes.py +175 -0
  244. docvortex/document/page_range.py +167 -0
  245. docvortex/document/pdf/__init__.py +19 -0
  246. docvortex/document/pdf/classify.py +1138 -0
  247. docvortex/document/pdf/constants.py +132 -0
  248. docvortex/document/pdf/diagnostics.py +350 -0
  249. docvortex/document/pdf/document.py +582 -0
  250. docvortex/document/pdf/font_runtime.py +335 -0
  251. docvortex/document/pdf/geometry.py +31 -0
  252. docvortex/document/pdf/images.py +654 -0
  253. docvortex/document/pdf/native_annotations.py +367 -0
  254. docvortex/document/pdf/native_contracts.py +169 -0
  255. docvortex/document/pdf/native_coordinates.py +216 -0
  256. docvortex/document/pdf/native_lifecycle.py +16 -0
  257. docvortex/document/pdf/native_objects.py +902 -0
  258. docvortex/document/pdf/native_text_geometry.py +315 -0
  259. docvortex/document/pdf/pdfium.py +325 -0
  260. docvortex/document/pdf/raster.py +46 -0
  261. docvortex/document/pdf/text/__init__.py +62 -0
  262. docvortex/document/pdf/text/contracts.py +211 -0
  263. docvortex/document/pdf/text/extract.py +165 -0
  264. docvortex/document/pdf/text/geometry.py +16 -0
  265. docvortex/document/pdf/text/groups.py +162 -0
  266. docvortex/document/pdf/visual_geometry.py +201 -0
  267. docvortex/document/pdf/visuals.py +343 -0
  268. docvortex/document/source.py +92 -0
  269. docvortex/errors.py +21 -0
  270. docvortex/export/__init__.py +3 -0
  271. docvortex/export/bundle.py +98 -0
  272. docvortex/export/files.py +66 -0
  273. docvortex/export/middle.py +208 -0
  274. docvortex/foundation/__init__.py +3 -0
  275. docvortex/foundation/geometry.py +125 -0
  276. docvortex/foundation/hyperlink.py +65 -0
  277. docvortex/foundation/image.py +48 -0
  278. docvortex/foundation/image_encoding.py +30 -0
  279. docvortex/foundation/image_payload.py +280 -0
  280. docvortex/foundation/language.py +92 -0
  281. docvortex/foundation/platform.py +38 -0
  282. docvortex/foundation/text.py +153 -0
  283. docvortex/foundation/type_identity.py +20 -0
  284. docvortex/foundation/xml_names.py +20 -0
  285. docvortex/options.py +30 -0
  286. docvortex/postprocess/__init__.py +3 -0
  287. docvortex/postprocess/content.py +53 -0
  288. docvortex/postprocess/document.py +19 -0
  289. docvortex/postprocess/lists.py +236 -0
  290. docvortex/postprocess/page_blocks.py +214 -0
  291. docvortex/postprocess/pages.py +95 -0
  292. docvortex/postprocess/paragraphs.py +580 -0
  293. docvortex/postprocess/visual.py +715 -0
  294. docvortex/render/__init__.py +48 -0
  295. docvortex/render/_internal/__init__.py +3 -0
  296. docvortex/render/_internal/common/__init__.py +3 -0
  297. docvortex/render/_internal/common/context.py +43 -0
  298. docvortex/render/_internal/common/html_table.py +178 -0
  299. docvortex/render/_internal/common/index.py +33 -0
  300. docvortex/render/_internal/common/list_items.py +158 -0
  301. docvortex/render/_internal/common/planner.py +140 -0
  302. docvortex/render/_internal/docx/__init__.py +3 -0
  303. docvortex/render/_internal/docx/assets.py +202 -0
  304. docvortex/render/_internal/docx/inline.py +434 -0
  305. docvortex/render/_internal/docx/math.py +220 -0
  306. docvortex/render/_internal/docx/renderer.py +905 -0
  307. docvortex/render/_internal/docx/styles.py +195 -0
  308. docvortex/render/_internal/docx/table.py +442 -0
  309. docvortex/render/_internal/epub/__init__.py +5 -0
  310. docvortex/render/_internal/epub/assets.py +173 -0
  311. docvortex/render/_internal/epub/package.py +249 -0
  312. docvortex/render/_internal/epub/renderer.py +1156 -0
  313. docvortex/render/_internal/html/__init__.py +3 -0
  314. docvortex/render/_internal/html/inline.py +346 -0
  315. docvortex/render/_internal/html/renderer.py +1041 -0
  316. docvortex/render/_internal/html/sanitizer.py +478 -0
  317. docvortex/render/_internal/html/table.py +121 -0
  318. docvortex/render/_internal/latex/__init__.py +1 -0
  319. docvortex/render/_internal/latex/assets.py +85 -0
  320. docvortex/render/_internal/latex/inline.py +145 -0
  321. docvortex/render/_internal/latex/renderer.py +506 -0
  322. docvortex/render/_internal/latex/table.py +347 -0
  323. docvortex/render/_internal/markdown/__init__.py +3 -0
  324. docvortex/render/_internal/markdown/assets.py +78 -0
  325. docvortex/render/_internal/markdown/blocks.py +635 -0
  326. docvortex/render/_internal/markdown/escaping.py +51 -0
  327. docvortex/render/_internal/markdown/inline.py +260 -0
  328. docvortex/render/_internal/markdown/renderer.py +93 -0
  329. docvortex/render/_internal/markdown/table.py +281 -0
  330. docvortex/render/_internal/pdf/__init__.py +3 -0
  331. docvortex/render/_internal/pdf/assets.py +197 -0
  332. docvortex/render/_internal/pdf/formula.py +417 -0
  333. docvortex/render/_internal/pdf/inline.py +343 -0
  334. docvortex/render/_internal/pdf/renderer.py +734 -0
  335. docvortex/render/_internal/pdf/styles.py +206 -0
  336. docvortex/render/_internal/pdf/table.py +272 -0
  337. docvortex/render/_internal/structured_content/__init__.py +3 -0
  338. docvortex/render/_internal/structured_content/renderer.py +193 -0
  339. docvortex/render/api.py +199 -0
  340. docvortex/render/contracts.py +205 -0
  341. docvortex/render/docx.py +38 -0
  342. docvortex/render/epub.py +35 -0
  343. docvortex/render/fragments.py +70 -0
  344. docvortex/render/html.py +29 -0
  345. docvortex/render/latex.py +24 -0
  346. docvortex/render/markdown.py +50 -0
  347. docvortex/render/pdf.py +25 -0
  348. docvortex/render/structured_content.py +23 -0
  349. docvortex/resources/epub/docvortex.css +91 -0
  350. docvortex/resources/fasttext-langdetect/lid.176.ftz +0 -0
  351. docvortex/resources/fonts/DroidSansFallbackFull.ttf +0 -0
  352. docvortex/resources/fonts/NOTICE +190 -0
  353. docvortex/resources/fonts/manifest.json +9 -0
  354. docvortex/resources/html/docvortex.css +601 -0
  355. docvortex/resources/html/docvortex.min.css +1 -0
  356. docvortex/result.py +91 -0
  357. docvortex/schema.py +1141 -0
  358. docvortex/version.py +3 -0
  359. docvortex-0.2.1.dist-info/METADATA +193 -0
  360. docvortex-0.2.1.dist-info/RECORD +364 -0
  361. docvortex-0.2.1.dist-info/WHEEL +5 -0
  362. docvortex-0.2.1.dist-info/entry_points.txt +2 -0
  363. docvortex-0.2.1.dist-info/licenses/LICENSE.md +21 -0
  364. docvortex-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,885 @@
1
+ """安全解析 Equation Native 中的 MTEF v3/v5 公式。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import re
7
+ import struct
8
+
9
+ from loguru import logger
10
+
11
+ from ..errors import LegacyOfficeResourceLimitError
12
+ from ..limits import MAX_RECORD_DEPTH, MAX_RECORDS
13
+ from ..legacy.ole import BoundedOleReader
14
+
15
+ _OBJECT_POOL_EQUATION_RE = re.compile(
16
+ r"^ObjectPool/_([0-9]+)/Equation Native$",
17
+ re.IGNORECASE,
18
+ )
19
+
20
+ _XF_NULL = 0x10
21
+ _XF_RULER = 0x20
22
+ _XF_EMBELL = 0x20
23
+ _XF_LSPACE = 0x40
24
+ _XF_LMOVE = 0x80
25
+
26
+
27
+ class _MtefError(ValueError):
28
+ """MTEF 输入不完整、不受支持或超过安全限制。"""
29
+
30
+
31
+ @dataclass(frozen=True, slots=True)
32
+ class _Node:
33
+ """MTEF 解析后的最小公式语法节点。"""
34
+
35
+ kind: str
36
+ value: object | None = None
37
+ children: tuple[_Node, ...] = ()
38
+
39
+
40
+ class _MtefReader:
41
+ """有界读取 MTEF v3 record tree。"""
42
+
43
+ def __init__(self, data: bytes) -> None:
44
+ """校验五字节 MTEF v3 头并初始化游标。"""
45
+
46
+ if len(data) < 5 or data[0] != 3:
47
+ raise _MtefError("Equation Native does not contain MTEF v3")
48
+ self.data = data
49
+ self.pos = 5
50
+ self.depth = 0
51
+ self.records = 0
52
+
53
+ def _charge(self) -> None:
54
+ """计入一条 record,超过共享上限时拒绝对象。"""
55
+
56
+ self.records += 1
57
+ if self.records > MAX_RECORDS:
58
+ raise LegacyOfficeResourceLimitError(f"MTEF record count exceeds max_records={MAX_RECORDS}")
59
+
60
+ def _u8(self) -> int:
61
+ """有界读取一个无符号字节。"""
62
+
63
+ if self.pos >= len(self.data):
64
+ raise _MtefError("MTEF record is truncated")
65
+ value = self.data[self.pos]
66
+ self.pos += 1
67
+ return value
68
+
69
+ def _u16(self) -> int:
70
+ """有界读取小端 u16。"""
71
+
72
+ if self.pos + 2 > len(self.data):
73
+ raise _MtefError("MTEF u16 is truncated")
74
+ value = int(struct.unpack_from("<H", self.data, self.pos)[0])
75
+ self.pos += 2
76
+ return value
77
+
78
+ def _skip_nudge(self) -> None:
79
+ """跳过短或长格式的 nudge 偏移。"""
80
+
81
+ dx = self._u8()
82
+ dy = self._u8()
83
+ if dx == 128 and dy == 128:
84
+ self._u16()
85
+ self._u16()
86
+
87
+ def _enter(self) -> None:
88
+ """进入一个嵌套 object list 并限制深度。"""
89
+
90
+ self.depth += 1
91
+ if self.depth > MAX_RECORD_DEPTH:
92
+ raise LegacyOfficeResourceLimitError(f"MTEF nesting exceeds max_record_depth={MAX_RECORD_DEPTH}")
93
+
94
+ def _leave(self) -> None:
95
+ """离开一个嵌套 object list。"""
96
+
97
+ self.depth -= 1
98
+
99
+ def _skip_ruler(self) -> None:
100
+ """跳过不影响公式语义的 RULER record。"""
101
+
102
+ count = self._u8()
103
+ for _ in range(count):
104
+ self._u8()
105
+ self._u16()
106
+
107
+ def _skip_font(self) -> None:
108
+ """跳过 FONT 的 typeface、style 和零结尾名称。"""
109
+
110
+ self._u8()
111
+ self._u8()
112
+ while self._u8() != 0:
113
+ pass
114
+
115
+ def _skip_size(self) -> None:
116
+ """跳过三种长度形式的 SIZE record。"""
117
+
118
+ first = self._u8()
119
+ if first == 100:
120
+ self._u8()
121
+ self._u16()
122
+ elif first == 101:
123
+ self._u16()
124
+ else:
125
+ self._u8()
126
+
127
+ def _parse_embellishments(self) -> tuple[int, ...]:
128
+ """读取 CHAR 后以 END 终止的 embellishment 列表。"""
129
+
130
+ values: list[int] = []
131
+ while True:
132
+ tag = self._u8()
133
+ self._charge()
134
+ record_type = tag & 0x0F
135
+ flags = tag & 0xF0
136
+ if record_type == 0:
137
+ return tuple(values)
138
+ if record_type != 6:
139
+ raise _MtefError("MTEF embellishment list contains a non-EMBELL record")
140
+ if flags & _XF_LMOVE:
141
+ self._skip_nudge()
142
+ values.append(self._u8())
143
+
144
+ def _parse_line(self, flags: int) -> _Node:
145
+ """解析一个 LINE slot。"""
146
+
147
+ if flags & _XF_NULL:
148
+ return _Node("sequence")
149
+ if flags & _XF_LMOVE:
150
+ self._skip_nudge()
151
+ if flags & _XF_LSPACE:
152
+ self._u8()
153
+ if flags & _XF_RULER:
154
+ ruler_tag = self._u8()
155
+ if ruler_tag & 0x0F != 7:
156
+ raise _MtefError("MTEF LINE ruler tag is invalid")
157
+ self._skip_ruler()
158
+ self._enter()
159
+ children = self._parse_list()
160
+ self._leave()
161
+ return _Node("sequence", children=children)
162
+
163
+ def _parse_template(self, flags: int) -> _Node:
164
+ """解析 TMPL selector、variation、options 和 slots。"""
165
+
166
+ if flags & _XF_LMOVE:
167
+ self._skip_nudge()
168
+ selector = self._u8()
169
+ variation = self._u8()
170
+ options = self._u8()
171
+ self._enter()
172
+ slots = self._parse_slots()
173
+ self._leave()
174
+ return _Node("template", (selector, variation, options), slots)
175
+
176
+ def _parse_pile(self, flags: int) -> _Node:
177
+ """解析纵向 PILE 中的 LINE slots。"""
178
+
179
+ if flags & _XF_LMOVE:
180
+ self._skip_nudge()
181
+ self._u8()
182
+ self._u8()
183
+ if flags & _XF_RULER:
184
+ ruler_tag = self._u8()
185
+ if ruler_tag & 0x0F != 7:
186
+ raise _MtefError("MTEF PILE ruler tag is invalid")
187
+ self._skip_ruler()
188
+ self._enter()
189
+ slots = self._parse_slots()
190
+ self._leave()
191
+ return _Node("pile", children=slots)
192
+
193
+ def _parse_matrix(self, flags: int) -> _Node:
194
+ """解析 MATRIX 维度、分隔线位图和逐格 LINE。"""
195
+
196
+ if flags & _XF_LMOVE:
197
+ self._skip_nudge()
198
+ self._u8()
199
+ self._u8()
200
+ self._u8()
201
+ rows = self._u8()
202
+ cols = self._u8()
203
+ if rows == 0 or cols == 0 or rows * cols > 4096:
204
+ raise _MtefError("MTEF matrix dimensions are invalid")
205
+ for entry_count in (rows + 1, cols + 1):
206
+ for _ in range((2 * entry_count + 7) // 8):
207
+ self._u8()
208
+ cells: list[_Node] = []
209
+ self._enter()
210
+ while len(cells) < rows * cols:
211
+ tag = self._u8()
212
+ self._charge()
213
+ record_type = tag & 0x0F
214
+ flags = tag & 0xF0
215
+ if record_type == 0:
216
+ continue
217
+ if record_type == 1:
218
+ cells.append(self._parse_line(flags))
219
+ elif record_type == 7:
220
+ self._skip_ruler()
221
+ elif record_type == 8:
222
+ self._skip_font()
223
+ elif record_type == 9:
224
+ self._skip_size()
225
+ elif 10 <= record_type <= 14:
226
+ continue
227
+ else:
228
+ cells.append(self._parse_record(record_type, flags))
229
+ self._leave()
230
+ if self.pos < len(self.data) and self.data[self.pos] & 0x0F == 0:
231
+ self.pos += 1
232
+ self._charge()
233
+ return _Node("matrix", (rows, cols), tuple(cells))
234
+
235
+ def _parse_record(self, record_type: int, flags: int) -> _Node:
236
+ """解析一条已读取 tag 的语义 record。"""
237
+
238
+ if record_type == 1:
239
+ return self._parse_line(flags)
240
+ if record_type == 2:
241
+ if flags & _XF_LMOVE:
242
+ self._skip_nudge()
243
+ typeface = self._u8()
244
+ character = self._u16()
245
+ embellishments = self._parse_embellishments() if flags & _XF_EMBELL else ()
246
+ return _Node("character", (typeface, character, embellishments))
247
+ if record_type == 3:
248
+ return self._parse_template(flags)
249
+ if record_type == 4:
250
+ return self._parse_pile(flags)
251
+ if record_type == 5:
252
+ return self._parse_matrix(flags)
253
+ if record_type == 6:
254
+ if flags & _XF_LMOVE:
255
+ self._skip_nudge()
256
+ return _Node("embellishment", self._u8())
257
+ if record_type == 7:
258
+ self._skip_ruler()
259
+ return _Node("metadata")
260
+ if record_type == 8:
261
+ self._skip_font()
262
+ return _Node("metadata")
263
+ if record_type == 9:
264
+ self._skip_size()
265
+ return _Node("metadata")
266
+ if 10 <= record_type <= 14:
267
+ return _Node("metadata")
268
+ raise _MtefError(f"unsupported MTEF v3 record type: {record_type}")
269
+
270
+ def _parse_list(self) -> tuple[_Node, ...]:
271
+ """解析以 END 终止的普通 object list。"""
272
+
273
+ nodes: list[_Node] = []
274
+ while True:
275
+ tag = self._u8()
276
+ self._charge()
277
+ record_type = tag & 0x0F
278
+ if record_type == 0:
279
+ return tuple(nodes)
280
+ node = self._parse_record(record_type, tag & 0xF0)
281
+ if node.kind != "metadata":
282
+ nodes.append(node)
283
+
284
+ def _parse_slots(self) -> tuple[_Node, ...]:
285
+ """解析 template/pile 中每个 LINE 对应的 slot。"""
286
+
287
+ slots: list[_Node] = []
288
+ while True:
289
+ tag = self._u8()
290
+ self._charge()
291
+ record_type = tag & 0x0F
292
+ flags = tag & 0xF0
293
+ if record_type == 0:
294
+ return tuple(slots)
295
+ if record_type == 1:
296
+ slots.append(self._parse_line(flags))
297
+ elif record_type == 7:
298
+ self._skip_ruler()
299
+ elif record_type == 8:
300
+ self._skip_font()
301
+ elif record_type == 9:
302
+ self._skip_size()
303
+ elif 10 <= record_type <= 14:
304
+ continue
305
+ else:
306
+ slots.append(self._parse_record(record_type, flags))
307
+
308
+ def parse(self) -> _Node:
309
+ """解析根 object list 并拒绝没有可见内容的对象。"""
310
+
311
+ children = self._parse_list()
312
+ root = _Node("sequence", children=children)
313
+ if not _render_node(root).strip():
314
+ raise _MtefError("MTEF formula contains no visible content")
315
+ return root
316
+
317
+
318
+ _CHAR_LATEX = {
319
+ "α": r"\alpha ",
320
+ "β": r"\beta ",
321
+ "γ": r"\gamma ",
322
+ "δ": r"\delta ",
323
+ "ε": r"\epsilon ",
324
+ "θ": r"\theta ",
325
+ "λ": r"\lambda ",
326
+ "μ": r"\mu ",
327
+ "π": r"\pi ",
328
+ "ρ": r"\rho ",
329
+ "σ": r"\sigma ",
330
+ "φ": r"\phi ",
331
+ "ω": r"\omega ",
332
+ "Γ": r"\Gamma ",
333
+ "Δ": r"\Delta ",
334
+ "Θ": r"\Theta ",
335
+ "Λ": r"\Lambda ",
336
+ "Ξ": r"\Xi ",
337
+ "Π": r"\Pi ",
338
+ "Σ": r"\Sigma ",
339
+ "Φ": r"\Phi ",
340
+ "Ψ": r"\Psi ",
341
+ "Ω": r"\Omega ",
342
+ "≤": r"\leq ",
343
+ "≥": r"\geq ",
344
+ "≠": r"\ne ",
345
+ "±": r"\pm ",
346
+ "∓": r"\mp ",
347
+ "−": "-",
348
+ "×": r"\times ",
349
+ "÷": r"\div ",
350
+ "∞": r"\infty ",
351
+ "∂": r"\partial ",
352
+ "∈": r"\in ",
353
+ "∉": r"\notin ",
354
+ "∪": r"\cup ",
355
+ "∩": r"\cap ",
356
+ "→": r"\rightarrow ",
357
+ "←": r"\leftarrow ",
358
+ "↔": r"\leftrightarrow ",
359
+ "·": r"\cdot ",
360
+ "∑": r"\sum ",
361
+ "∏": r"\prod ",
362
+ "∐": r"\coprod ",
363
+ "∫": r"\int ",
364
+ "∮": r"\oint ",
365
+ "ℏ": r"\hbar ",
366
+ }
367
+
368
+
369
+ def _render_character(character: int) -> str:
370
+ """把 MTEF 字符转为安全 LaTeX。"""
371
+
372
+ try:
373
+ value = chr(character)
374
+ except ValueError as exc:
375
+ raise _MtefError("MTEF character code is invalid") from exc
376
+ mapped = _CHAR_LATEX.get(value)
377
+ if mapped is not None:
378
+ return mapped
379
+ if value == "\\":
380
+ return r"\backslash "
381
+ if value in "#$%&_{}":
382
+ return f"\\{value}"
383
+ if value == "~":
384
+ return r"\sim "
385
+ if value == "^":
386
+ return r"\hat{}"
387
+ if ord(value) < 0x20:
388
+ raise _MtefError("MTEF formula contains a control character")
389
+ return value
390
+
391
+
392
+ def _escape_latex_text(value: str) -> str:
393
+ """转义 ``\text{}`` 中会改变 LaTeX 结构的字符。"""
394
+
395
+ replacements = {
396
+ "\\": r"\textbackslash{}",
397
+ "{": r"\{",
398
+ "}": r"\}",
399
+ "#": r"\#",
400
+ "$": r"\$",
401
+ "%": r"\%",
402
+ "&": r"\&",
403
+ "_": r"\_",
404
+ "^": r"\textasciicircum{}",
405
+ "~": r"\textasciitilde{}",
406
+ }
407
+ return "".join(replacements.get(character, character) for character in value)
408
+
409
+
410
+ def _apply_embellishments(value: str, embellishments: tuple[int, ...]) -> str:
411
+ """按记录顺序把常见 embellishment 转为 LaTeX。"""
412
+
413
+ for embellishment in embellishments:
414
+ if embellishment == 2:
415
+ value = rf"\dot{{{value}}}"
416
+ elif embellishment == 3:
417
+ value = rf"\ddot{{{value}}}"
418
+ elif embellishment == 4:
419
+ value = rf"\overset{{\ldots}}{{{value}}}"
420
+ elif embellishment == 5:
421
+ value = f"{value}'"
422
+ elif embellishment == 6:
423
+ value = f"{value}''"
424
+ elif embellishment == 7:
425
+ value = f"{{}}'{value}"
426
+ elif embellishment == 8:
427
+ value = rf"\widetilde{{{value}}}"
428
+ elif embellishment == 9:
429
+ value = rf"\widehat{{{value}}}"
430
+ elif embellishment == 10:
431
+ value = rf"\not{{{value}}}"
432
+ elif embellishment == 11:
433
+ value = rf"\overrightarrow{{{value}}}"
434
+ elif embellishment == 12:
435
+ value = rf"\overleftarrow{{{value}}}"
436
+ elif embellishment == 13:
437
+ value = rf"\overleftrightarrow{{{value}}}"
438
+ elif embellishment == 14:
439
+ value = rf"\overrightharpoon{{{value}}}"
440
+ elif embellishment == 15:
441
+ value = rf"\overleftharpoon{{{value}}}"
442
+ elif embellishment == 16:
443
+ value = rf"\bar{{{value}}}"
444
+ elif embellishment == 17:
445
+ value = rf"\overline{{{value}}}"
446
+ elif embellishment == 18:
447
+ value = f"{value}'''"
448
+ elif embellishment == 19:
449
+ value = rf"\overparen{{{value}}}"
450
+ elif embellishment == 20:
451
+ value = rf"\underparen{{{value}}}"
452
+ elif embellishment == 21:
453
+ value = rf"\xcancel{{{value}}}"
454
+ elif embellishment == 22:
455
+ value = rf"\cancel{{{value}}}"
456
+ elif embellishment == 23:
457
+ value = rf"\bcancel{{{value}}}"
458
+ elif embellishment == 24:
459
+ value = rf"\overset{{\cdots}}{{{value}}}"
460
+ elif embellishment == 25:
461
+ value = rf"\underset{{\cdot}}{{{value}}}"
462
+ elif embellishment == 26:
463
+ value = rf"\underset{{\cdot\cdot}}{{{value}}}"
464
+ elif embellishment == 27:
465
+ value = rf"\underset{{\cdots}}{{{value}}}"
466
+ elif embellishment == 28:
467
+ value = rf"\underset{{\cdots\cdot}}{{{value}}}"
468
+ elif embellishment == 29:
469
+ value = rf"\underline{{{value}}}"
470
+ elif embellishment == 30:
471
+ value = rf"\underset{{\sim}}{{{value}}}"
472
+ elif embellishment == 31:
473
+ value = rf"\underparen{{{value}}}"
474
+ elif embellishment == 32:
475
+ value = rf"\underset{{\frown}}{{{value}}}"
476
+ elif embellishment == 33:
477
+ value = rf"\underrightarrow{{{value}}}"
478
+ elif embellishment == 34:
479
+ value = rf"\underleftarrow{{{value}}}"
480
+ elif embellishment == 35:
481
+ value = rf"\underleftrightarrow{{{value}}}"
482
+ elif embellishment == 36:
483
+ value = rf"\underrightharpoon{{{value}}}"
484
+ elif embellishment == 37:
485
+ value = rf"\underleftharpoon{{{value}}}"
486
+ else:
487
+ raise _MtefError(f"unsupported MTEF embellishment: {embellishment}")
488
+ return value
489
+
490
+
491
+ def _slot(slots: tuple[_Node, ...], index: int) -> str:
492
+ """渲染一个可选 template slot。"""
493
+
494
+ return _render_node(slots[index]).strip() if index < len(slots) else ""
495
+
496
+
497
+ def _fence(selector: int, variation: int, body: str) -> str:
498
+ """渲染左右 fence 及单边 variation。"""
499
+
500
+ pairs = {
501
+ 0: (r"\langle", r"\rangle"),
502
+ 1: ("(", ")"),
503
+ 2: (r"\{", r"\}"),
504
+ 3: ("[", "]"),
505
+ 4: ("|", "|"),
506
+ 5: (r"\|", r"\|"),
507
+ 6: (r"\lfloor", r"\rfloor"),
508
+ 7: (r"\lceil", r"\rceil"),
509
+ }
510
+ left, right = pairs[selector]
511
+ if variation == 1:
512
+ right = "."
513
+ elif variation == 2:
514
+ left = "."
515
+ elif variation != 0:
516
+ raise _MtefError("unsupported MTEF fence variation")
517
+ return rf"\left{left}{body}\right{right}"
518
+
519
+
520
+ def _big_operator(selector: int, slots: tuple[_Node, ...]) -> str:
521
+ """渲染积分、求和、乘积及集合大运算。"""
522
+
523
+ operators = {
524
+ 21: r"\int",
525
+ 22: r"\iint",
526
+ 23: r"\iiint",
527
+ 24: r"\int",
528
+ 25: r"\iint",
529
+ 26: r"\iiint",
530
+ 29: r"\sum",
531
+ 30: r"\sum",
532
+ 31: r"\prod",
533
+ 32: r"\prod",
534
+ 33: r"\coprod",
535
+ 34: r"\coprod",
536
+ 35: r"\bigcup",
537
+ 36: r"\bigcup",
538
+ 37: r"\bigcap",
539
+ 38: r"\bigcap",
540
+ }
541
+ operator = operators[selector]
542
+ main = _slot(slots, 0)
543
+ upper = _slot(slots, 1)
544
+ lower = _slot(slots, 2)
545
+ limits = (rf"_{{{lower}}}" if lower else "") + (rf"^{{{upper}}}" if upper else "")
546
+ return rf"{operator}{limits}{{{main}}}"
547
+
548
+
549
+ def _render_template(selector: int, variation: int, options: int, slots: tuple[_Node, ...]) -> str:
550
+ """把一个受支持的 MTEF v3 template 转为 LaTeX。"""
551
+
552
+ del options
553
+ if 0 <= selector <= 7:
554
+ return _fence(selector, variation, _slot(slots, 0))
555
+ if selector == 8:
556
+ return rf"\left\{{\left\{{{_slot(slots, 0)}"
557
+ if selector == 9:
558
+ return rf"{_slot(slots, 0)}\right\}}\right\}}"
559
+ if selector == 10:
560
+ return rf"{_slot(slots, 0)}\right\}}\left\{{"
561
+ if selector == 11:
562
+ return rf"\left\{{{_slot(slots, 0)}\right)"
563
+ if selector == 12:
564
+ return rf"\left({_slot(slots, 0)}\right\}}"
565
+ if selector == 13:
566
+ radicand = _slot(slots, 0)
567
+ index = _slot(slots, 1)
568
+ return rf"\sqrt[{index}]{{{radicand}}}" if variation == 1 and index else rf"\sqrt{{{radicand}}}"
569
+ if selector == 14:
570
+ return rf"\frac{{{_slot(slots, 0)}}}{{{_slot(slots, 1)}}}"
571
+ if selector in {15, 44}:
572
+ subscript = _slot(slots, 0)
573
+ superscript = _slot(slots, 1)
574
+ return (rf"_{{{subscript}}}" if subscript else "") + (rf"^{{{superscript}}}" if superscript else "")
575
+ if selector == 16:
576
+ return rf"\underline{{{_slot(slots, 0)}}}"
577
+ if selector == 17:
578
+ return rf"\overline{{{_slot(slots, 0)}}}"
579
+ if selector in {18, 19, 20}:
580
+ arrow = {18: r"\leftarrow", 19: r"\rightarrow", 20: r"\leftrightarrow"}[selector]
581
+ return rf"\overset{{{_slot(slots, 0)}}}{{{arrow}}}" if variation == 0 else rf"\underset{{{_slot(slots, 0)}}}{{{arrow}}}"
582
+ if selector == 27:
583
+ return rf"\overbrace{{{_slot(slots, 0)}}}^{{{_slot(slots, 1)}}}"
584
+ if selector == 28:
585
+ return rf"\underbrace{{{_slot(slots, 0)}}}_{{{_slot(slots, 1)}}}"
586
+ if 21 <= selector <= 38:
587
+ return _big_operator(selector, slots)
588
+ if selector == 39:
589
+ main = _slot(slots, 0)
590
+ lower = _slot(slots, 1)
591
+ upper = _slot(slots, 2)
592
+ return rf"\lim_{{{lower}}}^{{{upper}}}{{{main}}}"
593
+ if selector == 40:
594
+ return rf"\overline{{{_slot(slots, 1)}}}\smash{{\big) {_slot(slots, 0)}}}"
595
+ if selector == 41:
596
+ return rf"{{{_slot(slots, 0)}}}/{{{_slot(slots, 1)}}}"
597
+ if selector in {42, 43}:
598
+ operator = _slot(slots, 3) or (r"\int" if selector == 42 else r"\sum")
599
+ main = _slot(slots, 0)
600
+ upper = _slot(slots, 1)
601
+ lower = _slot(slots, 2)
602
+ return operator + (rf"_{{{lower}}}" if lower else "") + (rf"^{{{upper}}}" if upper else "") + rf"{{{main}}}"
603
+ if selector == 45:
604
+ return rf"\left\langle {_slot(slots, 0)}\middle|{_slot(slots, 1)}\right\rangle"
605
+ if selector in {46, 47}:
606
+ command = "under" if selector == 46 else "over"
607
+ direction = {0: "leftarrow", 1: "rightarrow", 2: "leftrightarrow"}.get(variation)
608
+ if direction is None:
609
+ raise _MtefError("unsupported MTEF vector variation")
610
+ return rf"\{command}{direction}{{{_slot(slots, 0)}}}"
611
+ if selector == 48:
612
+ return rf"\overparen{{{_slot(slots, 0)}}}"
613
+ raise _MtefError(f"unsupported MTEF v3 template selector: {selector}")
614
+
615
+
616
+ def _render_node(node: _Node) -> str:
617
+ """递归序列化 MTEF AST。"""
618
+
619
+ if node.kind == "sequence":
620
+ return "".join(_render_node(child) for child in node.children)
621
+ if node.kind == "character":
622
+ _typeface, character, embellishments = node.value # type: ignore[misc]
623
+ return _apply_embellishments(_render_character(int(character)), tuple(embellishments))
624
+ if node.kind == "character_v5":
625
+ character, embellishments, style_bits, typeface, _function_start = node.value # type: ignore[misc]
626
+ value = _apply_embellishments(
627
+ _render_character(character) if isinstance(character, int) else str(character),
628
+ tuple(embellishments),
629
+ )
630
+ if int(typeface) in {1, 12}:
631
+ value = rf"\mathrm{{{value}}}"
632
+ if int(style_bits) == 1:
633
+ return rf"\mathbf{{{value}}}"
634
+ if int(style_bits) == 2:
635
+ return rf"\mathit{{{value}}}"
636
+ if int(style_bits) == 3:
637
+ return rf"\boldsymbol{{\mathit{{{value}}}}}"
638
+ return value
639
+ if node.kind == "text":
640
+ return rf"\text{{{_escape_latex_text(str(node.value))}}}"
641
+ if node.kind == "function":
642
+ function_name = str(node.value)
643
+ known = {
644
+ "arccos",
645
+ "arcsin",
646
+ "arctan",
647
+ "cos",
648
+ "cosh",
649
+ "cot",
650
+ "coth",
651
+ "csc",
652
+ "det",
653
+ "exp",
654
+ "gcd",
655
+ "hom",
656
+ "ker",
657
+ "lg",
658
+ "lim",
659
+ "ln",
660
+ "log",
661
+ "max",
662
+ "min",
663
+ "sec",
664
+ "sin",
665
+ "sinh",
666
+ "sup",
667
+ "tan",
668
+ "tanh",
669
+ }
670
+ return rf"\{function_name} " if function_name in known else rf"\operatorname{{{function_name}}}"
671
+ if node.kind == "template":
672
+ selector, variation, options = node.value # type: ignore[misc]
673
+ return _render_template(int(selector), int(variation), int(options), node.children)
674
+ if node.kind == "fraction_semantic":
675
+ numerator = _slot(node.children, 0)
676
+ denominator = _slot(node.children, 1)
677
+ if bool(node.value):
678
+ return rf"{{{numerator}}}/{{{denominator}}}"
679
+ return rf"\frac{{{numerator}}}{{{denominator}}}"
680
+ if node.kind == "root_semantic":
681
+ radicand = _slot(node.children, 0)
682
+ index = _slot(node.children, 1)
683
+ return rf"\sqrt[{index}]{{{radicand}}}" if bool(node.value) and index else rf"\sqrt{{{radicand}}}"
684
+ if node.kind == "scripts_semantic":
685
+ subscript = _slot(node.children, 0)
686
+ superscript = _slot(node.children, 1)
687
+ scripts = (rf"_{{{subscript}}}" if subscript else "") + (rf"^{{{superscript}}}" if superscript else "")
688
+ return ("{}" if bool(node.value) else "") + scripts
689
+ if node.kind == "fence_semantic":
690
+ default_left, default_right = node.value # type: ignore[misc]
691
+ body = _slot(node.children, 0)
692
+ left = _slot(node.children, 1) or str(default_left)
693
+ right = _slot(node.children, 2) or str(default_right)
694
+ if re.fullmatch(r"\\[A-Za-z]+", left):
695
+ left += " "
696
+ if re.fullmatch(r"\\[A-Za-z]+", right):
697
+ right += " "
698
+ return rf"\left{left}{body}\right{right}"
699
+ if node.kind == "bar_semantic":
700
+ under, doubled = node.value # type: ignore[misc]
701
+ command = "underline" if bool(under) else "overline"
702
+ value = rf"\{command}{{{_slot(node.children, 0)}}}"
703
+ return rf"\{command}{{{value}}}" if bool(doubled) else value
704
+ if node.kind == "arrow_semantic":
705
+ if not isinstance(node.value, int):
706
+ raise _MtefError("MTEF arrow semantic value is invalid")
707
+ variation = node.value
708
+ body = _slot(node.children, 0)
709
+ arrow = _slot(node.children, 1)
710
+ if not arrow:
711
+ if variation & 0x02:
712
+ arrow = r"\leftrightharpoons"
713
+ elif variation & 0x01:
714
+ arrow = r"\Leftrightarrow"
715
+ elif variation & 0x10 and not variation & 0x20:
716
+ arrow = r"\leftarrow"
717
+ elif variation & 0x20 and not variation & 0x10:
718
+ arrow = r"\rightarrow"
719
+ else:
720
+ arrow = r"\leftrightarrow"
721
+ if variation & 0x08 and not variation & 0x04:
722
+ return rf"\underset{{{body}}}{{{arrow}}}"
723
+ return rf"\overset{{{body}}}{{{arrow}}}" if body else arrow
724
+ if node.kind == "big_operator_semantic":
725
+ main = _slot(node.children, 0)
726
+ upper = _slot(node.children, 1)
727
+ lower = _slot(node.children, 2)
728
+ default_operator = str(node.value)
729
+ if default_operator in {
730
+ r"\sum",
731
+ r"\prod",
732
+ r"\coprod",
733
+ r"\bigcup",
734
+ r"\bigcap",
735
+ }:
736
+ operator = default_operator
737
+ else:
738
+ operator = _slot(node.children, 3) or default_operator
739
+ if not operator:
740
+ raise _MtefError("MTEF big operator has no visible operator")
741
+ limits = (rf"_{{{lower}}}" if lower else "") + (rf"^{{{upper}}}" if upper else "")
742
+ return operator + limits + rf"{{{main}}}"
743
+ if node.kind == "limit_semantic":
744
+ main = _slot(node.children, 0)
745
+ lower = _slot(node.children, 1)
746
+ upper = _slot(node.children, 2)
747
+ return r"\lim" + (rf"_{{{lower}}}" if lower else "") + (rf"^{{{upper}}}" if upper else "") + rf"{{{main}}}"
748
+ if node.kind == "horizontal_fence_semantic":
749
+ brace, top = node.value # type: ignore[misc]
750
+ main = _slot(node.children, 0)
751
+ small = _slot(node.children, 1)
752
+ if bool(brace):
753
+ command = "overbrace" if bool(top) else "underbrace"
754
+ else:
755
+ command = "overbracket" if bool(top) else "underbracket"
756
+ suffix = rf"^{{{small}}}" if bool(top) and small else rf"_{{{small}}}" if small else ""
757
+ return rf"\{command}{{{main}}}{suffix}"
758
+ if node.kind == "long_division_semantic":
759
+ dividend = _slot(node.children, 0)
760
+ quotient = _slot(node.children, 1)
761
+ return rf"\overline{{{quotient}}}\smash{{\big) {dividend}}}" if bool(node.value) else rf"\smash{{\big) {dividend}}}"
762
+ if node.kind == "dirac_semantic":
763
+ if not isinstance(node.value, int):
764
+ raise _MtefError("MTEF Dirac semantic value is invalid")
765
+ variation = node.value
766
+ left = _slot(node.children, 0)
767
+ right = _slot(node.children, 1)
768
+ if variation == 0x01:
769
+ return rf"\left\langle {left}\right|"
770
+ if variation == 0x02:
771
+ return rf"\left|{right}\right\rangle"
772
+ return rf"\left\langle {left}\middle|{right}\right\rangle"
773
+ if node.kind == "vector_semantic":
774
+ if not isinstance(node.value, int):
775
+ raise _MtefError("MTEF vector semantic value is invalid")
776
+ variation = node.value
777
+ left = bool(variation & 0x01)
778
+ right = bool(variation & 0x02)
779
+ under = bool(variation & 0x04)
780
+ harpoon = bool(variation & 0x08)
781
+ if harpoon:
782
+ direction = "leftharpoon" if left else "rightharpoon"
783
+ elif left and right:
784
+ direction = "leftrightarrow"
785
+ elif left:
786
+ direction = "leftarrow"
787
+ else:
788
+ direction = "rightarrow"
789
+ return rf"\{'under' if under else 'over'}{direction}{{{_slot(node.children, 0)}}}"
790
+ if node.kind == "accent_semantic":
791
+ return rf"\{str(node.value)}{{{_slot(node.children, 0)}}}"
792
+ if node.kind == "strike_semantic":
793
+ return rf"\{str(node.value)}{{{_slot(node.children, 0)}}}"
794
+ if node.kind == "box_semantic":
795
+ return rf"\boxed{{{_slot(node.children, 0)}}}"
796
+ if node.kind == "pile":
797
+ rows = [_render_node(child).strip() for child in node.children]
798
+ return r"\begin{gathered}" + r"\\".join(rows) + r"\end{gathered}"
799
+ if node.kind == "matrix":
800
+ rows, cols = node.value # type: ignore[misc]
801
+ rendered = [_render_node(child).strip() for child in node.children]
802
+ matrix_rows = ["&".join(rendered[row * int(cols) : (row + 1) * int(cols)]) for row in range(int(rows))]
803
+ return r"\begin{matrix}" + r"\\".join(matrix_rows) + r"\end{matrix}"
804
+ if node.kind in {"metadata", "embellishment"}:
805
+ return ""
806
+ raise _MtefError(f"unknown MTEF AST node: {node.kind}")
807
+
808
+
809
+ def decode_mtef_v3(data: bytes) -> str | None:
810
+ """把 MTEF v3 字节流转换为 LaTeX;不可信对象失败时返回 None。"""
811
+
812
+ try:
813
+ latex = _render_node(_MtefReader(data).parse()).strip()
814
+ except LegacyOfficeResourceLimitError:
815
+ raise
816
+ except (ArithmeticError, IndexError, struct.error, UnicodeError, _MtefError, ValueError):
817
+ return None
818
+ return latex or None
819
+
820
+
821
+ def decode_mtef_v5(data: bytes) -> str | None:
822
+ """延迟加载独立 v5 reader,并返回其完整 LaTeX 结果。"""
823
+
824
+ from .mtef_v5 import decode_mtef_v5 as _decode_mtef_v5
825
+
826
+ return _decode_mtef_v5(data)
827
+
828
+
829
+ def decode_mtef(data: bytes) -> str | None:
830
+ """仅按 MTEF header 首字节分派 v3/v5,其他版本整体拒绝。"""
831
+
832
+ if not data:
833
+ return None
834
+ if data[0] == 3:
835
+ return decode_mtef_v3(data)
836
+ if data[0] == 5:
837
+ return decode_mtef_v5(data)
838
+ return None
839
+
840
+
841
+ def decode_equation_native(data: bytes) -> str | None:
842
+ """校验 28 字节 EQNOLEFILEHDR 并按 header 解码 MTEF v3/v5。"""
843
+
844
+ if len(data) < 28:
845
+ return None
846
+ header_size, version, _clipboard_format, object_size = struct.unpack_from("<HIHI", data, 0)
847
+ if header_size < 28 or header_size > len(data) or version != 0x0002_0000:
848
+ return None
849
+ object_end = header_size + object_size
850
+ if object_end < header_size or object_end > len(data):
851
+ return None
852
+ return decode_mtef(data[header_size:object_end])
853
+
854
+
855
+ def decode_equation_object(data: bytes) -> str | None:
856
+ """从独立公式 OLE 对象读取 Equation Native 并转为 LaTeX。"""
857
+
858
+ try:
859
+ with BoundedOleReader(data) as ole:
860
+ native = ole.read_stream("Equation Native")
861
+ except LegacyOfficeResourceLimitError:
862
+ raise
863
+ except ValueError:
864
+ return None
865
+ return decode_equation_native(native)
866
+
867
+
868
+ def read_object_pool_equations(ole: BoundedOleReader) -> dict[int, str]:
869
+ """只读提取 DOC ObjectPool 中可安全解码的 Equation Native streams。"""
870
+
871
+ equations: dict[int, str] = {}
872
+ for stream_name in ole.stream_names(prefix="ObjectPool/"):
873
+ match = _OBJECT_POOL_EQUATION_RE.match(stream_name)
874
+ if match is None:
875
+ continue
876
+ storage_id = int(match.group(1))
877
+ latex = decode_equation_native(ole.read_stream(stream_name))
878
+ if latex is None:
879
+ logger.warning(
880
+ "DOC_MTEF_FALLBACK: storage_id={} has an invalid or unsupported Equation Native stream",
881
+ storage_id,
882
+ )
883
+ continue
884
+ equations.setdefault(storage_id, latex)
885
+ return equations