المدونةسير العملقراءة في 6 دقائق

دفعة مستندات عربية ممسوحة: قسّمها وسمّها وعدّ صفحاتها قبل التحويل

قبل أن ترفع دفعة مستندات ممسوحة للتحويل اجعل كل مستند في ملف مستقل، وسمِّ الملفات بتاريخ ورقم مسبوق بأصفار وأرقام من نظام واحد بلا رموز ممنوعة، ثم شغّل سكربت يكتب قائمة بالملفات وصفحاتها وما فيها من مشكلات. هكذا تعرف الرصيد المتوقع وتطابق كل ناتج بأصله عند التسليم.

وصلك مجلد من عميل أو من قسم آخر: عقود إيجار، وصكوك، وخطابات رسمية، وفواتير، بعضها ممسوح في ملف واحد طويل، وبعضها صور من الجوال، وأسماؤها «scan0001» و«عقد جديد (2)» و«عقد-1» و«عقد-10». لو رفعته كما هو للتحويل فستحصل على ملفات وورد أو PDF قابلة للبحث لا تعرف أيها يقابل أي أصل، ولن تعرف كم صفحة ستدفع ثمنها قبل أن تبدأ.

هذا الدليل لمكاتب خدمات المستندات، وموظفي الإدارات والأرشيف، ومكاتب المحاماة والترجمة التي تحوّل المستندات العربية بالجملة. يشرح كيف تجهّز الدفعة في أقل من ساعة: تقسيم الملفات المدمجة، وتسمية تُرتَّب صحيحًا على أي جهاز، وقائمة (manifest) بالملفات وصفحاتها تكتبها لك أداة صغيرة. أما جودة المسح نفسها (الدقة ونمط الألوان) فلها دليل مستقل عن فحص صفحات الكتاب الممسوح قبل OCR.

الخطوة ١: مستند واحد في كل ملف

ملف PDF واحد فيه ثلاثون مستندًا يصعّب كل ما بعده: لا تستطيع تسمية كل عقد، ولا تسليمه وحده، ولا معرفة أين انتهى أحدها وبدأ الآخر في الناتج. افصل المستندات أولًا. أداة qpdf المجانية تفعل ذلك من سطر الأوامر دون إعادة ضغط الصور. في المثال التالي الصفحات ١–٣ من الملف الممسوح عقد، و٤–٥ خطاب، ثم ملف ثانٍ من إيصالات ذات صفحة واحدة يُقسَّم صفحةً صفحة:

qpdf --empty --pages scan0001.pdf 1-3 -- 2026-11-08_عقد-إيجار_001.pdf
qpdf --empty --pages scan0001.pdf 4-5 -- 2026-11-08_خطاب_002.pdf
qpdf scan0002.pdf --split-pages fatura-%d.pdf

مع --split-pages تضيف qpdf أرقام الصفحات إلى اسم الملف مسبوقة بأصفار حتى تتساوى أطوالها، فتُرتَّب الأسماء ترتيبًا رقميًا صحيحًا. وإن مسحت مستندًا على وجهين بماسح يأخذ الوجوه في ملف والظهور في ملف آخر، فالخيار --collate يدمجهما بالتناوب: qpdf --collate --empty --pages odd.pdf even.pdf -- all.pdf. وإن خرجت الظهور بترتيب معكوس فاكتب لها النطاق z-1 (من الصفحة الأخيرة إلى الأولى) بعد اسم الملف.

ولا تقسّم ما هو مستند واحد: العقد وملحقه الموقّع، أو الصك وصفحة التصديق، يبقيان معًا إن كان العميل سيطلبهما معًا.

الخطوة ٢: قاعدة تسمية واحدة للدفعة كلها

اتفق على صيغة اسم واحدة قبل أن تبدأ، واكتبها في أول القائمة. صيغة تعمل جيدًا مع المستندات الإدارية:

