النص القرآني
نحفظ النص القرآني كما نُقل من مصدره دون تعديل، وهذه قواعد تخزينه وترميزه وتقسيمه وعرضه واختباره.
القاعدة الأساسية: نحفظ النص القرآني كما هو دون تعديل، ونوثق هويته ونتحقق منه، ونبني كل ما يعتمد عليه في طبقات مشتقة دون تغيير الأصل.
والسبب ليس تقنيًا، فالقرآن كلام الله، وقد تكفل سبحانه بحفظه فقال: ﴿إِنَّا نَحۡنُ نَزَّلۡنَا ٱلذِّكۡرَ وَإِنَّا لَهُۥ لَحَٰفِظُونَ﴾ (15:9). والقارئ الذي يفتح تطبيقك يقرأ المصحف، فإذا وجد خللًا على الشاشة نسبه إلى المصحف وليس إلى الكود.
الأرقام الواردة هنا مأخوذة من فحص واحد لنص عثماني منشور برواية حفص عن عاصم
وفق العد الكوفي، ويعيد tools/audit_text.py حساب كل رقم منها من أي ملف نصي.
وفي examples/ مخطط قاعدة بيانات وواجهة API وسجلات بيانات واختبارات
تلتزم بهذه القواعد وتستخدم الأسماء المعتمدة.
1. المصدر
النص أصل منقول، ولست مؤلفه ولا محرره.
1.1 وثّق هوية النص قبل استيراده بذكر الطبعة والرواية ونظام عد الآي والإصدار وبصمة الملف.
يمثل examples/data/mushaf_edition.json هذا السجل.
يفحصه: فحص للمخطط يتأكد من أن كل جدول يحتوي على نص يتضمن مفتاحًا أجنبيًا يشير إلى الطبعة التي أُخذ منها (examples/schema/).
1.2 أبقِ الأصل دون تعديل، واجعل كل معالجة تنتج نسخة أو طبقة جديدة مع إمكان إعادة إنشاء الأصل من المصدر المنشور.
يفحصه: بصمة متوقعة لكل إصدار معتمد، ويتوقف البناء إذا اختلفت البصمة المحسوبة عنها.
1.3 أعد اشتقاق كل ما يمكن اشتقاقه، واحفظ ما نُقل من المصحف كما نُقل مع ذكر مصدره.
يفحصه: تسجل كل طبقة مشتقة بصمة النص الذي بُنيت عليه، وتقارن إحدى خطوات البناء هذه البصمة ببصمة المصدر الحالي.
1.4 أدرج النص القرآني بالإحالة إلى موضعه، أو انسخه من إصدار منشور من dataset مع ذكر الإصدار، دون كتابته يدويًا في الكود أو الشرح أو الاختبارات أو التوثيق.
يظن القارئ أن النص العربي المؤقت الذي تضعه مكان القرآن نص قرآني، والآية المكتوبة يدويًا لا تحمل هوية تتيح التحقق منها.
يفحصه: فحص lint المذكور في القاعدة 7.5، مع تشغيله على ملفات المصدر والتوثيق أيضًا، والمراجعة.
2. الترميز والمحارف
اعتمد في المقارنة على قيم codepoint بدل الأشكال، ولا تسمح لأي مكتبة «بتنظيف» النص.
2.1 احفظ النص بترميز Unicode واحد معلن، من ملف المصدر إلى قاعدة البيانات وواجهة API والواجهة الأمامية.
يفحصه: اختبار يتأكد من أن القراءة والتخزين والاسترجاع تعيد قيم codepoint نفسها في كل مرحلة.
2.2 لا تشغّل normalize على الأصل بأي من الصيغ NFC أو NFD أو NFKC أو NFKD.
غيّر تشغيل NFC على مصحف كامل 5,771 آية من 6,236، بطريقتين:
يعيد الترتيب: ل + ّ (U+0651) + َ (U+064E) → ل + َ (U+064E) + ّ (U+0651)
ويدمج: ا (U+0627) + ٓ (U+0653) → آ (U+0622) في ٱلضَّآلِّينَ (1:7)
وأثر NFKC أشد، لأنه يستبدل المحرف بما تعده Unicode مكافئًا له، فيضيع الأصل:
ﷲ (U+FDF2) → الله
ﷺ (U+FDFA) → صلى الله عليه وسلم
يفحصه: قاعدة lint تمنع normalize في مسار النص من الملف إلى القارئ.
2.3 أبقِ الأصل دون حذف المسافات من طرفيه أو إزالة «المحارف الغريبة» أو دمج المسافات أو استبدال المحارف بأخرى تشبهها أو حذف العلامات، وأنشئ نسخة منفصلة إذا احتاج البحث إليها.
يفحصه: اختبار البصمة، واختبار يتأكد من أن حقل النص لا يحمل أرقامًا أو حروفًا لاتينية أو HTML.
2.4 أنشئ قائمة بالمحارف المسموح بها من محارف النص، وأوقف البناء عند ظهور أي محرف خارجها.
يتكون النص المفحوص من 70 محرفًا مختلفًا، ومع ذلك بدأت الآية 1:1 بمحرف U+FEFF أُضيف عند التصدير ولا يظهر على الشاشة.
python3 tools/audit_text.py <file> --format tanzil # يحسب قائمة المحارف المسموح بها وكل رقم في هذه الصفحة
يفحصه: التحقق عند إدخال البيانات من التزام النص بقائمة المحارف المسموح بها، ورفض الاستيراد عند وجود محرف خارجها.
2.5 طول السلسلة لا يساوي عدد الحروف المرئية، فلا تعتمد عليه لتحديد الحجم أو القص أو العد.
في النص المفحوص 1,360,018 بايت و721,236 قيمة codepoint، وعدد الحروف المرئية أقل من كل منهما.
يفحصه: اختبار يقص آية عند كل طول ويتأكد من أنه لا يفصل حرفًا عن حركاته.
3. الرسم والضبط والخطوط
نفصل بين 3 طبقات: هيكل الحروف وعلامات الضبط والخط، ويتكون النص من الطبقتين الأوليين فقط.
3.1 افصل الرسم، وهو هيكل الحروف، عن الضبط، وهو علامات الحركات، وعن الخط الذي يرسمهما، فالصورة المعروضة تختلف عن النص المخزن.
يفحصه: مخطط يخزن الخط والتخطيط والنص المرمّز بالخط في datasets منفصلة مرتبطة بالطبعة.
3.2 الشكل الذي يرسمه الخط ليس محرفًا، ولا يُعد الخط أو محرك تشكيل النص مرجعًا معتمدًا.
يفحصه: اختبار القراءة والتخزين والاسترجاع الذي يقارن قيم codepoint.
3.3 تحقق من وجود محارف النص في جدول cmap للخط قبل اعتماده، لأن المحرف الذي لا يقابله شكل في الخط يختفي أو يظهر مربعًا دون أن ينتبه القارئ.
يفحصه: اختبار تغطية cmap لكل إصدار من الخط، ولقطة لآية فيها شدة وحركة وعلامة وقف صغيرة.
3.4 علامات الطبعة ليست من نص الآية، ومنها رأس الآية (U+06DD) وربع الحزب ۞ (U+06DE) والسجدة ۩ (U+06E9).
في النص المفحوص 199 آية تبدأ بـ۞ و15 تحتوي على ۩ داخل حقل النص، فيحسب من يستخدم هذا الحقل العلامات ضمن الكلمات ويشملها البحث.
يفحصه: اختبار يتأكد من خلو حقل النص من علامات الطبعة.
3.5 أبقِ الأصل خاليًا من وسوم العرض وHTML.
يفحصه: اختبار يتأكد من أن حقل النص لا يحمل إلا نصًا.
4. المواضع ووحدات التقسيم والإزاحات
النص نفسه لا يكون مفتاحًا أبدًا.
4.1 خصص معرفات ثابتة للسور والآيات والكلمات، ولا تستخدم النص نفسه مفتاحًا.
يفحصه: فحص للمخطط يتأكد من أن لكل صف يحتوي على نص معرفًا غير مشتق من النص.
4.2 اربط رقم الآية بالسورة وبنظام عد الآي، لأن رقم الآية وحده لا يحدد موضعًا.
يفحصه: فحص للمخطط يتأكد من ذكر السورة ورقم الآية ونظام عد الآي في كل صف يحتوي على نص وفي كل نقطة API تعيده (examples/schema/).
4.3 عرّف طريقة التقسيم صراحةً، لأن الكلمة البرمجية لا تطابق الكلمة في المصحف دائمًا.
يَٰٓأَيُّهَا (2:21) وحدة واحدة عند التقسيم بالمسافات، وكلمتان في اللغة
مَالِ هَٰذَا (25:7) وحدتان عند التقسيم بالمسافات، وكلمة واحدة في اللغة
ينتج عن تقسيم النص المفحوص بالمسافات 82,456 وحدة، وهذا لا يساوي عدد كلمات المصحف، ومن هذه الوحدات 199 علامة ربع حزب.
يفحصه: ترقيم إصدارات التقسيم مع dataset، واختبار مرجعي يحفظ عدد وحدات التقسيم في كل إصدار.
4.4 اذكر مع كل إزاحة النص الذي حُسبت عليه والترميز الذي حُسبت به ووحدة القياس.
يفحصه: فحص للمخطط يتحقق من بيانات كل إزاحة مخزنة ويوقف البناء عند غياب أي حقل.
5. الروايات والطبعات وأنظمة العد
لا يحل نص محل آخر مهما تشابها في الشكل.
5.1 لا تخلط بيانات روايات أو طبعات مختلفة، ولو بدا النص متطابقًا.
يفحصه: مفتاح أجنبي يشير إلى الطبعة في كل جدول للنص، مع اشتراط ربط صريح بين الطبعات عند الاستعلام عنها معًا بدل الربط الافتراضي.
5.2 لا تحل أنظمة عد الآي بعضها محل بعض، ولا تتطابق حدود الآيات بينها.
عدد الآيات في العد الكوفي (kufi) هو 6,236، ويختلف العدد باختلاف نظام العد، ويخصص standards/terminology/registries/ayah_numbering.tsv صفًا لكل نظام مع مجموع آياته.
يفحصه: اختبار أعداد السور والآيات وفق الطبعة ونظام عد الآي اللذين تنشرهما، دون الاعتماد على أعداد ثابتة من الذاكرة.
5.3 خصص حقلًا مستقلًا للبسملة، لأن احتسابها آية يعتمد على نظام عد الآي، ولأن سورة التوبة لا تبدأ بالبسملة.
يفحصه: فحص للمخطط يتأكد من أن البسملة ليست داخل حقل نص الآية الأولى.
6. الطبقات المشتقة
كل ما يعتمد على النص يسجل النص الذي بُني عليه.
6.1 تخزن كل طبقة مشتقة بصمة النص الذي اشتُقت منه، من التجويد والتقسيم إلى الترجمة والتفسير وفهارس البحث.
يفحصه: خطوة في البناء تقارن البصمة المسجلة في كل طبقة ببصمة المصدر الحالي.
6.2 سجّل التجويد والوقف والسجدة والتلوين بوصفها تعليقات مرتبطة بمواضع في النص دون تعديله، إلا إذا كانت جزءًا من الطبعة التي أُخذ منها النص.
يفحصه: اختبار يتأكد من أن إضافة التعليقات لا تغير بصمة حقل النص.
6.3 اشتق حقل البحث بدالة حتمية تحذف الحركات والتطويل وتوحد صور الحروف، ولا تعرضه للقارئ أو تخزنه مكان النص.
يفحصه: حالات مرجعية للاشتقاق، واختبار يتأكد من أن الفهرس بُني بالإصدار الحالي من الدالة.
6.4 لا تستخدم تعبيرًا نمطيًا غير مختبر في أي معالجة لهذا النص.
يفحصه: اختبارا البصمة والقراءة والتخزين والاسترجاع بعد كل تحويل.
7. العرض
غيّر التخطيط وأبقِ النص كما هو.
7.1 لا تقص النص ليناسب التخطيط، ولا تضع علامة الحذف داخل النص القرآني.
يفحصه: اختبار لقطة يتأكد من أن الآية المعروضة تطابق النص المخزن.
7.2 بيّن أن المقتطف جزء من الآية وأرفق مرجعه ورابطه، حتى لا يفهم القارئ أنه الآية كاملة.
الوقف على آخر (107:4) دون وصلها بـ(107:5) يقلب المعنى، فحد الكلمة ليس بالضرورة موضع وقف جائزًا.
يفحصه: اختبار لقطة يتأكد من أن النص المقتطع يتضمن مرجعه وعلامة تدل على اقتطاعه، ولا يطابق الآية كاملة.
7.3 إذا اضطررت إلى قص السلسلة نفسها لإشعار أو og:description، فاقطع عند حدود grapheme clusters باستخدام Intl.Segmenter، ولا تستخدم slice.
يقص examples/tests/truncation.js آية عند كل طول ويتأكد من أنه لا يفصل حرفًا عن حركاته.
يفحصه: اختبار القص.
7.4 لا تعرض آية لم يكتمل تحميلها، واعرض مكانها مؤشر تحميل أو رسالة خطأ.
يفحصه: اختبار للمكوّن عند تأخر جلب البيانات وعند فشله.
7.5 لا تضع النص في placeholder أو بيانات الاختبار أو سجلات الأخطاء أو أسماء الملفات أو الروابط، ويكفي في هذه المواضع مرجع مثل 114:1.
يفحصه: فحص lint يبحث عن المحارف القرآنية في بيانات الاختبار والسجلات وأسماء الملفات والروابط.
8. الأخطاء والتصحيحات
أوقف العملية برسالة خطأ واضحة، وتجنب الإصلاح اليدوي، ووثّق كل تصحيح.
8.1 أبلغ الناشر بالخطأ المحتمل ووثّق التصحيح، ولا تصحح الأصل يدويًا دون إعلان.
يعرض examples/data/errata.json تصحيحًا بالشكل الذي تحدده صفحة الإصدارات.
يفحصه: سجل التصحيحات وقاعدة مراجعة ترفض أي تغيير في النص ليس له مدخل في السجل.
8.2 فرّق بين خطأ المصدر وخطأ النقل وخطأ العرض، لأن لكل واحد منها موضع إصلاح مختلفًا.
يفحصه: حقل kind في كل مدخل في سجل التصحيحات، ويأخذ إحدى القيم الـ3.
8.3 اعرض تغييرات النص بطريقة تتيح مراجعتها محرفًا محرفًا، وتعامل مع أي ترحيل يغير عمود النص بوصفه تغييرًا عالي الخطورة.
يفحصه: ينبّه CI إلى أي ترحيل يغير عمود نص، ويشترط عرض الفرق على مستوى codepoint ومراجعًا ثانيًا.
8.4 إذا لم يطابق النص ما تتوقعه، فأوقف العملية برسالة خطأ واضحة، ولا تصلحه تلقائيًا أو تخمّن النص أو البيانات الوصفية المفقودة.
يفحصه: اختبار استيراد ببيانات تالفة يتأكد من فشل العملية دون تغيير أي شيء.
8.5 أعلن للقارئ كل تحديث لبيانات المصحف، فكل إصدار جديد هو dataset جديد موثق المصدر والتغييرات.
يفحصه: اختبارات الإصدار في صفحة الإصدارات.
المصدر: quran-ws/docs، عند d1d6be33be9d. ما لم يُوسم «معتمد» فهو مقترح للنقاش، ولا يُبنى عليه بعد.