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,365 @@
1
+ """Standalone HTML 链接、图片与本地 stylesheet 的安全解析。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ from pathlib import Path
7
+ from typing import Protocol
8
+ from urllib.parse import SplitResult, unquote, urljoin, urlsplit, urlunsplit
9
+
10
+ from lxml import etree # type: ignore[reportMissingImports]
11
+
12
+ from ....foundation.image_payload import parse_image_data_uri_strict, validate_remote_image_url
13
+ from .._shared.hyperlink import sanitize_hyperlink_target
14
+ from docvortex.content.markup import ResolvedMarkupImage
15
+ from .constants import (
16
+ MAX_HTML_IMAGE_BYTES,
17
+ MAX_HTML_IMAGE_TOTAL_BYTES,
18
+ MAX_HTML_STYLESHEET_BYTES,
19
+ MAX_HTML_STYLESHEET_TOTAL_BYTES,
20
+ )
21
+ from docvortex.document.contracts import HtmlSourceContext
22
+ from .errors import HtmlResourceLimitError
23
+
24
+
25
+ _IMAGE_MIME_SIGNATURES: tuple[tuple[str, tuple[bytes, ...]], ...] = (
26
+ ("image/jpeg", (b"\xff\xd8\xff",)),
27
+ ("image/png", (b"\x89PNG\r\n\x1a\n",)),
28
+ ("image/gif", (b"GIF87a", b"GIF89a")),
29
+ ("image/webp", (b"RIFF",)),
30
+ ("image/bmp", (b"BM",)),
31
+ ("image/tiff", (b"II*\x00", b"MM\x00*")),
32
+ )
33
+ _SAME_DOCUMENT_SCHEMES = frozenset({"file", "http", "https"})
34
+
35
+
36
+ class HtmlAnchorResolver(Protocol):
37
+ """定义资源解析与共享 projector 所需的文档内 anchor 查询。"""
38
+
39
+ def resolve_fragment(self, fragment: str) -> str | None:
40
+ """把源 fragment 转为统一内部链接。"""
41
+
42
+ def heading_anchor(self, heading: etree._Element) -> str | None:
43
+ """返回标题节点的统一 anchor。"""
44
+
45
+ def heading_label(self, anchor: str) -> str | None:
46
+ """返回统一 anchor 对应的标题文本。"""
47
+
48
+ def note_anchor(self, note: etree._Element) -> str | None:
49
+ """返回脚注节点的统一 anchor。"""
50
+
51
+
52
+ class HtmlResourceContext:
53
+ """实现共享 projector 所需的 HTML 来源、资源与 anchor 适配。"""
54
+
55
+ def __init__(
56
+ self,
57
+ source_context: HtmlSourceContext,
58
+ *,
59
+ base_href: str | None = None,
60
+ ) -> None:
61
+ """绑定来源上下文并计算不会逃逸本地根目录的 base。"""
62
+ self.source_context = source_context
63
+ self.base_href = base_href
64
+ self.anchors: HtmlAnchorResolver | None = None
65
+ self._image_bytes = 0
66
+ self._stylesheet_bytes = 0
67
+ self._image_cache: dict[Path, str] = {}
68
+ self._data_image_cache: dict[str, ResolvedMarkupImage] = {}
69
+ self._stylesheet_cache: dict[Path, str] = {}
70
+ self._local_root = source_context.local_resource_root.resolve() if source_context.local_resource_root else None
71
+ self._local_base = self._resolve_local_base()
72
+ self._remote_base = self._resolve_remote_base()
73
+
74
+ def bind_anchors(self, anchors: HtmlAnchorResolver) -> None:
75
+ """在正文选择完成后绑定仅包含实际输出目标的 anchor 表。"""
76
+ self.anchors = anchors
77
+
78
+ def same_document_fragment(self, href: str) -> str | None:
79
+ """解析纯 fragment 或与来源文档身份相同的相对、绝对 URL fragment。"""
80
+ normalized = sanitize_hyperlink_target(
81
+ href,
82
+ allowed_schemes=_SAME_DOCUMENT_SCHEMES,
83
+ allow_relative=True,
84
+ allow_fragment=True,
85
+ allow_root_relative=True,
86
+ )
87
+ if normalized is None:
88
+ return None
89
+ base_href = (self.base_href or "").strip()
90
+ if normalized.startswith("#") and (not base_href or base_href.startswith("#")):
91
+ fragment = unquote(normalized[1:]).strip()
92
+ return fragment or None
93
+ source_uri = (self.source_context.source_uri or "").strip()
94
+ if not source_uri:
95
+ return None
96
+ try:
97
+ source_parts = urlsplit(source_uri)
98
+ base_uri = urljoin(source_uri, base_href)
99
+ target_parts = urlsplit(urljoin(base_uri, normalized))
100
+ except ValueError:
101
+ return None
102
+ fragment = unquote(target_parts.fragment).strip()
103
+ if not fragment or _document_url_identity(target_parts) != _document_url_identity(source_parts):
104
+ return None
105
+ return fragment
106
+
107
+ def resolve_link(self, href: str) -> str | None:
108
+ """解析安全外链、相对链接或实际存在的文档内 fragment。"""
109
+ candidate = (href or "").strip()
110
+ if self._remote_base and candidate.startswith("//"):
111
+ try:
112
+ candidate = urljoin(self._remote_base, candidate)
113
+ except ValueError:
114
+ return None
115
+ if self.anchors is not None and (fragment := self.same_document_fragment(candidate)) is not None:
116
+ if internal := self.anchors.resolve_fragment(fragment):
117
+ return internal
118
+ normalized = sanitize_hyperlink_target(
119
+ candidate,
120
+ allow_relative=True,
121
+ allow_fragment=True,
122
+ allow_root_relative=True,
123
+ )
124
+ if normalized is None:
125
+ return None
126
+ if normalized.startswith("#"):
127
+ base_href = (self.base_href or "").strip()
128
+ if base_href:
129
+ source_uri = (self.source_context.source_uri or "").strip()
130
+ try:
131
+ target = urljoin(urljoin(source_uri, base_href), normalized)
132
+ except ValueError:
133
+ return None
134
+ return sanitize_hyperlink_target(
135
+ target,
136
+ allow_relative=True,
137
+ allow_fragment=True,
138
+ allow_root_relative=True,
139
+ )
140
+ return self.anchors.resolve_fragment(normalized) if self.anchors else None
141
+ parsed = urlsplit(normalized)
142
+ if parsed.scheme:
143
+ return normalized
144
+ if self._remote_base:
145
+ resolved = urljoin(self._remote_base, normalized)
146
+ return sanitize_hyperlink_target(resolved)
147
+ if self._local_root is not None and self._local_base is not None and parsed.path and not parsed.path.startswith("/"):
148
+ local_path = self._resolve_local_path(normalized)
149
+ if local_path is None:
150
+ return None
151
+ relative_path = local_path.relative_to(self._local_root).as_posix()
152
+ return sanitize_hyperlink_target(
153
+ urlunsplit(("", "", relative_path, parsed.query, parsed.fragment)),
154
+ allow_relative=True,
155
+ allow_fragment=True,
156
+ )
157
+ return sanitize_hyperlink_target(normalized, allow_relative=True, allow_fragment=True)
158
+
159
+ def resolve_image(self, source: str, *, alt: str = "") -> ResolvedMarkupImage | None:
160
+ """按 data URI、本地安全文件、远程 URL 的顺序解析图片。"""
161
+ normalized = (source or "").strip()
162
+ if not normalized:
163
+ return ResolvedMarkupImage(alt=alt) if alt else None
164
+ if normalized.casefold().startswith("data:"):
165
+ return self._resolve_data_image(normalized, alt=alt)
166
+ if remote_url := self._resolve_remote_image_url(normalized):
167
+ return ResolvedMarkupImage(image_url=remote_url, alt=alt)
168
+ local_path = self._resolve_local_path(normalized)
169
+ if local_path is None or not local_path.is_file():
170
+ return ResolvedMarkupImage(alt=alt) if alt else None
171
+ cached = self._image_cache.get(local_path)
172
+ if cached is not None:
173
+ return ResolvedMarkupImage(image_base64=cached, alt=alt)
174
+ with local_path.open("rb") as source_file:
175
+ payload = source_file.read(MAX_HTML_IMAGE_BYTES + 1)
176
+ if len(payload) > MAX_HTML_IMAGE_BYTES:
177
+ raise HtmlResourceLimitError(f"HTML image exceeds max_html_image_bytes={MAX_HTML_IMAGE_BYTES}")
178
+ data_uri = _image_data_uri(payload)
179
+ if data_uri is None:
180
+ return ResolvedMarkupImage(alt=alt) if alt else None
181
+ self._charge_image_bytes(len(payload))
182
+ self._image_cache[local_path] = data_uri
183
+ return ResolvedMarkupImage(image_base64=data_uri, alt=alt)
184
+
185
+ def load_stylesheet(self, href: str) -> str | None:
186
+ """只读取安全本地根目录内的 stylesheet,远程 CSS 始终忽略。"""
187
+ path = self._resolve_local_path(href)
188
+ if path is None or not path.is_file():
189
+ return None
190
+ cached = self._stylesheet_cache.get(path)
191
+ if cached is not None:
192
+ return cached
193
+ with path.open("rb") as source_file:
194
+ payload = source_file.read(MAX_HTML_STYLESHEET_BYTES + 1)
195
+ self._charge_stylesheet_bytes(len(payload))
196
+ stylesheet = payload.decode("utf-8-sig", errors="replace")
197
+ self._stylesheet_cache[path] = stylesheet
198
+ return stylesheet
199
+
200
+ def charge_inline_stylesheet(self, stylesheet: str) -> None:
201
+ """把一段内联 CSS 的 UTF-8 字节数计入统一 stylesheet 预算。"""
202
+ self._charge_stylesheet_bytes(len(stylesheet.encode("utf-8")))
203
+
204
+ def _charge_stylesheet_bytes(self, byte_count: int) -> None:
205
+ """执行单份和整文档 stylesheet 字节限制,并只在校验通过后累计。"""
206
+ if byte_count > MAX_HTML_STYLESHEET_BYTES:
207
+ raise HtmlResourceLimitError(f"HTML stylesheet exceeds max_html_stylesheet_bytes={MAX_HTML_STYLESHEET_BYTES}")
208
+ total_bytes = self._stylesheet_bytes + byte_count
209
+ if total_bytes > MAX_HTML_STYLESHEET_TOTAL_BYTES:
210
+ raise HtmlResourceLimitError(
211
+ f"HTML stylesheets exceed max_html_stylesheet_total_bytes={MAX_HTML_STYLESHEET_TOTAL_BYTES}"
212
+ )
213
+ self._stylesheet_bytes = total_bytes
214
+
215
+ def heading_anchor(self, heading: etree._Element) -> str | None:
216
+ """把标题 anchor 查询委托给已绑定的注册表。"""
217
+ return self.anchors.heading_anchor(heading) if self.anchors else None
218
+
219
+ def heading_label(self, anchor: str) -> str | None:
220
+ """把标题标签查询委托给已绑定的注册表。"""
221
+ return self.anchors.heading_label(anchor) if self.anchors else None
222
+
223
+ def note_anchor(self, note: etree._Element) -> str | None:
224
+ """把脚注 anchor 查询委托给已绑定的注册表。"""
225
+ return self.anchors.note_anchor(note) if self.anchors else None
226
+
227
+ def _resolve_data_image(self, data_uri: str, *, alt: str) -> ResolvedMarkupImage | None:
228
+ """严格解析 data URI,并执行单图与累计图片预算。"""
229
+ if cached := self._data_image_cache.get(data_uri):
230
+ return ResolvedMarkupImage(image_base64=cached.image_base64, alt=alt)
231
+ if len(data_uri) > MAX_HTML_IMAGE_BYTES * 2:
232
+ raise HtmlResourceLimitError(f"HTML image exceeds max_html_image_bytes={MAX_HTML_IMAGE_BYTES}")
233
+ try:
234
+ payload, extension = parse_image_data_uri_strict(data_uri)
235
+ except ValueError:
236
+ return ResolvedMarkupImage(alt=alt) if alt else None
237
+ if extension == "svg":
238
+ return ResolvedMarkupImage(alt=alt) if alt else None
239
+ if len(payload) > MAX_HTML_IMAGE_BYTES:
240
+ raise HtmlResourceLimitError(f"HTML image exceeds max_html_image_bytes={MAX_HTML_IMAGE_BYTES}")
241
+ self._charge_image_bytes(len(payload))
242
+ resolved = ResolvedMarkupImage(image_base64=data_uri, alt=alt)
243
+ self._data_image_cache[data_uri] = resolved
244
+ return resolved
245
+
246
+ def _charge_image_bytes(self, byte_count: int) -> None:
247
+ """累计实际保留的图片字节,并在超限时终止整份文档。"""
248
+ self._image_bytes += byte_count
249
+ if self._image_bytes > MAX_HTML_IMAGE_TOTAL_BYTES:
250
+ raise HtmlResourceLimitError(f"HTML images exceed max_html_image_total_bytes={MAX_HTML_IMAGE_TOTAL_BYTES}")
251
+
252
+ def _resolve_remote_image_url(self, source: str) -> str | None:
253
+ """把远程或远程来源相对图片解析为受限 HTTP(S) 绝对 URL。"""
254
+ try:
255
+ parsed = urlsplit(source)
256
+ except ValueError:
257
+ return None
258
+ if parsed.scheme:
259
+ try:
260
+ return validate_remote_image_url(source)
261
+ except ValueError:
262
+ return None
263
+ if not self._remote_base:
264
+ return None
265
+ try:
266
+ return validate_remote_image_url(urljoin(self._remote_base, source))
267
+ except ValueError:
268
+ return None
269
+
270
+ def _resolve_remote_base(self) -> str | None:
271
+ """返回来源 URI 与 base href 合成后的 HTTP(S) 资源基址。"""
272
+ source_uri = (self.source_context.source_uri or "").strip()
273
+ base_href = (self.base_href or "").strip()
274
+ try:
275
+ base_scheme = urlsplit(base_href).scheme.casefold() if base_href else ""
276
+ source_scheme = urlsplit(source_uri).scheme.casefold() if source_uri else ""
277
+ except ValueError:
278
+ return None
279
+ if base_href and base_scheme in {"http", "https"}:
280
+ base = base_href
281
+ elif source_uri and source_scheme in {"http", "https"}:
282
+ base = urljoin(source_uri, base_href) if base_href else source_uri
283
+ else:
284
+ return None
285
+ try:
286
+ parsed = urlsplit(base)
287
+ except ValueError:
288
+ return None
289
+ return base if parsed.scheme.casefold() in {"http", "https"} and parsed.hostname else None
290
+
291
+ def _resolve_local_base(self) -> Path | None:
292
+ """按本地安全根与相对 base href 计算资源起始目录。"""
293
+ root = self._local_root
294
+ if root is None:
295
+ return None
296
+ raw_base = (self.base_href or "").strip()
297
+ if not raw_base:
298
+ return root
299
+ try:
300
+ parsed = urlsplit(raw_base)
301
+ except ValueError:
302
+ return root
303
+ if parsed.scheme or parsed.netloc or parsed.path.startswith(("/", "\\")) or "\\" in parsed.path:
304
+ return root
305
+ decoded = unquote(parsed.path)
306
+ if not decoded:
307
+ return root
308
+ candidate = (root / decoded).resolve()
309
+ base = candidate if raw_base.endswith("/") else candidate.parent
310
+ return base if _is_within(base, root) else root
311
+
312
+ def _resolve_local_path(self, source: str) -> Path | None:
313
+ """解析本地相对资源,并在 resolve 后再次校验根目录边界。"""
314
+ root = self._local_root
315
+ base = self._local_base
316
+ if root is None or base is None:
317
+ return None
318
+ try:
319
+ parsed = urlsplit(source)
320
+ except ValueError:
321
+ return None
322
+ if parsed.scheme or parsed.netloc or parsed.path.startswith(("/", "\\")) or "\\" in parsed.path:
323
+ return None
324
+ decoded = unquote(parsed.path)
325
+ if not decoded or "\x00" in decoded or ".." in Path(decoded).parts:
326
+ return None
327
+ candidate = (base / decoded).resolve()
328
+ return candidate if _is_within(candidate, root) else None
329
+
330
+
331
+ def _image_data_uri(payload: bytes) -> str | None:
332
+ """按文件签名构造并复核受支持栅格图片的 data URI。"""
333
+ mime = next(
334
+ (
335
+ media_type
336
+ for media_type, signatures in _IMAGE_MIME_SIGNATURES
337
+ if any(payload.startswith(signature) for signature in signatures)
338
+ ),
339
+ None,
340
+ )
341
+ if mime == "image/webp" and (len(payload) < 12 or payload[8:12] != b"WEBP"):
342
+ mime = None
343
+ if mime is None:
344
+ return None
345
+ data_uri = f"data:{mime};base64,{base64.b64encode(payload).decode('ascii')}"
346
+ try:
347
+ parse_image_data_uri_strict(data_uri)
348
+ except ValueError:
349
+ return None
350
+ return data_uri
351
+
352
+
353
+ def _document_url_identity(parts: SplitResult) -> tuple[str, str, str, str]:
354
+ """返回忽略 fragment 的确定文档身份,并规范层级 URL 的空路径。"""
355
+ scheme = parts.scheme.casefold()
356
+ path = parts.path or ("/" if scheme in {"http", "https"} else "")
357
+ return scheme, parts.netloc.casefold(), unquote(path), parts.query
358
+
359
+
360
+ def _is_within(path: Path, root: Path) -> bool:
361
+ """判断已解析路径是否等于安全根或位于其内部。"""
362
+ return path == root or root in path.parents
363
+
364
+
365
+ __all__ = ["HtmlAnchorResolver", "HtmlResourceContext"]