<التاريخ>_<نوع المستند>_<رقم تسلسلي>.pdf مثل 2026-11-08_عقد-إيجار_001.pdf

  • التاريخ بالصيغة سنة-شهر-يوم حتى تُرتَّب الملفات زمنيًا بالترتيب الأبجدي. إن كان تاريخ المستند هجريًا فاكتبه في عمود من القائمة، لا في الاسم، أو اختر نظامًا واحدًا للدفعة كلها ولا تخلط بينهما.
  • رقم تسلسلي بأصفار بادئة (001، 002 … 010). بدونها يأتي «عقد-10» قبل «عقد-2» في كثير من البرامج والأنظمة.
  • أرقام من نظام واحد في الأسماء. «صك ١٤٤٥-07» يجمع أرقامًا هندية وعربية، فيُرتَّب في غير مكانه ويصعب البحث عنه. الأرقام 0–9 هي الأسلم في أسماء الملفات.
  • لا رموز ممنوعة. تنص Microsoft على أن أسماء الملفات في Windows لا تحتوي على < > : " / \ | ? *، ولا تنتهي بمسافة أو نقطة، ولا تكون أسماء محجوزة مثل CON وPRN وAUX وNUL وCOM1 وLPT1 ولو أضيف إليها امتداد. الملف الذي سُمّي على Mac أو في نظام آخر قد يحمل هذه الرموز ثم يفشل نسخه على Windows.
  • انتبه للرموز الخفية. نسخ اسم من رسالة أو من مستند عربي قد ينقل معه علامة اتجاه غير مرئية (مثل U+200F). الاسم يبدو سليمًا، لكن البحث عنه بكتابته يفشل، وقد يظهر مكررًا.
  • لا تفرّق الأسماء بحالة الأحرف وحدها. «Fatura.pdf» و«fatura.PDF» ملفان على Linux، لكنهما الاسم نفسه على Windows وعادةً على macOS، فيستبدل أحدهما الآخر عند النسخ.

الخطوة ٣: افحص المجلد واكتب القائمة

السكربت التالي (Python 3 مع أداة pdfinfo من حزمة Poppler) يمر على كل ملف في المجلد، ويعدّ صفحات ملفات PDF، ويعدّ كل صورة صفحة واحدة، ويفحص الأسماء بالقواعد أعلاه، ثم يكتب manifest.csv بترميز UTF-8 يفتحه Excel بالعربية سليمة. ويقدّر الرصيد بقاعدة صفحة الأسعار في ٨ نوفمبر ٢٠٢٦: رصيد واحد لكل صفحتين من PDF قابل للبحث أو نص، ورصيد لكل صفحة من المخرجات الأخرى، مع تقريب الكسر إلى الأعلى في كل ملف. راجع الصفحة قبل أن تعتمد التقدير، فالأسعار قد تتغير.

"""فحص دفعة ملفات ممسوحة قبل رفعها للتحويل، وكتابة قائمة بها (manifest.csv).
الاستخدام: python3 check_batch.py مجلد_الدفعة
يحتاج: Python 3 وأداة pdfinfo من Poppler لعدّ صفحات ملفات PDF.
"""
import csv
import math
import re
import subprocess
import sys
import unicodedata
from pathlib import Path

RESERVED_CHARS = set('<>:"/\\|?*')
RESERVED_NAMES = {"CON", "PRN", "AUX", "NUL"} | {f"{p}{i}" for p in ("COM", "LPT") for i in range(1, 10)}
BIDI_CONTROLS = {"‎", "‏", "؜"} | {chr(c) for c in range(0x202A, 0x202F)} | {chr(c) for c in range(0x2066, 0x206A)}
IMAGES = {".jpg", ".jpeg", ".png", ".tif", ".tiff", ".webp", ".heic"}
NUMBER = re.compile(r"[0-9]+|[٠-٩]+|[۰-۹]+")


def pdf_info(path):
    out = subprocess.run(["pdfinfo", str(path)], capture_output=True, text=True)
    if out.returncode != 0:
        return None, "pdfinfo لم يقرأ الملف"
    fields = dict(line.split(":", 1) for line in out.stdout.splitlines() if ":" in line)
    pages = int(fields.get("Pages", "0").strip() or 0)
    encrypted = fields.get("Encrypted", "no").strip().startswith("yes")
    return pages, "محمي بكلمة مرور أو مشفّر" if encrypted else ""


