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,117 @@
1
+ """Standalone HTML 到单页 DocVortex raw model-list 的原生 converter。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from typing import Any, BinaryIO
8
+
9
+ from loguru import logger
10
+
11
+ from ....schema import BlockType
12
+ from docvortex.content.markup import MarkupProjector, MarkupStylesheet
13
+ from ....content.spans import text_spans
14
+ from .anchors import HtmlAnchorRegistry, append_referenced_notes
15
+ from .constants import MAX_HTML_BYTES, MAX_HTML_RENDERED_BYTES
16
+ from docvortex.document.contracts import HtmlSourceContext
17
+ from .document import HtmlDocument, parse_html_document
18
+ from .errors import HtmlResourceLimitError
19
+ from .resources import HtmlResourceContext
20
+ from .selector import select_auto_content
21
+ from ....codecs.html import decode_docvortex_html_wire
22
+
23
+
24
+ class HtmlConverter:
25
+ """把静态 HTML 转换为一个无 bbox 的逻辑页。"""
26
+
27
+ def __init__(self) -> None:
28
+ """初始化空页面结果。"""
29
+ self.pages: list[list[dict[str, Any]]] = []
30
+
31
+ def convert(
32
+ self,
33
+ file_binary: BinaryIO,
34
+ *,
35
+ source_context: HtmlSourceContext | None = None,
36
+ ) -> None:
37
+ """读取调用方 HTML 流,自动选择正文并生成单页 raw blocks。"""
38
+ file_bytes = file_binary.read(MAX_HTML_BYTES + 1)
39
+ if len(file_bytes) > MAX_HTML_BYTES:
40
+ raise HtmlResourceLimitError(f"HTML resource limit exceeded: max_html_bytes={MAX_HTML_BYTES}")
41
+ document = parse_html_document(file_bytes, source_context)
42
+ resources = HtmlResourceContext(document.source_context, base_href=document.base_href)
43
+ wire_result = decode_docvortex_html_wire(document.body, resources)
44
+ if wire_result.blocks is not None:
45
+ blocks = wire_result.blocks
46
+ log_values = ("docvortex_exact", 1.0, 1.0, "version_1")
47
+ else:
48
+ if wire_result.fallback_reason is not None:
49
+ logger.warning("DocVortex HTML marker fallback reason={}", wire_result.fallback_reason)
50
+ stylesheet = _load_stylesheet(document, resources)
51
+ selection = select_auto_content(document.body, stylesheet)
52
+ selected_root = append_referenced_notes(
53
+ selection.root,
54
+ document.body,
55
+ stylesheet=stylesheet,
56
+ resolve_same_document_fragment=resources.same_document_fragment,
57
+ )
58
+ source_key = document.source_context.source_uri or "html"
59
+ anchors = HtmlAnchorRegistry(selected_root, stylesheet, source_key=source_key)
60
+ resources.bind_anchors(anchors)
61
+ blocks = MarkupProjector(
62
+ selected_root,
63
+ resources,
64
+ stylesheet,
65
+ single_document_title=True,
66
+ ).convert()
67
+ if not any(block.get("type") == BlockType.DOC_TITLE for block in blocks):
68
+ if title := _document_title(document):
69
+ blocks.insert(0, {"type": BlockType.DOC_TITLE, "level": 1, "content": text_spans(title)})
70
+ log_values = (
71
+ selection.mode_used,
72
+ selection.confidence,
73
+ selection.retained_text_ratio,
74
+ selection.reason,
75
+ )
76
+ rendered_bytes = len(json.dumps(blocks, ensure_ascii=False, separators=(",", ":")).encode())
77
+ if rendered_bytes > MAX_HTML_RENDERED_BYTES:
78
+ raise HtmlResourceLimitError(f"HTML projection exceeds max_html_rendered_bytes={MAX_HTML_RENDERED_BYTES}")
79
+ logger.debug(
80
+ "HTML content selection finished mode={} confidence={:.3f} retained_text_ratio={:.3f} reason={}",
81
+ *log_values,
82
+ )
83
+ self.pages = [blocks]
84
+
85
+
86
+ def _load_stylesheet(document: HtmlDocument, resources: HtmlResourceContext) -> MarkupStylesheet:
87
+ """按 head 文档顺序加载本地 stylesheet 与内联 style 的受支持子集。"""
88
+ stylesheet = MarkupStylesheet()
89
+ for source in document.stylesheets:
90
+ if source.kind == "inline":
91
+ resources.charge_inline_stylesheet(source.value)
92
+ stylesheet.add(source.value)
93
+ elif css := resources.load_stylesheet(source.value):
94
+ stylesheet.add(css)
95
+ return stylesheet
96
+
97
+
98
+ def _document_title(document: HtmlDocument) -> str | None:
99
+ """按 OpenGraph/title 优先级返回去重且保守去站点后缀的标题。"""
100
+ title = (document.open_graph_title or document.title or "").strip()
101
+ if not title:
102
+ return None
103
+ site_name = (document.site_name or "").strip()
104
+ if site_name:
105
+ for separator in (" - ", " | ", " · ", " — ", " _ "):
106
+ suffix = f"{separator}{site_name}"
107
+ prefix = f"{site_name}{separator}"
108
+ if title.casefold().endswith(suffix.casefold()):
109
+ title = title[: -len(suffix)].strip()
110
+ break
111
+ if title.casefold().startswith(prefix.casefold()):
112
+ title = title[len(prefix) :].strip()
113
+ break
114
+ return re.sub(r"\s+", " ", title) or None
115
+
116
+
117
+ __all__ = ["HtmlConverter"]
@@ -0,0 +1,389 @@
1
+ """安全加载、规范化并描述一个 standalone HTML 文档。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import codecs
6
+ from copy import deepcopy
7
+ from dataclasses import dataclass
8
+ import re
9
+ from typing import Literal
10
+
11
+ from lxml import etree, html as lxml_html # type: ignore[reportMissingImports]
12
+
13
+ from docvortex.content.markup.formula import FormulaExtraction, extract_formula, is_tex_script
14
+ from docvortex.content.markup.projector import local_name
15
+ from .constants import MAX_HTML_BYTES, MAX_HTML_DEPTH, MAX_HTML_NODES
16
+ from docvortex.document.contracts import HtmlSourceContext
17
+ from .errors import HtmlParseError, HtmlResourceLimitError
18
+
19
+
20
+ _ACTIVE_TAGS = frozenset(
21
+ {
22
+ "applet",
23
+ "audio",
24
+ "button",
25
+ "canvas",
26
+ "embed",
27
+ "form",
28
+ "iframe",
29
+ "input",
30
+ "object",
31
+ "script",
32
+ "select",
33
+ "style",
34
+ "template",
35
+ "textarea",
36
+ "video",
37
+ }
38
+ )
39
+ _FORMULA_GENERATOR_CLASS_TOKENS = frozenset({"katex", "mathjax", "docvortex-math"})
40
+ _GENERIC_FORMULA_CLASS_TOKENS = frozenset({"formula", "math", "tex"})
41
+ _FORMULA_VISIBILITY_ATTRIBUTES = ("hidden", "aria-hidden", "style", "class")
42
+ _MEANINGFUL_FORMULA_SIBLING_TAGS = frozenset(
43
+ {"audio", "br", "canvas", "figure", "hr", "iframe", "image", "img", "object", "svg", "table", "video"}
44
+ )
45
+ _ASCIIMATH_SCRIPT_TYPE_RE = re.compile(r"^math/asciimath(?:\s*;.*)?$", re.IGNORECASE)
46
+ _HTML_ENCODING_DECLARATION_CANDIDATE_RE = re.compile(rb"(?is)<(?:meta\b[^>]*\bcharset\s*=|\?xml\b[^>]*\bencoding\s*=)")
47
+ _XML_ENCODING_DECLARATION_RE = re.compile(rb"(?is)^<\?xml\b[^>]*\bencoding\s*=")
48
+ _META_CONTENT_CHARSET_RE = re.compile(r"\bcharset\s*=", re.IGNORECASE)
49
+ _UNICODE_BOMS = (b"\x00\x00\xfe\xff", b"\xff\xfe\x00\x00", b"\xef\xbb\xbf", b"\xfe\xff", b"\xff\xfe")
50
+
51
+
52
+ @dataclass(frozen=True, slots=True)
53
+ class HtmlStylesheetSource:
54
+ """保存一个按 head 源顺序出现的内联或外链 stylesheet。"""
55
+
56
+ kind: Literal["inline", "link"]
57
+ value: str
58
+
59
+
60
+ @dataclass(frozen=True, slots=True)
61
+ class HtmlDocument:
62
+ """保存已规范化 DOM、标题、样式引用与来源上下文。"""
63
+
64
+ root: etree._Element
65
+ body: etree._Element
66
+ stylesheets: tuple[HtmlStylesheetSource, ...]
67
+ base_href: str | None
68
+ title: str | None
69
+ open_graph_title: str | None
70
+ site_name: str | None
71
+ source_context: HtmlSourceContext
72
+
73
+
74
+ def parse_html_document(file_bytes: bytes, source_context: HtmlSourceContext | None = None) -> HtmlDocument:
75
+ """从受限字节输入构造不执行脚本且资源引用尚未加载的 HTML DOM。"""
76
+ if len(file_bytes) > MAX_HTML_BYTES:
77
+ raise HtmlResourceLimitError(f"HTML resource limit exceeded: max_html_bytes={MAX_HTML_BYTES}")
78
+ context = source_context or HtmlSourceContext()
79
+ if not file_bytes.strip():
80
+ root = etree.Element("html")
81
+ body = etree.SubElement(root, "body")
82
+ return HtmlDocument(root, body, (), None, None, None, None, context)
83
+
84
+ transport_encoding = _normalize_transport_encoding(context.transport_encoding)
85
+ parser = lxml_html.HTMLParser(
86
+ recover=True,
87
+ no_network=True,
88
+ remove_comments=False,
89
+ huge_tree=False,
90
+ encoding=transport_encoding,
91
+ )
92
+ try:
93
+ root = lxml_html.document_fromstring(
94
+ _html_parser_input(file_bytes, transport_encoding=transport_encoding),
95
+ parser=parser,
96
+ )
97
+ except (etree.ParserError, etree.XMLSyntaxError, UnicodeError, ValueError) as exc:
98
+ raise HtmlParseError(f"Malformed HTML document: {exc}") from exc
99
+ _validate_dom_shape(root)
100
+
101
+ stylesheets: list[HtmlStylesheetSource] = []
102
+ for element in root.iter():
103
+ if not isinstance(element.tag, str):
104
+ continue
105
+ name = local_name(element)
106
+ if name == "style":
107
+ if _has_discarded_active_ancestor(element):
108
+ continue
109
+ stylesheets.append(HtmlStylesheetSource("inline", "".join(element.itertext())))
110
+ elif name == "link" and "stylesheet" in (element.get("rel") or "").casefold().split():
111
+ if _has_discarded_active_ancestor(element):
112
+ continue
113
+ if href := (element.get("href") or "").strip():
114
+ stylesheets.append(HtmlStylesheetSource("link", href))
115
+ base_href = next(
116
+ (
117
+ value
118
+ for element in root.iter()
119
+ if isinstance(element.tag, str)
120
+ and local_name(element) == "base"
121
+ and not _has_discarded_active_ancestor(element)
122
+ and (value := (element.get("href") or "").strip())
123
+ ),
124
+ None,
125
+ )
126
+ title = next(
127
+ (
128
+ _collapsed_text(element)
129
+ for element in root.iter()
130
+ if isinstance(element.tag, str) and local_name(element) == "title" and _collapsed_text(element)
131
+ ),
132
+ None,
133
+ )
134
+ open_graph_title = _meta_content(root, property_name="og:title")
135
+ site_name = _meta_content(root, property_name="og:site_name")
136
+
137
+ _normalize_formula_sources(root)
138
+ _remove_active_content(root)
139
+ body = next(
140
+ (element for element in root.iter() if isinstance(element.tag, str) and local_name(element) == "body"),
141
+ None,
142
+ )
143
+ if body is None:
144
+ body = etree.Element("body")
145
+ body.append(deepcopy(root))
146
+ root = etree.Element("html")
147
+ root.append(body)
148
+ return HtmlDocument(
149
+ root=root,
150
+ body=body,
151
+ stylesheets=tuple(stylesheets),
152
+ base_href=base_href,
153
+ title=title,
154
+ open_graph_title=open_graph_title,
155
+ site_name=site_name,
156
+ source_context=context,
157
+ )
158
+
159
+
160
+ def _normalize_transport_encoding(value: str | None) -> str | None:
161
+ """把 HTTP 声明编码规范化为 lxml 可用名称,未知标签继续走文档内探测。"""
162
+ if not value:
163
+ return None
164
+ try:
165
+ return codecs.lookup(value).name
166
+ except LookupError:
167
+ return None
168
+
169
+
170
+ def _has_html_encoding_declaration(file_bytes: bytes) -> bool:
171
+ """只承认首个 4 KiB 内由 HTML 语法解析出的真实编码声明。"""
172
+ prefix = file_bytes[:4096]
173
+ if not _HTML_ENCODING_DECLARATION_CANDIDATE_RE.search(prefix):
174
+ return False
175
+ if _XML_ENCODING_DECLARATION_RE.match(prefix):
176
+ return True
177
+
178
+ declaration_parser = lxml_html.HTMLParser(
179
+ recover=True,
180
+ no_network=True,
181
+ remove_comments=False,
182
+ huge_tree=False,
183
+ encoding="iso-8859-1",
184
+ )
185
+ try:
186
+ root = lxml_html.document_fromstring(prefix, parser=declaration_parser)
187
+ except (etree.ParserError, etree.XMLSyntaxError, UnicodeError, ValueError):
188
+ return False
189
+ for element in root.iter():
190
+ if not isinstance(element.tag, str) or local_name(element) != "meta":
191
+ continue
192
+ if (element.get("charset") or "").strip():
193
+ return True
194
+ http_equiv = (element.get("http-equiv") or "").strip().casefold()
195
+ if http_equiv == "content-type" and _META_CONTENT_CHARSET_RE.search(element.get("content") or ""):
196
+ return True
197
+ return False
198
+
199
+
200
+ def _html_parser_input(file_bytes: bytes, *, transport_encoding: str | None = None) -> bytes | str:
201
+ """无显式编码且符合 UTF-8 时先解码,避免 lxml 按单字节旧编码解释正文。"""
202
+ if transport_encoding is not None:
203
+ return file_bytes
204
+ if file_bytes.startswith(_UNICODE_BOMS) or _has_html_encoding_declaration(file_bytes):
205
+ return file_bytes
206
+ try:
207
+ return file_bytes.decode("utf-8")
208
+ except UnicodeDecodeError:
209
+ return file_bytes
210
+
211
+
212
+ def _validate_dom_shape(root: etree._Element) -> None:
213
+ """迭代校验 DOM 节点数和最大深度,避免深层递归继续传播。"""
214
+ node_count = 0
215
+ stack: list[tuple[etree._Element, int]] = [(root, 1)]
216
+ while stack:
217
+ node, depth = stack.pop()
218
+ node_count += 1
219
+ if node_count > MAX_HTML_NODES:
220
+ raise HtmlResourceLimitError(f"HTML resource limit exceeded: max_html_nodes={MAX_HTML_NODES}")
221
+ if not isinstance(node.tag, str):
222
+ continue
223
+ if depth > MAX_HTML_DEPTH:
224
+ raise HtmlResourceLimitError(f"HTML resource limit exceeded: max_html_depth={MAX_HTML_DEPTH}")
225
+ stack.extend((child, depth + 1) for child in node)
226
+
227
+
228
+ def _meta_content(root: etree._Element, *, property_name: str) -> str | None:
229
+ """返回首个匹配 property/name 的非空 meta content。"""
230
+ target = property_name.casefold()
231
+ for element in root.iter():
232
+ if not isinstance(element.tag, str) or local_name(element) != "meta":
233
+ continue
234
+ name = (element.get("property") or element.get("name") or "").strip().casefold()
235
+ content = (element.get("content") or "").strip()
236
+ if name == target and content:
237
+ return content
238
+ return None
239
+
240
+
241
+ def _collapsed_text(element: etree._Element) -> str:
242
+ """折叠元素纯文本中的 HTML 排版空白。"""
243
+ return re.sub(r"\s+", " ", "".join(element.itertext())).strip()
244
+
245
+
246
+ def _has_discarded_active_ancestor(element: etree._Element) -> bool:
247
+ """判断元素是否位于稍后会整棵删除的活动内容祖先中。"""
248
+ return any(isinstance(ancestor.tag, str) and local_name(ancestor) in _ACTIVE_TAGS for ancestor in element.iterancestors())
249
+
250
+
251
+ def _normalize_formula_sources(root: etree._Element) -> None:
252
+ """按共享优先级把成功来源收敛为携带裸 LaTeX 的静态 math 元素。"""
253
+ _preserve_asciimath_text(root)
254
+ for element in list(root.iter()):
255
+ if not isinstance(element.tag, str) or not _is_attached(root, element):
256
+ continue
257
+ classes = frozenset((element.get("class") or "").casefold().split())
258
+ is_candidate = _is_formula_carrier(element) or (
259
+ bool(classes & _GENERIC_FORMULA_CLASS_TOKENS) and _formula_wrapper_contains_only_carrier(element)
260
+ )
261
+ if not is_candidate:
262
+ continue
263
+ if formula := extract_formula(element):
264
+ _replace_with_formula(element, formula)
265
+
266
+
267
+ def _is_formula_carrier(element: etree._Element) -> bool:
268
+ """判断元素自身是否携带公式来源,而不是仅从任意后代继承。"""
269
+ if local_name(element) == "math" or is_tex_script(element):
270
+ return True
271
+ if any((element.get(attribute) or "").strip() for attribute in ("data-docvortex-latex", "data-tex", "data-expr")):
272
+ return True
273
+ classes = frozenset((element.get("class") or "").casefold().split())
274
+ return bool(classes & _FORMULA_GENERATOR_CLASS_TOKENS)
275
+
276
+
277
+ def _formula_wrapper_contains_only_carrier(element: etree._Element) -> bool:
278
+ """仅允许恰好一个 carrier 且其外没有可见文本或媒体的通用 wrapper 整体折叠。"""
279
+ carrier: etree._Element | None = None
280
+ for candidate in element.iterdescendants():
281
+ if not isinstance(candidate.tag, str) or not _is_formula_carrier(candidate):
282
+ continue
283
+ if carrier is None:
284
+ carrier = candidate
285
+ continue
286
+ if any(ancestor is carrier for ancestor in candidate.iterancestors()):
287
+ continue
288
+ return False
289
+ if carrier is None:
290
+ return False
291
+
292
+ def inside_carrier(candidate: etree._Element | None) -> bool:
293
+ """判断节点正文是否位于唯一 carrier 子树内。"""
294
+ return candidate is not None and (
295
+ candidate is carrier or any(ancestor is carrier for ancestor in candidate.iterancestors())
296
+ )
297
+
298
+ outside_text = [element.text or ""]
299
+ for candidate in element.iterdescendants():
300
+ if not isinstance(candidate.tag, str):
301
+ if not inside_carrier(candidate.getparent()):
302
+ outside_text.append(candidate.tail or "")
303
+ continue
304
+ if not inside_carrier(candidate):
305
+ outside_text.append(candidate.text or "")
306
+ if local_name(candidate) in _MEANINGFUL_FORMULA_SIBLING_TAGS:
307
+ return False
308
+ if not inside_carrier(candidate.getparent()):
309
+ outside_text.append(candidate.tail or "")
310
+ return not any(value.strip() for value in outside_text)
311
+
312
+
313
+ def _preserve_asciimath_text(root: etree._Element) -> None:
314
+ """把暂不支持的 AsciiMath script 转为可见静态文本,避免活动内容清理时丢失。"""
315
+ for element in list(root.iter()):
316
+ if not isinstance(element.tag, str) or local_name(element) != "script":
317
+ continue
318
+ script_type = (element.get("type") or "").strip()
319
+ if _ASCIIMATH_SCRIPT_TYPE_RE.fullmatch(script_type) is None:
320
+ continue
321
+ value = "".join(element.itertext()).strip()
322
+ if not value:
323
+ continue
324
+ parent = element.getparent()
325
+ if parent is None:
326
+ continue
327
+ replacement = etree.Element("span")
328
+ replacement.set("class", "docvortex-formula-fallback")
329
+ replacement.text = value
330
+ replacement.tail = element.tail
331
+ parent.replace(element, replacement)
332
+
333
+
334
+ def _replace_with_formula(element: etree._Element, formula: FormulaExtraction) -> None:
335
+ """用携带规范 LaTeX 的安全 math 占位替换一个网页公式节点。"""
336
+ parent = element.getparent()
337
+ if parent is None:
338
+ return
339
+ replacement = etree.Element("math")
340
+ replacement.set("data-docvortex-latex", formula.latex)
341
+ replacement.set("data-formula-display", formula.display)
342
+ for attribute in _FORMULA_VISIBILITY_ATTRIBUTES:
343
+ if (value := element.get(attribute)) is not None:
344
+ replacement.set(attribute, value)
345
+ if formula.display == "block":
346
+ replacement.set("display", "block")
347
+ if (element.get("data-block-type") or "").strip() == "equation":
348
+ replacement.set("data-block-type", "equation")
349
+ replacement.tail = element.tail
350
+ parent.replace(element, replacement)
351
+
352
+
353
+ def _is_attached(root: etree._Element, element: etree._Element) -> bool:
354
+ """判断预扫描元素是否仍属于当前 DOM,跳过已被外层公式替换的旧后代。"""
355
+ return element is root or any(ancestor is root for ancestor in element.iterancestors())
356
+
357
+
358
+ def _remove_active_content(root: etree._Element) -> None:
359
+ """删除活动内容并把 noscript 静态回退转换为普通容器。"""
360
+ for element in list(root.iter()):
361
+ if isinstance(element, etree._Comment):
362
+ _drop_tree_preserve_tail(element)
363
+ continue
364
+ if not isinstance(element.tag, str):
365
+ continue
366
+ name = local_name(element)
367
+ if name == "noscript":
368
+ element.tag = "div"
369
+ continue
370
+ if name in _ACTIVE_TAGS:
371
+ _drop_tree_preserve_tail(element)
372
+
373
+
374
+ def _drop_tree_preserve_tail(element: etree._Element) -> None:
375
+ """删除节点整棵子树,同时把 tail 归还给相邻文本位置。"""
376
+ parent = element.getparent()
377
+ if parent is None:
378
+ return
379
+ tail = element.tail or ""
380
+ previous = element.getprevious()
381
+ if tail:
382
+ if previous is not None:
383
+ previous.tail = (previous.tail or "") + tail
384
+ else:
385
+ parent.text = (parent.text or "") + tail
386
+ parent.remove(element)
387
+
388
+
389
+ __all__ = ["HtmlDocument", "HtmlStylesheetSource", "parse_html_document"]
@@ -0,0 +1,12 @@
1
+ """HTML Flash 解析的稳定异常类型。"""
2
+
3
+
4
+ class HtmlParseError(ValueError):
5
+ """表示 HTML 字节无法形成可用静态 DOM。"""
6
+
7
+
8
+ class HtmlResourceLimitError(HtmlParseError):
9
+ """表示 HTML 输入、DOM 或资源超过固定安全预算。"""
10
+
11
+
12
+ __all__ = ["HtmlParseError", "HtmlResourceLimitError"]