Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -18,11 +18,10 @@ Explicitly omitted vs the heavy Space build:
|
|
| 18 |
- No text-layer row injection, institution-specific splits (UCB / Navy /
|
| 19 |
TD / First Horizon / …), or doc-wide dedupe passes.
|
| 20 |
|
| 21 |
-
Included (data-driven,
|
| 22 |
-
- HTML tables:
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
fix same-width rows with content in the wrong cell (needs bbox / PDF text).
|
| 26 |
|
| 27 |
Configure GLMOCR_API_KEY (environment variable). Optional: glmocr + gradio +
|
| 28 |
pymupdf + pillow installed.
|
|
@@ -315,7 +314,7 @@ def _logical_row_width(entries: List[Tuple[str, int]]) -> int:
|
|
| 315 |
|
| 316 |
|
| 317 |
def _cell_text_empty(full_cell: str) -> bool:
|
| 318 |
-
m = _CELL.fullmatch(full_cell.strip()
|
| 319 |
if not m:
|
| 320 |
inner = re.sub(r"<[^>]+>", " ", full_cell)
|
| 321 |
else:
|
|
@@ -325,6 +324,47 @@ def _cell_text_empty(full_cell: str) -> bool:
|
|
| 325 |
return inner == ""
|
| 326 |
|
| 327 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
def _infer_modal_logical_width(tr_inners: List[str]) -> int:
|
| 329 |
"""
|
| 330 |
Modal logical column count across rows (colspan sums). On frequency ties,
|
|
@@ -355,13 +395,16 @@ def _normalize_one_tr_inner(tr_inner: str, target: int) -> str:
|
|
| 355 |
w = sum(spans)
|
| 356 |
if w < target:
|
| 357 |
cells.extend(["<td></td>"] * (target - w))
|
| 358 |
-
|
|
|
|
| 359 |
while w > target and cells:
|
| 360 |
if spans[-1] != 1 or not _cell_text_empty(cells[-1]):
|
| 361 |
break
|
| 362 |
w -= spans[-1]
|
| 363 |
cells.pop()
|
| 364 |
spans.pop()
|
|
|
|
|
|
|
| 365 |
return "".join(cells)
|
| 366 |
|
| 367 |
|
|
@@ -371,8 +414,11 @@ def normalize_html_table_row_widths(md: str) -> str:
|
|
| 371 |
rows (colspan-aware), then pad rows that are too narrow or strip trailing
|
| 372 |
empty single-colspan cells from rows that are too wide.
|
| 373 |
|
| 374 |
-
No column names
|
| 375 |
-
|
|
|
|
|
|
|
|
|
|
| 376 |
"""
|
| 377 |
if not md or "<table" not in md.lower():
|
| 378 |
return md
|
|
@@ -664,8 +710,8 @@ def _create_gradio_demo():
|
|
| 664 |
with gr.Blocks(title="GLM-OCR (simple)") as demo:
|
| 665 |
gr.Markdown(
|
| 666 |
"# GLM-OCR (simple)\n"
|
| 667 |
-
"Upload a PDF or image. Header and footer bands
|
| 668 |
-
"
|
| 669 |
)
|
| 670 |
file_in = gr.File(
|
| 671 |
label="Upload PDF or image",
|
|
|
|
| 18 |
- No text-layer row injection, institution-specific splits (UCB / Navy /
|
| 19 |
TD / First Horizon / …), or doc-wide dedupe passes.
|
| 20 |
|
| 21 |
+
Included (data-driven, no institution names):
|
| 22 |
+
- HTML tables: modal logical width from rowspan-free rows (colspan-aware);
|
| 23 |
+
pad short rows; trim trailing empty cells; if last cell is empty and the
|
| 24 |
+
previous cell is only a currency token, move that token to the last column.
|
|
|
|
| 25 |
|
| 26 |
Configure GLMOCR_API_KEY (environment variable). Optional: glmocr + gradio +
|
| 27 |
pymupdf + pillow installed.
|
|
|
|
| 314 |
|
| 315 |
|
| 316 |
def _cell_text_empty(full_cell: str) -> bool:
|
| 317 |
+
m = _CELL.fullmatch(full_cell.strip())
|
| 318 |
if not m:
|
| 319 |
inner = re.sub(r"<[^>]+>", " ", full_cell)
|
| 320 |
else:
|
|
|
|
| 324 |
return inner == ""
|
| 325 |
|
| 326 |
|
| 327 |
+
def _cell_plain_text(full_cell: str) -> str:
|
| 328 |
+
"""Visible text of one td/th, no tags."""
|
| 329 |
+
m = _CELL.fullmatch(full_cell.strip())
|
| 330 |
+
if not m:
|
| 331 |
+
t = re.sub(r"<[^>]+>", " ", full_cell)
|
| 332 |
+
else:
|
| 333 |
+
t = m.group(3)
|
| 334 |
+
t = html.unescape(re.sub(r"\s+", " ", t).strip())
|
| 335 |
+
return t
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def _is_whole_cell_currency(text: str) -> bool:
|
| 339 |
+
"""
|
| 340 |
+
True iff the cell is nothing but a currency-looking amount (optional $, commas, 2 decimals).
|
| 341 |
+
Excludes dates (slashes) and arbitrary prose — not keyed to column headers.
|
| 342 |
+
"""
|
| 343 |
+
if not text or "/" in text:
|
| 344 |
+
return False
|
| 345 |
+
return bool(
|
| 346 |
+
re.fullmatch(
|
| 347 |
+
r"-?(?:\$|€|£)?\s*\d{1,3}(?:,\d{3})*\.\d{2}\s*",
|
| 348 |
+
text,
|
| 349 |
+
)
|
| 350 |
+
or re.fullmatch(r"-?(?:\$|€|£)?\s*\d+\.\d{2}\s*", text)
|
| 351 |
+
)
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def _realign_money_if_last_cell_empty(cells: List[str]) -> List[str]:
|
| 355 |
+
"""
|
| 356 |
+
… | X | empty -> … | empty | X when X is currency-only (fixes amount parked
|
| 357 |
+
one column left of an empty trailing cell, including after width padding).
|
| 358 |
+
"""
|
| 359 |
+
if len(cells) < 2:
|
| 360 |
+
return cells
|
| 361 |
+
if not _cell_text_empty(cells[-1]):
|
| 362 |
+
return cells
|
| 363 |
+
if not _is_whole_cell_currency(_cell_plain_text(cells[-2])):
|
| 364 |
+
return cells
|
| 365 |
+
return cells[:-2] + ["<td></td>", cells[-2]]
|
| 366 |
+
|
| 367 |
+
|
| 368 |
def _infer_modal_logical_width(tr_inners: List[str]) -> int:
|
| 369 |
"""
|
| 370 |
Modal logical column count across rows (colspan sums). On frequency ties,
|
|
|
|
| 395 |
w = sum(spans)
|
| 396 |
if w < target:
|
| 397 |
cells.extend(["<td></td>"] * (target - w))
|
| 398 |
+
spans.extend([1] * (target - w))
|
| 399 |
+
w = target
|
| 400 |
while w > target and cells:
|
| 401 |
if spans[-1] != 1 or not _cell_text_empty(cells[-1]):
|
| 402 |
break
|
| 403 |
w -= spans[-1]
|
| 404 |
cells.pop()
|
| 405 |
spans.pop()
|
| 406 |
+
if cells and all(s == 1 for s in spans):
|
| 407 |
+
cells = _realign_money_if_last_cell_empty(cells)
|
| 408 |
return "".join(cells)
|
| 409 |
|
| 410 |
|
|
|
|
| 414 |
rows (colspan-aware), then pad rows that are too narrow or strip trailing
|
| 415 |
empty single-colspan cells from rows that are too wide.
|
| 416 |
|
| 417 |
+
No column names or fixed N: width comes from per-table row statistics.
|
| 418 |
+
If the last cell is empty and the previous cell is only a currency token,
|
| 419 |
+
that amount is moved into the last column (whole-cell shape, not headers).
|
| 420 |
+
Tables with rowspan are skipped. Same-width wrong text that is not
|
| 421 |
+
currency-shaped is not altered.
|
| 422 |
"""
|
| 423 |
if not md or "<table" not in md.lower():
|
| 424 |
return md
|
|
|
|
| 710 |
with gr.Blocks(title="GLM-OCR (simple)") as demo:
|
| 711 |
gr.Markdown(
|
| 712 |
"# GLM-OCR (simple)\n"
|
| 713 |
+
"Upload a PDF or image. Header and footer bands use PDF text when available. "
|
| 714 |
+
"Body markdown is produced by the vision model and may disagree with the PDF text layer."
|
| 715 |
)
|
| 716 |
file_in = gr.File(
|
| 717 |
label="Upload PDF or image",
|