Spaces:
Sleeping
Sleeping
| """ | |
| modules/export/markdown_exporter.py | |
| ════════════════════════════════════ | |
| تصدير نتائج OCR إلى Markdown مع الحفاظ على البنية. | |
| مستوحى من: Docling (IBM) — docling.txt في مجلد الاقتراحات. | |
| يدعم: | |
| - العناوين (H1, H2, H3) بناءً على حجم النص النسبي | |
| - الجداول بتنسيق Markdown مع محاذاة RTL | |
| - تسميات الصور (caption) | |
| - القوائم النقطية والرقمية | |
| - الفقرات مع الحفاظ على الفراغات | |
| """ | |
| import re | |
| from typing import Optional | |
| def blocks_to_markdown(layout_data: dict, rtl: bool = True) -> str: | |
| """ | |
| تحويل layout_data إلى نص Markdown. | |
| Args: | |
| layout_data: dict يحتوي على 'blocks' (نفس تنسيق layout_preserving.py) | |
| rtl: True = إضافة ماركر RTL للنصوص العربية | |
| Returns: | |
| نص Markdown منسّق | |
| """ | |
| lines = [] | |
| rtl_marker = "\u202b" if rtl else "" # RIGHT-TO-LEFT EMBEDDING | |
| for block in layout_data.get("blocks", []): | |
| btype = block.get("type", "paragraph") | |
| text = block.get("text", "").strip() | |
| if btype == "header": | |
| lines.append(f"## {rtl_marker}{text}\n") | |
| elif btype == "paragraph": | |
| if text: | |
| lines.append(f"{rtl_marker}{text}\n") | |
| elif btype == "caption": | |
| if text: | |
| lines.append(f"*{rtl_marker}{text}*\n") | |
| elif btype == "table": | |
| md_table = _table_to_markdown(block.get("cells", []), rtl_marker) | |
| if md_table: | |
| lines.append(md_table + "\n") | |
| elif btype == "image": | |
| img = block.get("image_file", "") | |
| caption = block.get("caption", "") | |
| if img: | |
| lines.append(f"\n\n\n") | |
| elif btype == "list_item": | |
| prefix = block.get("list_prefix", "-") | |
| lines.append(f"{prefix} {rtl_marker}{text}\n") | |
| # فراغ بين الكتل | |
| lines.append("") | |
| return "\n".join(lines).strip() | |
| def _table_to_markdown(cells: list[list], rtl_marker: str = "") -> str: | |
| """تحويل خلايا جدول إلى Markdown table.""" | |
| if not cells: | |
| return "" | |
| rows = [] | |
| col_count = max(len(row) for row in cells) | |
| # صف العنوان | |
| header = cells[0] | |
| header_cells = [f"{rtl_marker}{str(c).strip()}" for c in header] | |
| # تعبئة الخلايا الناقصة | |
| while len(header_cells) < col_count: | |
| header_cells.append("") | |
| rows.append("| " + " | ".join(header_cells) + " |") | |
| # صف الفاصل (RTL alignment) | |
| rows.append("| " + " | ".join(["---:"] * col_count) + " |") | |
| # بقية الصفوف | |
| for row in cells[1:]: | |
| cells_fmt = [f"{rtl_marker}{str(c).strip()}" for c in row] | |
| while len(cells_fmt) < col_count: | |
| cells_fmt.append("") | |
| rows.append("| " + " | ".join(cells_fmt) + " |") | |
| return "\n".join(rows) | |
| def detect_list_items(text: str) -> list[dict]: | |
| """ | |
| كشف عناصر القوائم العربية والإنجليزية. | |
| الأنماط المدعومة: •, -, *, 1., أ), a) | |
| """ | |
| patterns = [ | |
| (r"^[•\-\*○▪▸►]\s+(.+)", "-"), | |
| (r"^\d+[\.\)]\s+(.+)", "1."), | |
| (r"^[أ-ي][\.\)]\s+(.+)", "أ)"), | |
| (r"^[a-z][\.\)]\s+(.+)", "a)"), | |
| ] | |
| items = [] | |
| for line in text.split("\n"): | |
| line = line.strip() | |
| for pattern, prefix in patterns: | |
| m = re.match(pattern, line) | |
| if m: | |
| items.append({"type": "list_item", "text": m.group(1), "list_prefix": prefix}) | |
| break | |
| else: | |
| if line: | |
| items.append({"type": "paragraph", "text": line}) | |
| return items | |
| def export_to_markdown(layout_data: dict, output_path: Optional[str] = None, | |
| rtl: bool = True) -> str: | |
| """ | |
| واجهة رئيسية: تصدير layout_data إلى ملف Markdown. | |
| Args: | |
| layout_data: dict بنفس تنسيق export_to_docx | |
| output_path: مسار الملف (اختياري — إذا None يُرجع النص فقط) | |
| rtl: True للنصوص العربية | |
| Returns: | |
| نص Markdown | |
| """ | |
| md = blocks_to_markdown(layout_data, rtl=rtl) | |
| if output_path: | |
| with open(output_path, "w", encoding="utf-8") as f: | |
| f.write(md) | |
| return md | |