def name_issues(path):
    name, stem = path.name, path.stem
    issues = []
    if any(ch in RESERVED_CHARS or ord(ch) < 32 for ch in name):
        issues.append("حرف ممنوع في أسماء ملفات Windows")
    if stem.upper().split(".")[0] in RESERVED_NAMES:
        issues.append("اسم محجوز في Windows")
    if name != name.rstrip(" ."):
        issues.append("ينتهي بمسافة أو نقطة")
    if any(ch in BIDI_CONTROLS for ch in name):
        issues.append("رمز اتجاه خفي في الاسم")
    if unicodedata.normalize("NFC", name) != name:
        issues.append("الاسم غير مطبّع (NFC)")
    if len({("0" if n[0] in "0123456789" else "٠" if n[0] in "٠١٢٣٤٥٦٧٨٩" else "۰") for n in NUMBER.findall(stem)}) > 1:
        issues.append("أنظمة أرقام مختلطة في الاسم")
    return issues


def series(path):
    nums = list(NUMBER.finditer(path.stem))
    return path.parent, path.stem[: nums[-1].start()] if nums else path.stem


def main(folder):
    files = sorted(p for p in Path(folder).rglob("*") if p.is_file() and p.name != "manifest.csv")
    rows, problems = [], 0
    seen = {}
    widths = {}  # الملفات التي تختلف أسماؤها في الرقم الأخير فقط
    for p in files:
        nums = NUMBER.findall(p.stem)
        if nums:
            widths.setdefault(series(p), set()).add(len(nums[-1]))
    for p in files:
        issues = name_issues(p)
        key = unicodedata.normalize("NFC", str(p.relative_to(folder))).casefold()
        if key in seen:
            issues.append(f"يتكرر مع {seen[key]} في نظام لا يفرّق بين الأحرف")
        seen[key] = p.name
        if NUMBER.search(p.stem) and len(widths[series(p)]) > 1:
            issues.append("أرقام بلا أصفار بادئة: الترتيب الأبجدي سيختلف")
        ext = p.suffix.lower().rstrip(" .")
        if ext == ".pdf":
            pages, note = pdf_info(p)
            if note:
                issues.append(note)
        elif ext in IMAGES:
            pages = 1
        else:
            pages = None
            issues.append("نوع ملف غير متوقع في الدفعة")
        size_mb = p.stat().st_size / 1_000_000
        rows.append({
            "file": str(p.relative_to(folder)),
            "pages": pages if pages is not None else "",
            "size_mb": f"{size_mb:.1f}",
            "credits_pdf_or_text": math.ceil(pages / 2) if pages else "",
            "credits_other": pages or "",
            "issues": "؛ ".join(issues),
        })
        problems += bool(issues)
    with open(Path(folder) / "manifest.csv", "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.DictWriter(f, fieldnames=list(rows[0]))
        writer.writeheader()
        writer.writerows(rows)
    for r in rows:
        if r["issues"]:
            print(f"{r['file']}\t{r['issues']}")
    total_pages = sum(r["pages"] for r in rows if r["pages"] != "")
    pdf_credits = sum(r["credits_pdf_or_text"] for r in rows if r["credits_pdf_or_text"] != "")
    print(f"الملفات: {len(rows)}، فيها ملاحظات: {problems}، الصفحات: {total_pages}، "
          f"تقدير الرصيد لـ PDF قابل للبحث أو نص: {pdf_credits}")


if __name__ == "__main__":
    main(sys.argv[1])

شغّله هكذا: python3 check_batch.py الدفعة. جرّبناه أثناء كتابة هذا الدليل (Python 3.11 وpdfinfo 24.02) على مجلدين صنعناهما بأنفسنا من ملفات PDF فارغة الصفحات، لا على مستندات حقيقية. في المجلد الأول عشرة ملفات بأخطاء التسمية الشائعة، فطبع:

fatura.PDF	يتكرر مع Fatura.pdf في نظام لا يفرّق بين الأحرف
notes.docx	نوع ملف غير متوقع في الدفعة
تقرير?.pdf	حرف ممنوع في أسماء ملفات Windows
صك ١٤٤٥-07.pdf	أنظمة أرقام مختلطة في الاسم
عقد-إيجار-1.pdf	أرقام بلا أصفار بادئة: الترتيب الأبجدي سيختلف
عقد-إيجار-10.pdf	أرقام بلا أصفار بادئة: الترتيب الأبجدي سيختلف
عقد-إيجار-2.pdf	أرقام بلا أصفار بادئة: الترتيب الأبجدي سيختلف
ملاحظات.pdf 	ينتهي بمسافة أو نقطة
‏خطاب.pdf	رمز اتجاه خفي في الاسم
الملفات: 10، فيها ملاحظات: 9، الصفحات: 20، تقدير الرصيد لـ PDF قابل للبحث أو نص: 13

لاحظ ترتيب «عقد-إيجار-10» قبل «عقد-إيجار-2» في الناتج نفسه: هذا ما تفعله الأسماء بلا أصفار بادئة. وفي المجلد الثاني أربعة عقود بالتسمية المقترحة (2026-11-08_عقد-إيجار_01.pdf إلى _04) فطبع «الملفات: 4، فيها ملاحظات: 0، الصفحات: 11، تقدير الرصيد لـ PDF قابل للبحث أو نص: 7».

أضف إلى القائمة بعد ذلك ما تحتاجه أنت: اسم العميل الداخلي أو رقم المعاملة، والتاريخ الهجري، والمخرج المطلوب لكل ملف (وورد أم PDF قابل للبحث أم جدول)، وعمودًا لحالة المراجعة. هذه القائمة هي ما تطابق به كل ناتج بأصله عند التسليم.

الخطوة ٤: قدّر الرصيد وقرر كيف تجمّع الملفات

لأن التقريب يحدث في كل ملف، فملف من صفحة واحدة يستهلك رصيدًا كاملًا حتى في PDF القابل للبحث الذي يحسب صفحتين بالرصيد. أربعون إيصالًا كل منها في ملف مستقل تساوي أربعين رصيدًا بهذه القاعدة، ولو دمجتها في ملف واحد من أربعين صفحة لصارت عشرين. لكن الدمج له ثمن: ستحتاج إلى تقسيم الناتج من جديد إن كان كل إيصال سيُسلَّم أو يُؤرشف وحده. أما في وورد والمخرجات الأخرى فالرصيد صفحة بصفحة، فلا يوفر الدمج شيئًا.

وانتبه لحدود الرفع في صفحة الأسعار: ٢٥ ميغابايت للملف في الخطة المجانية، و٥١٢ ميغابايت للملف و٢٠ ملفًا في المرة الواحدة في باقات الرصيد، وحتى ١٠٠ ملف في المرة في خطة Pro. عمود size_mb في القائمة يخبرك بالملفات التي تتجاوز حدّ خطتك قبل أن يرفضها الرفع.

الخطوة ٥: إن ضغطت الدفعة في ملف ZIP

إذا كنت ستستلم الدفعة أو تسلّمها ملفًا مضغوطًا، فالأسماء العربية قد تصل مشوّهة. مواصفة ZIP تعرّف علامة (البت ١١ في رأس كل ملف) تعني أن الاسم مخزّن بترميز UTF-8، وأداة الضغط التي لا تضعها تخزّن الاسم بترميز الجهاز المحلي، فيقرؤه جهاز آخر حروفًا غير مفهومة. افتح الملف المضغوط على جهاز آخر قبل إرساله، وإن فسدت الأسماء فاستخدم أداة ضغط أخرى، أو اعتمد على القائمة: الرقم التسلسلي اللاتيني في الاسم يبقى مقروءًا حتى لو فسد الجزء العربي.

مثال توضيحي

مثال توضيحي، لا يمثّل جهة بعينها: مكتب خدمات يستلم من شركة عقارية ١٢٠ عقد إيجار ممسوحًا في ستة ملفات PDF كبيرة، والمطلوب نسخة وورد قابلة للتعديل لكل عقد. يقسّم الموظف الملفات بـ qpdf إلى ١٢٠ ملفًا بالتسمية 2026-11-08_عقد-إيجار_001 إلى _120، ويشغّل السكربت فيكتشف ملفين مكررين بحالة أحرف مختلفة واسمًا فيه علامة استفهام، ويضيف إلى القائمة عمودًا لرقم العقد في نظام الشركة. يرفع الملفات على دفعات، وعند التسليم يطابق كل ملف وورد بالسطر نفسه في القائمة، ويرسل القائمة مع الملفات.

قائمة مراجعة قبل الرفع

  • كل ملف فيه مستند واحد، والمستندات التي تُسلَّم معًا بقيت معًا
  • صفحات الوجهين مرتبة، ولا صفحة مكررة أو ناقصة (قارن عدد الصفحات في القائمة بالأصل)
  • الأسماء كلها بصيغة واحدة، والأرقام التسلسلية بأصفار بادئة
  • الأرقام في الأسماء من نظام واحد، ولا رموز ممنوعة ولا مسافة أو نقطة في آخر الاسم
  • لا اسمان يختلفان بحالة الأحرف وحدها، ولا رموز اتجاه خفية
  • الملفات المحمية بكلمة مرور فُتحت أو استُبعدت
  • حجم كل ملف ضمن حدّ خطتك، وعدد الملفات في كل دفعة ضمن الحد
  • القائمة فيها المخرج المطلوب لكل ملف، والرصيد المتوقع راجعته على صفحة الأسعار
  • فتحت الملف المضغوط (إن وجد) على جهاز آخر والأسماء العربية سليمة

أين يدخل ScribeTools

يحوّل ScribeTools المستندات العربية الممسوحة أو المصوّرة إلى وورد قابل للتعديل أو PDF قابل للبحث أو نص أو جداول بيانات، ويعمل مع الإنجليزية والأردية وتقريبًا مع أي لغة أخرى. ويمكنك رفع عدة ملفات في المرة الواحدة ضمن حدود خطتك المذكورة أعلاه. وللصكوك تحديدًا صفحة عن تحويل الصك إلى وورد. وكما مع أي أداة تحويل، راجع الناتج على الأصل قبل الاعتماد عليه، ولا سيما الأسماء والمبالغ والتواريخ؛ ودليل مراجعة العقد المحوّل إلى وورد يشرح ذلك خطوة خطوة.

حدود هذا الدليل

  • السكربت يفحص الأسماء وعدد الصفحات فقط، لا جودة المسح ولا محتوى المستندات، ولا يعرف إن كان الملف يجمع مستندين.
  • يعدّ كل صورة صفحة واحدة، ولا يقرأ صور TIFF متعددة الصفحات صفحاتٍ منفصلة. وكشف الملفات المحمية يعتمد على سطر Encrypted في ناتج pdfinfo، ولم نجرّبه على ملف محمي.
  • تقدير الرصيد مبني على قاعدة صفحة الأسعار في تاريخ كتابة الدليل، والرصيد الفعلي هو ما تعرضه حسابك. وبعض المحركات أو الخيارات قد تحسب بطريقة مختلفة.
  • قواعد الأسماء هنا هي قواعد Windows، لأنها الأشد عادةً. أنظمة الأرشفة وإدارة المستندات قد تفرض قواعد إضافية على الطول والرموز، فارجع إلى توثيقها.
  • لم نجرّب سلوك أدوات ضغط بعينها مع الأسماء العربية؛ الفقرة عن ZIP تشرح ما تنص عليه المواصفة، فافحص الملف المضغوط بنفسك.

المصادر

  1. Microsoft Learn: Naming Files, Paths, and Namespaces
  2. qpdf documentation: Running qpdf (--pages, --split-pages, --collate)
  3. Debian manpages: pdfinfo(1), Poppler utilities
  4. PKWARE: ZIP File Format Specification (APPNOTE.TXT), general purpose bit 11
دفعة مستندات عربية ممسوحة: قسّمها وسمّها وعدّ صفحاتها قبل التحويل | ScribeTools