raw2md 0.1.0__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 (91) hide show
  1. raw2md/__init__.py +1 -0
  2. raw2md/__main__.py +5 -0
  3. raw2md/cleaner.py +389 -0
  4. raw2md/cleaning/__init__.py +169 -0
  5. raw2md/cleaning/findings.py +640 -0
  6. raw2md/cleaning/headings.py +2506 -0
  7. raw2md/cleaning/lists.py +348 -0
  8. raw2md/cleaning/math.py +2528 -0
  9. raw2md/cleaning/ocr.py +354 -0
  10. raw2md/cleaning/options.py +36 -0
  11. raw2md/cleaning/prose.py +1513 -0
  12. raw2md/cleaning/segments.py +357 -0
  13. raw2md/cleaning/tables.py +1661 -0
  14. raw2md/cli.py +240 -0
  15. raw2md/config.py +113 -0
  16. raw2md/doctor.py +222 -0
  17. raw2md/engines/__init__.py +40 -0
  18. raw2md/engines/base.py +179 -0
  19. raw2md/engines/djvu.py +309 -0
  20. raw2md/engines/marker.py +273 -0
  21. raw2md/engines/pandoc.py +206 -0
  22. raw2md/engines/pandoc_tables.py +187 -0
  23. raw2md/engines/preflight.py +56 -0
  24. raw2md/engines/pymupdf.py +153 -0
  25. raw2md/engines/signature.py +81 -0
  26. raw2md/exit_codes.py +16 -0
  27. raw2md/header.py +281 -0
  28. raw2md/init.py +147 -0
  29. raw2md/katex.min.js +1 -0
  30. raw2md/katex.py +93 -0
  31. raw2md/keywords.py +251 -0
  32. raw2md/keywords.yaml +111 -0
  33. raw2md/llm/__init__.py +58 -0
  34. raw2md/llm/acceptance.py +196 -0
  35. raw2md/llm/base.py +327 -0
  36. raw2md/llm/chunking.py +427 -0
  37. raw2md/llm/claude.py +189 -0
  38. raw2md/llm/edit_guard.py +816 -0
  39. raw2md/llm/factory.py +25 -0
  40. raw2md/llm/gemini.py +475 -0
  41. raw2md/llm/inspection/__init__.py +11 -0
  42. raw2md/llm/inspection/addressing.py +460 -0
  43. raw2md/llm/inspection/apply.py +369 -0
  44. raw2md/llm/inspection/common.py +73 -0
  45. raw2md/llm/inspection/coordinator.py +410 -0
  46. raw2md/llm/inspection/masking.py +272 -0
  47. raw2md/llm/inspection/numbering.py +216 -0
  48. raw2md/llm/inspection/reply.py +302 -0
  49. raw2md/llm/ocr.py +196 -0
  50. raw2md/llm/post/__init__.py +9 -0
  51. raw2md/llm/post/blocks.py +820 -0
  52. raw2md/llm/post/common.py +186 -0
  53. raw2md/llm/post/coordinator.py +341 -0
  54. raw2md/llm/post/ladder.py +609 -0
  55. raw2md/llm/post/spacing.py +341 -0
  56. raw2md/llm/post/tables.py +599 -0
  57. raw2md/llm/rpd.py +102 -0
  58. raw2md/llm/trace.py +98 -0
  59. raw2md/lock.py +178 -0
  60. raw2md/logging_setup.py +188 -0
  61. raw2md/mdtext/__init__.py +12 -0
  62. raw2md/mdtext/formulas.py +355 -0
  63. raw2md/mdtext/lines.py +93 -0
  64. raw2md/mdtext/links.py +67 -0
  65. raw2md/mdtext/loops.py +436 -0
  66. raw2md/mdtext/math_spans.py +118 -0
  67. raw2md/mdtext/pages.py +65 -0
  68. raw2md/mdtext/tables.py +490 -0
  69. raw2md/mdtext/zones.py +190 -0
  70. raw2md/media.py +379 -0
  71. raw2md/orchestrator.py +535 -0
  72. raw2md/output.py +229 -0
  73. raw2md/paths.py +47 -0
  74. raw2md/pipeline.py +1161 -0
  75. raw2md/progress.py +239 -0
  76. raw2md/prompts.py +336 -0
  77. raw2md/quality/__init__.py +5 -0
  78. raw2md/quality/evaluator.py +1155 -0
  79. raw2md/quality/headings.py +461 -0
  80. raw2md/queue.py +405 -0
  81. raw2md/service.py +62 -0
  82. raw2md/settings.py +473 -0
  83. raw2md/source_outline.py +141 -0
  84. raw2md/source_text.py +196 -0
  85. raw2md/temp.py +65 -0
  86. raw2md-0.1.0.dist-info/METADATA +322 -0
  87. raw2md-0.1.0.dist-info/RECORD +91 -0
  88. raw2md-0.1.0.dist-info/WHEEL +4 -0
  89. raw2md-0.1.0.dist-info/entry_points.txt +2 -0
  90. raw2md-0.1.0.dist-info/licenses/LICENSE +21 -0
  91. raw2md-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +87 -0
raw2md/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """raw2md: convert raw documents (PDF, DOCX, DjVu) to cleaned Markdown."""
raw2md/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from raw2md.cli import main
4
+
5
+ sys.exit(main())
raw2md/cleaner.py ADDED
@@ -0,0 +1,389 @@
1
+ """Deterministic cleaning: in-place fixes, and a report of what stays broken.
2
+
3
+ Cleaning runs before any LLM step. An unambiguous defect is fixed in place
4
+ (`clean_in_place`); an ambiguous one is reported as a finding and left in the
5
+ body (`cleaning.findings`). `clean_with_report` returns both; `clean` returns
6
+ the body alone.
7
+
8
+ Protected zones pass through verbatim: front matter, fenced code, and HTML
9
+ tables. Three rules reach into an HTML table's cells but never its markup:
10
+ the alt-path strip, the LLM-OCR fabrication strip, and the math cell opening.
11
+
12
+ This module owns the order of the passes, the one thing about cleaning that
13
+ must be read as a whole. The rules live in the `cleaning` package.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass, replace
19
+
20
+ from raw2md.cleaning import (
21
+ CleanOptions,
22
+ Finding,
23
+ apply_outline_levels,
24
+ classify_and_fix,
25
+ clear_display_span_tail,
26
+ close_section_level_gaps,
27
+ close_unpaired_delimiters,
28
+ collapse_blanks,
29
+ collapse_unused_heading_levels,
30
+ decompose_contents_table,
31
+ defuse_false_math,
32
+ drop_bodyless_table,
33
+ drop_content_free_math_spans,
34
+ drop_empty_table_columns,
35
+ drop_orphan_sizers,
36
+ drop_page_furniture,
37
+ drop_paragraph_emphasis,
38
+ drop_running_header_headings,
39
+ drop_stray_bullet_glyph,
40
+ fix_images_and_report,
41
+ fix_table_math,
42
+ fold_repeated_column_cycle,
43
+ group_settled_headings,
44
+ headings_settled_before_nesting,
45
+ join_contents_entry_breaks,
46
+ join_flattened_paragraphs,
47
+ join_letter_spaced_headings,
48
+ join_letter_spaced_runs,
49
+ join_wrapped_contents_entries,
50
+ keep_table_delimiter_under_header,
51
+ lay_out_table_blocks,
52
+ lift_prose_from_display_math,
53
+ mend_pipe_block_missing_delimiter,
54
+ normalize_heading_numbering,
55
+ normalize_lists,
56
+ normalize_math,
57
+ normalize_repeated_heading_levels,
58
+ normalize_unnumbered_heading_levels,
59
+ open_math_html_cells,
60
+ printed_outline,
61
+ promote_dingbat_bullet,
62
+ promote_paragraph_formula,
63
+ reclose_runaway_display_spans,
64
+ reclose_runaway_inline_spans,
65
+ recut_periodic_stack,
66
+ recut_swallowed_cells,
67
+ rejoin_hyphenation,
68
+ repair_attested_tokens,
69
+ reunite_split_formulas,
70
+ reunite_split_tables,
71
+ separate_bold_list_marker,
72
+ separate_glued_inline_spans,
73
+ settle_compound_hyphens,
74
+ split_glued_table_header,
75
+ split_run_in_headings,
76
+ spread_crushed_runs,
77
+ strip_alt_file_paths,
78
+ strip_heading_emphasis,
79
+ strip_heading_leading_glyphs,
80
+ strip_html_anchors,
81
+ strip_math_span_html,
82
+ strip_ocr_fabrications,
83
+ strip_oversize_heading_markers,
84
+ tag_equation_numbers,
85
+ trim_repetition_loops,
86
+ unwrap_single_glyph_math,
87
+ )
88
+ from raw2md.mdtext.zones import Segment, segments
89
+ from raw2md.source_outline import SourceOutline
90
+
91
+ # `CleanOptions` is re-exported: the options travel with the entry points.
92
+ __all__ = [
93
+ "CleanOptions",
94
+ "CleanResult",
95
+ "clean",
96
+ "clean_in_place",
97
+ "clean_with_report",
98
+ ]
99
+
100
+ # The two unnumbered-level rules can hand each other a fresh violation, so
101
+ # they alternate to a fixed point. The cap is defensive, not a proven bound:
102
+ # observed documents settle in one or two rounds. A missed fix beats a hang.
103
+ _HEADING_LEVEL_SETTLE_ROUNDS = 8
104
+
105
+ # The running-header drop reads levels that the later heading rules rewrite,
106
+ # and each drop changes their evidence, so the whole block repeats until
107
+ # nothing moves.
108
+ _HEADING_STRUCTURE_SETTLE_ROUNDS = 4
109
+
110
+ # A joined line carries the break of the line it absorbed, behind the walk.
111
+ _HYPHENATION_SETTLE_ROUNDS = 4
112
+
113
+
114
+ def clean_in_place(text: str, options: CleanOptions | None = None) -> str:
115
+ """Apply the in-place fixes to LF `text` and return the cleaned markdown.
116
+
117
+ The result ends with one newline, or is empty. Without `options` the
118
+ route is unknown, which disables heading recovery.
119
+ """
120
+ if options is None:
121
+ options = CleanOptions()
122
+ text = strip_alt_file_paths(text)
123
+ if options.llm_ocr:
124
+ text = strip_ocr_fabrications(text, options.keywords)
125
+ seg_list = classify_and_fix(text)
126
+ seg_list = strip_html_anchors(seg_list)
127
+ seg_list = strip_heading_emphasis(seg_list)
128
+ # Before every rule that reads a heading's number: a stray leading glyph
129
+ # hides the number, as emphasis does.
130
+ seg_list = strip_heading_leading_glyphs(seg_list)
131
+ seg_list = split_run_in_headings(seg_list)
132
+ # Before every rule that compares titles: a letter-spaced title matches
133
+ # nothing but itself.
134
+ seg_list = join_letter_spaced_headings(seg_list)
135
+ # Before every rule that reads a heading's level: an oversize "heading" is
136
+ # mistagged prose or a crushed table and would feed a false section to
137
+ # the level votes.
138
+ seg_list = strip_oversize_heading_markers(seg_list)
139
+ seg_list = drop_running_header_headings(seg_list)
140
+ seg_list = drop_page_furniture(seg_list, options.keywords)
141
+ seg_list = _settle_heading_structure(seg_list, options)
142
+ # Before every span-scoped math rule: a glued-shut delimiter pairs with the
143
+ # next formula's opener, and every span between reads as one runaway block.
144
+ seg_list = reclose_runaway_display_spans(seg_list)
145
+ seg_list = reclose_runaway_inline_spans(seg_list)
146
+ seg_list = fix_table_math(seg_list)
147
+ seg_list = defuse_false_math(seg_list)
148
+ # Before the normalization: a piece of a cut formula balances only once
149
+ # the pieces are joined.
150
+ seg_list = reunite_split_formulas(seg_list)
151
+ # Before every rule that reads span content, after the reunion so that it
152
+ # reads whole spans.
153
+ seg_list = strip_math_span_html(seg_list)
154
+ seg_list = normalize_math(seg_list)
155
+ # After the normalization, which decides what renders; before the tag fold,
156
+ # which would read a legend entry's punctuation as a formula's.
157
+ seg_list = lift_prose_from_display_math(seg_list)
158
+ # Before the tag fold, so a number under a promoted formula folds in as
159
+ # under any display block.
160
+ seg_list = promote_paragraph_formula(seg_list)
161
+ # After the reunion, which moves a number to the span's tail, and after the
162
+ # normalization, which clears `\label{...}` and turns `\eqno` into a tag.
163
+ seg_list = tag_equation_numbers(seg_list)
164
+ seg_list = unwrap_single_glyph_math(seg_list)
165
+ seg_list = drop_content_free_math_spans(seg_list)
166
+ # Before the pairing fix: an orphan sizer would throw off its count.
167
+ seg_list = drop_orphan_sizers(seg_list)
168
+ seg_list = close_unpaired_delimiters(seg_list)
169
+ # Last of the span rules: it moves every span bound after the insert.
170
+ seg_list = separate_glued_inline_spans(seg_list)
171
+ # Before list de-nesting, so the promoted items form a block.
172
+ seg_list = promote_dingbat_bullet(seg_list)
173
+ seg_list = normalize_lists(seg_list)
174
+ seg_list = separate_bold_list_marker(seg_list)
175
+ seg_list = drop_stray_bullet_glyph(seg_list)
176
+ seg_list = split_glued_table_header(seg_list)
177
+ # Before every grid rule: until folded, one column index names several
178
+ # fields. After the header split, which leaves the title row to read.
179
+ seg_list = fold_repeated_column_cycle(seg_list)
180
+ # Before every rule that reads a table body whole: a half reads as a column
181
+ # that no row fills.
182
+ seg_list = reunite_split_tables(seg_list)
183
+ # Before the bodyless collapse, which would claim the same table.
184
+ seg_list = decompose_contents_table(seg_list)
185
+ # Before the bodyless collapse: a body stacked in one header cell leaves no
186
+ # data row, and the collapse would join it into prose.
187
+ seg_list = recut_periodic_stack(seg_list)
188
+ # Before the column rules: without rows, every header cell reads as a
189
+ # column that no row fills.
190
+ seg_list = drop_bodyless_table(seg_list)
191
+ seg_list = drop_empty_table_columns(seg_list)
192
+ # After the empty-column drop, which rewrites the column indices.
193
+ seg_list = recut_swallowed_cells(seg_list)
194
+ # After the recut: what is left is a run whose blanks leave a choice, which
195
+ # only the table's own columns decide.
196
+ seg_list = spread_crushed_runs(seg_list)
197
+ # After every grid rule, which reads an in-cell break as a lost cell; a
198
+ # break still standing is a wrapped contents entry.
199
+ seg_list = join_contents_entry_breaks(seg_list, options.keywords)
200
+ # After that join: a cell still stacking fragments defeats the column
201
+ # comparison.
202
+ seg_list = join_wrapped_contents_entries(seg_list, options.keywords)
203
+ # After the table rules: collapsing a carrier table leaves its number or
204
+ # picture behind the display closer.
205
+ seg_list = clear_display_span_tail(seg_list)
206
+ seg_list = _settle_hyphenation(seg_list, options)
207
+ # After heading recovery: a bold title is a heading by now, so a wrap
208
+ # still standing is noise around prose.
209
+ seg_list = drop_paragraph_emphasis(seg_list)
210
+ # After the rejoin: a fragment still ending in a break holds a split
211
+ # nothing attested, and this rule leaves it.
212
+ seg_list = join_flattened_paragraphs(seg_list)
213
+ seg_list = join_letter_spaced_runs(seg_list)
214
+ # After the rejoin, whose joined spellings the counts must weigh.
215
+ seg_list = settle_compound_hyphens(seg_list)
216
+ # Last of the body-count rules: every join above spells words it weighs.
217
+ seg_list = repair_attested_tokens(seg_list, options.witness_min)
218
+ # Postcondition on every table block: several passes drop a junk header
219
+ # row and leave the separator first, where GFM reads no table. Before the
220
+ # missing-delimiter mend, which would misread the rows under it.
221
+ seg_list = keep_table_delimiter_under_header(seg_list)
222
+ # Postcondition: a pipe block with no delimiter row renders as no table.
223
+ seg_list = mend_pipe_block_missing_delimiter(seg_list)
224
+ # Last segment pass, once no rule moves a cell: padding is billed, since
225
+ # the chunk planner sizes a request by text length.
226
+ seg_list = lay_out_table_blocks(seg_list)
227
+ # Last of all: it writes blank lines inside a protected zone, which every
228
+ # rule above reads as one line per table row.
229
+ return open_math_html_cells(collapse_blanks(seg_list))
230
+
231
+
232
+ def _settle_heading_structure(
233
+ seg_list: list[Segment], options: CleanOptions
234
+ ) -> list[Segment]:
235
+ """Run the heading rules until the structure they decide stops moving.
236
+
237
+ A round drops running headers, then settles levels. A later round sees
238
+ the drop against settled levels instead of the converter's guess. The
239
+ level witness is read once: the heading that closes the contents zone
240
+ moves between rounds, and a re-read would state a new ladder each time.
241
+ """
242
+ outline = _level_witness(seg_list, options)
243
+ for _ in range(_HEADING_STRUCTURE_SETTLE_ROUNDS):
244
+ fixed = _settle_heading_levels(
245
+ drop_running_header_headings(seg_list), options, outline
246
+ )
247
+ if fixed == seg_list:
248
+ break
249
+ seg_list = fixed
250
+ return seg_list
251
+
252
+
253
+ def _level_witness(seg_list: list[Segment], options: CleanOptions) -> SourceOutline:
254
+ """The source outline when it has one, else the printed contents.
255
+
256
+ The two are never merged: each numbers levels on its own scale.
257
+ """
258
+ if options.outline.has_outline:
259
+ return options.outline
260
+ return printed_outline(seg_list, options.keywords)
261
+
262
+
263
+ def _settle_heading_levels(
264
+ seg_list: list[Segment], options: CleanOptions, outline: SourceOutline
265
+ ) -> list[Segment]:
266
+ """Decide every heading's level from the evidence the document itself gives."""
267
+ seg_list, stated = apply_outline_levels(seg_list, outline)
268
+ seg_list, stated = normalize_heading_numbering(seg_list, options.method, stated)
269
+ # A settled group applies from the next round, since its vote reads levels
270
+ # this round validates. A self-numbered series and repeated unnumbered
271
+ # titles settle up front: the nesting rule has nothing to tell them.
272
+ settled = headings_settled_before_nesting(seg_list, options.keywords, stated)
273
+ for _ in range(_HEADING_LEVEL_SETTLE_ROUNDS):
274
+ fixed = normalize_repeated_heading_levels(
275
+ normalize_unnumbered_heading_levels(
276
+ seg_list, options.keywords, stated, settled
277
+ ),
278
+ options.keywords,
279
+ stated,
280
+ )
281
+ settled = group_settled_headings(fixed, options.keywords, stated)
282
+ if fixed == seg_list:
283
+ break
284
+ seg_list = fixed
285
+ # The whole-body collapse first: after a lift the unused rank is in use,
286
+ # and the sections the lift missed keep their hole. The collapse is
287
+ # monotonic, so the lift still sees the holes it would have seen.
288
+ return close_section_level_gaps(
289
+ collapse_unused_heading_levels(seg_list), stated, settled
290
+ )
291
+
292
+
293
+ def _settle_hyphenation(
294
+ seg_list: list[Segment], options: CleanOptions
295
+ ) -> list[Segment]:
296
+ """Rejoin hyphenated line splits until no further join is attested.
297
+
298
+ Rounds count a chain of breaks, not joins; a page does not produce a
299
+ chain longer than two. Each changing round shrinks the body.
300
+ """
301
+ for _ in range(_HYPHENATION_SETTLE_ROUNDS):
302
+ joined = rejoin_hyphenation(seg_list, options.source_text)
303
+ if joined == seg_list:
304
+ break
305
+ seg_list = joined
306
+ return seg_list
307
+
308
+
309
+ @dataclass(frozen=True)
310
+ class CleanResult:
311
+ """What the cleaning stage produced: the body, and what it could not fix.
312
+
313
+ Each finding is addressed by its line in `body`.
314
+ """
315
+
316
+ body: str
317
+ findings: tuple[Finding, ...]
318
+
319
+ def counts(self) -> dict[str, int]:
320
+ """How many findings of each kind, ordered by kind."""
321
+ return {kind: len(group) for kind, group in _by_kind(self.findings).items()}
322
+
323
+
324
+ def _by_kind(findings: tuple[Finding, ...]) -> dict[str, list[Finding]]:
325
+ grouped: dict[str, list[Finding]] = {}
326
+ for finding in sorted(findings, key=lambda f: (f.kind, f.line)):
327
+ grouped.setdefault(finding.kind, []).append(finding)
328
+ return grouped
329
+
330
+
331
+ def clean(text: str, options: CleanOptions | None = None) -> str:
332
+ """Fully clean `text` and return the body, dropping the report."""
333
+ return clean_with_report(text, options).body
334
+
335
+
336
+ def clean_with_report(text: str, options: CleanOptions | None = None) -> CleanResult:
337
+ """Fully clean `text`: in-place fixes, then a report of what stays broken.
338
+
339
+ Idempotent in both halves. Repetition loops are trimmed after the
340
+ detectors, since the trim removes the evidence of a lost formula; only the
341
+ run that made the cut records it. Findings are renumbered onto the
342
+ returned body.
343
+ """
344
+ if options is None:
345
+ options = CleanOptions()
346
+ fixed = clean_in_place(text, options)
347
+ seg_list = segments(fixed)
348
+ seg_list, findings = fix_images_and_report(seg_list, options)
349
+ seg_list, landed = trim_repetition_loops(seg_list)
350
+ numbers = _final_line_numbers(seg_list)
351
+ return CleanResult(
352
+ collapse_blanks(seg_list),
353
+ tuple(
354
+ replace(finding, line=numbers[landed[finding.line]]) for finding in findings
355
+ ),
356
+ )
357
+
358
+
359
+ def _final_line_numbers(seg_list: list[Segment]) -> list[int]:
360
+ """Line number in the collapsed body for each segment, 1-based.
361
+
362
+ Mirrors `collapse_blanks`. A dropped line takes the number of the next
363
+ line, else the last line, else 1 for an empty body.
364
+ """
365
+ numbers: list[int | None] = []
366
+ written = 0
367
+ pending_blank = False
368
+ seen_content = False
369
+ for line, protected in seg_list:
370
+ if not protected and not line.strip():
371
+ pending_blank = seen_content
372
+ numbers.append(None)
373
+ continue
374
+ if pending_blank:
375
+ written += 1
376
+ pending_blank = False
377
+ written += 1
378
+ seen_content = True
379
+ numbers.append(written)
380
+ settled: list[int] = [0] * len(numbers)
381
+ follower = max(written, 1)
382
+ for idx in range(len(numbers) - 1, -1, -1):
383
+ number = numbers[idx]
384
+ if number is None:
385
+ settled[idx] = follower
386
+ else:
387
+ settled[idx] = number
388
+ follower = number
389
+ return settled
@@ -0,0 +1,169 @@
1
+ """Deterministic cleaning rules, one module per object they fix.
2
+
3
+ `cleaner` is the coordinator: it holds the order of the passes and the public
4
+ entry points. The rules here are grouped by what they repair, not by the
5
+ engine that produced the defect. `options` carries the context of the passes;
6
+ `segments` holds the predicates that several rule modules share.
7
+ """
8
+
9
+ from raw2md.cleaning.findings import (
10
+ Finding,
11
+ block_shows_defect,
12
+ drop_image_links,
13
+ fix_images_and_report,
14
+ trim_repetition_loops,
15
+ )
16
+ from raw2md.cleaning.headings import (
17
+ apply_outline_levels,
18
+ close_section_level_gaps,
19
+ collapse_unused_heading_levels,
20
+ detect_oversize_headings,
21
+ detect_run_in_headings,
22
+ drop_running_header_headings,
23
+ group_settled_headings,
24
+ headings_settled_before_nesting,
25
+ join_letter_spaced_headings,
26
+ normalize_heading_numbering,
27
+ normalize_repeated_heading_levels,
28
+ normalize_unnumbered_heading_levels,
29
+ printed_outline,
30
+ split_run_in_headings,
31
+ strip_heading_emphasis,
32
+ strip_heading_leading_glyphs,
33
+ strip_oversize_heading_markers,
34
+ )
35
+ from raw2md.cleaning.lists import (
36
+ drop_stray_bullet_glyph,
37
+ normalize_lists,
38
+ promote_dingbat_bullet,
39
+ separate_bold_list_marker,
40
+ )
41
+ from raw2md.cleaning.math import (
42
+ clear_display_span_tail,
43
+ close_unpaired_delimiters,
44
+ defuse_false_math,
45
+ drop_content_free_math_spans,
46
+ drop_orphan_sizers,
47
+ fix_table_math,
48
+ lift_prose_from_display_math,
49
+ normalize_math,
50
+ promote_paragraph_formula,
51
+ reclose_runaway_display_spans,
52
+ reclose_runaway_inline_spans,
53
+ reunite_split_formulas,
54
+ separate_glued_inline_spans,
55
+ strip_math_span_html,
56
+ tag_equation_numbers,
57
+ unwrap_single_glyph_math,
58
+ )
59
+ from raw2md.cleaning.ocr import (
60
+ DEFAULT_WITNESS_MIN,
61
+ repair_attested_tokens,
62
+ strip_ocr_fabrications,
63
+ )
64
+ from raw2md.cleaning.options import CleanOptions
65
+ from raw2md.cleaning.prose import (
66
+ classify_and_fix,
67
+ collapse_blanks,
68
+ drop_page_furniture,
69
+ drop_paragraph_emphasis,
70
+ join_flattened_paragraphs,
71
+ join_letter_spaced_runs,
72
+ rejoin_hyphenation,
73
+ settle_compound_hyphens,
74
+ strip_alt_file_paths,
75
+ strip_html_anchors,
76
+ strip_line_noise,
77
+ )
78
+ from raw2md.cleaning.tables import (
79
+ decompose_contents_table,
80
+ drop_bodyless_table,
81
+ drop_empty_table_columns,
82
+ fold_repeated_column_cycle,
83
+ join_contents_entry_breaks,
84
+ join_wrapped_contents_entries,
85
+ keep_table_delimiter_under_header,
86
+ lay_out_table_blocks,
87
+ mend_pipe_block_missing_delimiter,
88
+ open_math_html_cells,
89
+ recut_periodic_stack,
90
+ recut_swallowed_cells,
91
+ reunite_split_tables,
92
+ split_glued_table_header,
93
+ spread_crushed_runs,
94
+ )
95
+
96
+ __all__ = [
97
+ "DEFAULT_WITNESS_MIN",
98
+ "CleanOptions",
99
+ "Finding",
100
+ "apply_outline_levels",
101
+ "block_shows_defect",
102
+ "classify_and_fix",
103
+ "clear_display_span_tail",
104
+ "close_section_level_gaps",
105
+ "close_unpaired_delimiters",
106
+ "collapse_blanks",
107
+ "collapse_unused_heading_levels",
108
+ "decompose_contents_table",
109
+ "defuse_false_math",
110
+ "detect_oversize_headings",
111
+ "detect_run_in_headings",
112
+ "drop_bodyless_table",
113
+ "drop_content_free_math_spans",
114
+ "drop_empty_table_columns",
115
+ "drop_image_links",
116
+ "drop_orphan_sizers",
117
+ "drop_page_furniture",
118
+ "drop_paragraph_emphasis",
119
+ "drop_running_header_headings",
120
+ "drop_stray_bullet_glyph",
121
+ "fix_images_and_report",
122
+ "fix_table_math",
123
+ "fold_repeated_column_cycle",
124
+ "group_settled_headings",
125
+ "headings_settled_before_nesting",
126
+ "join_contents_entry_breaks",
127
+ "join_flattened_paragraphs",
128
+ "join_letter_spaced_headings",
129
+ "join_letter_spaced_runs",
130
+ "join_wrapped_contents_entries",
131
+ "keep_table_delimiter_under_header",
132
+ "lay_out_table_blocks",
133
+ "lift_prose_from_display_math",
134
+ "mend_pipe_block_missing_delimiter",
135
+ "normalize_heading_numbering",
136
+ "normalize_lists",
137
+ "normalize_math",
138
+ "normalize_repeated_heading_levels",
139
+ "normalize_unnumbered_heading_levels",
140
+ "open_math_html_cells",
141
+ "printed_outline",
142
+ "promote_dingbat_bullet",
143
+ "promote_paragraph_formula",
144
+ "reclose_runaway_display_spans",
145
+ "reclose_runaway_inline_spans",
146
+ "recut_periodic_stack",
147
+ "recut_swallowed_cells",
148
+ "rejoin_hyphenation",
149
+ "repair_attested_tokens",
150
+ "reunite_split_formulas",
151
+ "reunite_split_tables",
152
+ "separate_bold_list_marker",
153
+ "separate_glued_inline_spans",
154
+ "settle_compound_hyphens",
155
+ "split_glued_table_header",
156
+ "split_run_in_headings",
157
+ "spread_crushed_runs",
158
+ "strip_alt_file_paths",
159
+ "strip_heading_emphasis",
160
+ "strip_heading_leading_glyphs",
161
+ "strip_html_anchors",
162
+ "strip_line_noise",
163
+ "strip_math_span_html",
164
+ "strip_ocr_fabrications",
165
+ "strip_oversize_heading_markers",
166
+ "tag_equation_numbers",
167
+ "trim_repetition_loops",
168
+ "unwrap_single_glyph_math",
169
+ ]