ورقة بيضاء تقنية

هندسة مزامنة الآيات
رواية ورش مقابل رواية حفص
Verse Synchronization Architecture
Warsh Narration vs. Hafs Narration

توثيق تقني شامل لإشكالية الاختلاف في ترقيم الآيات وحلها البرمجي، موجّه للمطورين العاملين على تطبيقات القرآن الكريم. A comprehensive technical document on the verse-numbering discrepancy and its algorithmic solution, aimed at developers building Quran applications.

مصحف موري 2026 عربي / English
عند بناء تطبيقات قرآنية تدعم تعدد الروايات، يصطدم المطورون بتحدٍّ هندسي خفي: الواجهات المصممة لعرض مصحف ورش تعتمد العدّ المدني الأخير (6,214 آية)، بينما تعتمد غالبية واجهات برمجة التطبيقات المفتوحة — الخاصة بالتفاسير والترجمات والصوتيات — على العدّ الكوفي (6,236 آية) المرتبط برواية حفص.

١ الفجوة التقنية في رقمنة المصحف

هذا التباين الرقمي ليس خللاً برمجياً، بل هو اختلاف علمي موثق تاريخياً في مدارس إحصاء فواصل الآيات — أين تنتهي الآية وتبدأ الأخرى. غير أن أثره من الناحية البرمجية البحتة يؤدي إلى كسر المزامنة؛ فاستدعاء الآية بناءً على رقمها الظاهر في واجهة ورش سيجلب بيانات غير متطابقة من قاعدة البيانات المصممة على هيكل حفص، مما ينتج عنه تفاوت (Décalage) في النص المقروء، التفسير المعروض، أو المقطع الصوتي.

تهدف هذه الورقة إلى سدّ واحدة من أندر الفجوات التوثيقية على الإنترنت: تحليل دقيق وحل خوارزمي يوثّق نقاط الاختلاف آية بآية بصيغة قابلة للبرمجة. تم استخراج هذه البيانات لبناء طبقة وسيطة (Middleware) تعالج حالات الدمج، التقسيم، والإزاحة ديناميكياً.

6,214 آية — ورش (مدني أخير)
6,236 آية — حفص (كوفي)
22 آية فرق، موزّعة على 50 سورة
56 سورة تحتاج خريطة تحويل

٢ المفاهيم الأساسية: التشريح الهندسي للاختلافات

تخيّل النص القرآني كـ"شريط متصل" من الكلمات، وأن أرقام الآيات مجرد فواصل (Markers) نضعها عليه. الاختلاف بين الروايتين ليس في طول الشريط، بل في أماكن وضع هذه الفواصل. ينتج عن ذلك أربع ظواهر برمجية رئيسية:

🔗

الدمج (Merge)

ورش يعتبر مقطعين آية واحدة، بينما حفص يعدّهما آيتين. يُقلل العدد الإجمالي للآيات.

✂️

التقسيم (Split)

حفص يعدّ مقطعاً طويلاً آية واحدة، بينما ورش يقسمه إلى آيتين. يزيد العدد الإجمالي.

↔️

الإزاحة (Offset)

الفارق الرياضي المستمر بين رقم الآية في ورش ورقمها المقابل في حفص، ينشأ من الدمج أو التقسيم.

🔀

التحول الهيكلي المعقد

سورة تحتوي دمجاً وتقسيماً في آنٍ واحد، مما يُبطل الاعتماد على إزاحة ثابتة.

2.1 ظاهرة الدمج — مثال سورة البقرة

في قاعدة البيانات (حفص): الآية 1 هي الم والآية 2 هي ذَلِكَ الْكِتَابُ لاَ رَيْبَ فِيهِ...

في الواجهة (ورش): الآية 1 المدموجة هي الم ذَلِكَ الْكِتَابُ لاَ رَيْبَ فِيهِ...

الأثر البرمجي: عند الضغط على الآية (1) في مصحف ورش، يجب إرسال طلب للـ API لجلب تفسير الآيتين (1 و2) معاً ودمجهما.

2.2 ظاهرة التقسيم — مثال سورة محمد

في قاعدة البيانات (حفص): الآية 4 طويلة تبدأ بـ فَإِذَا لَقِيتُمُ الَّذِينَ كَفَرُوا...

في الواجهة (ورش): مقسّمة إلى الآية 4 والآية 5.

الأثر البرمجي: الآيتان (4) و(5) في ورش تجلبان كلتاهما تفسير الآية (4) من حفص.

2.3 مفهوم الإزاحة

الإزاحة هي الفارق الرياضي المستمر: رقم حفص = رقم ورش + الإزاحة

  • offset = +1: حفص يسبق ورش بآية (بسبب دمج سابق)
  • offset = -1: حفص يتأخر عن ورش بآية (بسبب تقسيم سابق)
  • الإزاحة تتغير عند كل نقطة دمج أو تقسيم

2.4 التحولات الهيكلية المعقدة — مثال سورة آل عمران

التحدي: سورة آل عمران لديها 200 آية في كلتا الروايتين! المطور قد يفترض التطابق التام (offset = 0) ويتجاهل برمجتها، وهنا تقع الكارثة.

ما يحدث فعلياً: ورش دمج الفاتحة الم في بداية السورة مما أحدث إزاحة (+1)، ثم قسّم الآية 92 في حفص إلى آيتين (91 و92) في ورش، ليُلغي أثر الدمج الأول. من الآية 93 حتى 200 يعود التطابق. لذا لا يمكن حل هذا بمعادلة بسيطة، بل بـخرائط نطاقات (Range Maps) داخل الكود.

When building Quranic apps that support multiple recitation narrations, developers face a hidden engineering challenge: frontends displaying the Warsh Mushaf rely on the Last Madani Count (6,214 verses), while most open APIs for commentaries, translations, and audio use the Kufi Count (6,236 verses) associated with the Hafs narration.

1 The Technical Gap in Quran Digitization

This numerical discrepancy is not a programming bug — it is a historically documented scholarly difference in verse-boundary counting schools (where one verse ends and another begins). But from a purely technical standpoint, it breaks synchronization: requesting a verse by its displayed number in the Warsh UI will fetch mismatched data from a Hafs-based database, causing discrepancies in the displayed text, commentary, or audio segment.

This white paper fills one of the rarest documentation gaps online: a precise analysis and algorithmic solution that documents verse-by-verse divergence points in a programmatic mapping format, used to build a middleware layer that dynamically handles Merge, Split, and Offset cases.

6,214 verses — Warsh (Last Madani)
6,236 verses — Hafs (Kufi)
22 verse difference, across 50 surahs
56 surahs needing a mapping table

2 Core Concepts: Engineering Anatomy of the Divergence

Think of the Quranic text as a continuous tape of words, and verse numbers as markers placed on it. The difference between the two narrations is not in the length of the tape, but in where those markers are placed. This produces four main technical phenomena:

🔗

Merge

Warsh counts two segments as one verse, while Hafs counts them as two. Reduces total verse count.

✂️

Split

Hafs counts a long passage as one verse, while Warsh divides it into two. Increases total verse count.

↔️

Offset

The running mathematical difference between a verse number in Warsh and its counterpart in Hafs, arising from merges or splits.

🔀

Complex Structural Shift

A surah containing both merges and splits simultaneously, making a fixed offset approach impossible.

2.1 Merge — Al-Baqarah Example

In the database (Hafs): verse 1 is Alif Lam Meem, verse 2 is That is the Book...

In the UI (Warsh): verse 1 is merged: Alif Lam Meem. That is the Book...

Programmatic effect: Tapping verse 1 in the Warsh UI must fetch the commentary for both Hafs verses 1 and 2 and combine them.

2.2 Split — Surah Muhammad Example

In the database (Hafs): verse 4 is a long verse beginning So when you meet those who disbelieve...

In the Warsh UI: split into verse 4 and verse 5.

Programmatic effect: Both Warsh verse 4 and verse 5 must fetch Hafs verse 4's commentary.

2.3 The Offset

The offset is the running mathematical difference: hafs_aya = warsh_aya + offset

  • offset = +1: Hafs is ahead by one verse (due to a prior merge)
  • offset = -1: Hafs is behind by one verse (due to a prior split)
  • The offset changes at every merge or split point

2.4 Complex Structural Shifts — Al Imran Example

The trap: Surah Al Imran has 200 verses in both narrations. A developer may assume full alignment (offset = 0) and skip programming it — that's where things break silently.

What actually happens: Warsh merges the opening Alif Lam Meem, creating offset +1; then later splits Hafs verse 92 into Warsh verses 91+92, cancelling the earlier merge. From verse 93 to 200 they realign. This requires building Range Maps in code — not a simple formula.

هذه الوثيقة تشرح الحل التقني المُطبَّق في التطبيق لمعالجة الاختلاف في ترقيم الآيات بين رواية ورش (الواجهة) ورواية حفص (مصدر التفاسير والبيانات). الهدف موجّه أساساً للمطورين العاملين على تطبيقات القرآن الكريم.

١ المقدمة والإشكالية

لماذا هذا الموضوع نادر على الإنترنت؟

اختلاف عدد الآيات بين رواية ورش ورواية حفص موثّق في كتب علم القراءات، لكن لا يوجد — في حدود ما بحثنا — مصدر رقمي يوثّق نقاط الانتقال بدقة آية بآية بصيغة قابلة للبرمجة. معظم المصادر تذكر فقط أن "العدّ الكوفي يختلف عن العدّ المدني" دون تحديد السورة والآية بالضبط.

السياق التقني

التطبيق يعرض مصحف ورش (رواية ورش عن نافع) ويدعم تشغيل الصوت وعرض التفسير. مصدر التفسير هو API خارجي يستخدم ترقيم حفص (العدّ الكوفي). عند الضغط على آية في وضع ورش، كانت الدالة ترسل رقم الآية كما هو إلى الـ API، مما يُجلب بيانات آية مختلفة في السور التي يختلف فيها الترقيم.

الروايةالعدّالاستخدام
ورش (نافع — مدني أخير)6,214 آيةقاعدة بيانات الواجهة
حفص (عاصم — كوفي)6,236 آيةمصدر التفاسير والبيانات
الفرق22 آيةموزّعة على 50 سورة

٢ المفاهيم الأساسية

2.1 أنواع الاختلاف

الدمج (Merge) — ورش يجمع ما فرّقه حفص:

H[n] + H[n+1]  →  W[k]        (ورش له آية أقل)

التقسيم (Split) — ورش يفرّق ما جمعه حفص:

H[n]  →  W[k] + W[k+1]        (ورش له آية أكثر)

الفواتح (Fawatih Merge): الحروف المقطعة كـ"الم" و"حم" تُعدّ في ورش آية مع ما بعدها، بينما حفص يعدّها مستقلة. هذا النوع الأكثر تكراراً ويظهر في 16 سورة.

2.2 مفهوم الإزاحة (Offset)

الإزاحة offset هي الفرق بين رقم الآية في ورش ورقمها المقابل في حفص:

W[n]  =  H[n + offset]
  • offset = 0: تطابق مباشر
  • offset = +1: ورش دمج سابقاً (حفص أمامه بآية)
  • offset = -1: ورش قسّم سابقاً (حفص خلفه بآية)

الإزاحة تتغير عند كل نقطة دمج أو تقسيم وتبقى ثابتة حتى النقطة التالية.

٣ الحل التقني

3.1 هيكل الكود

// src/utils/tafsir.ts

const WARSH_EXCEPTIONS: Record<number, ExceptionType> = { ... }

export function warshToHafsAyahs(sura: number, warshAya: number): number[]

الدالة warshToHafsAyahs تُرجع مصفوفة (وليس رقماً واحداً) لأن آية ورش الواحدة قد تقابل آيتين في حفص عند الدمج.

3.2 أنواع الاستثناءات

type ExceptionType =
  | { type: "manual"; map: Record<number, number[]> }
  | { type: "offset"; mergeAtWarshAyah: number; hafsTargetsToMerge: number[]; offsetForSubsequent: number }
  | { type: "advanced"; explicit_map: Record<number, number[]>; ranges: Range[] }
النوعالاستخدام
manualالفاتحة فقط — خريطة يدوية كاملة
offsetدمج فاتحة بسيط عند W1، إزاحة +1 لكل ما بعدها
advancedأي سورة بها انتقالات متعددة أو داخلية

3.3 مثال على advanced — سورة مريم

// مريم (19): W=99, H=98 — دمج عند البداية + تقسيمان داخليان
19: {
  type: "advanced",
  explicit_map: {
    1: [1, 2],   // W1 = H1+H2  (دمج الفاتحة كهيعص)
    40: [41],    // W40 = H41   (أول نصف التقسيم الأول)
    41: [41],    // W41 = H41   (ثاني نصف التقسيم الأول)
    75: [75],    // W75 = H75   (أول نصف التقسيم الثاني)
    76: [75],    // W76 = H75   (ثاني نصف التقسيم الثاني)
  },
  ranges: [
    { warshStart: 2,  warshEnd: 39, hafsOffset:  1 },
    { warshStart: 42, warshEnd: 74, hafsOffset:  0 },
    { warshStart: 77, warshEnd: 99, hafsOffset: -1 },
  ],
}

٤ جدول السور الكاملة

الفئة 0 — خريطة يدوية

السورةWHالتفصيل
الفاتحة (1)77البسملة لا تُعدّ آية؛ H7 مقسّم W6+W7

الفئة 1 — دمج فاتحة بسيط (offset +1 ثابت)

السورةWHالفرقالفاتحة المدموجة
البقرة (2)285286+1الم + ذلك الكتاب
طه (20)134135+1طه + ما أنزلنا
الشعراء (26)226227+1طسم + تلك آيات
الروم (30)5960+1الم + غلبت الروم
لقمان (31)3334+1الم + تلك آيات
يس (36)8283+1يس + والقرآن
غافر (40)8485+1حم + تنزيل
فصلت (41)5354+1حم + كتاب
الجاثية (45)3637+1حم + تنزيل
الأحقاف (46)3435+1حم + تنزيل
القارعة (101)1011+1القارعة + ما القارعة

الفئة 1b — دمج فاتحة + تقسيم داخلي (صافي = 0)

السورةWHنقطة الدمجنقطة التقسيم
آل عمران (3)200200W1=[H1,H2]H92→W91+W92
الأعراف (7)206206W1=[H1,H2]H137→W136+W137
القصص (28)8888W1=[H1,H2]H23→W22+W23
العنكبوت (29)6969W1=[H1,H2]H29→W28+W29
السجدة (32)3030W1=[H1,H2]H10→W9+W10
الزخرف (43)8989W1=[H1,H2]H52→W51+W52

الفئة 2 — دمجات متعددة للفواتح

السورةWHالفرقالتفاصيل
ص (38)8688+2W1=[H1,H2]؛ W83=[H84,H85]
الشورى (42)5053+3W1=[H1,H2,H3] (حم+عسق)؛ W30=[H32,H33]
الدخان (44)5659+3W1=[H1,H2]؛ W33=[H34,H35]؛ W42=[H44,H45]

الفئة 3 — تقسيم أو دمج وحيد (diff=±1)

السورةWHالفرقالتفصيل
مريم (19)9998-1W1=[H1,H2]؛ H41→W40+W41؛ H75→W75+W76
محمد (47)3938-1H4→W4+W5
العلق (96)2019-1H15→W15+W16
الزلزلة (99)98-1H6→W6+W7
قريش (106)54-1H4→W4+W5
الماعون (107)67+1W6=[H6,H7]

الفئة 4 — دمج وحيد داخلي (diff=+1)

السورةWHنقطة الدمج
النساء (4)175176W44=[H44,H45]
الإسراء (17)110111W107=[H107,H108]
الأنبياء (21)111112W73=[H73,H74]
النجم (53)6162W28=[H28,H29]
الحديد (57)2829W13=[H13,H14]
المجادلة (58)2122W20=[H20,H21]
المدثر (74)5556W40=[H40,H41]
القيامة (75)3940W18=[H18,H19]
النازعات (79)4546W37=[H37,H38]

الفئة 4b — تقسيم وحيد داخلي (diff=-1)

السورةWHنقطة التقسيم
المؤمنون (23)119118H47→W47+W48
فاطر (35)4645H43→W43+W44
الملك (67)3130H10→W10+W11
الأنفال (8)7675H47→W47+W48
التوبة (9)130129H72→W72+W73

الفئة 5 — تحولات هيكلية متعددة

السورةWHالفرقالتفاصيل
المائدة (5)122120-2H1→W1+W2؛ H15→W16+W17
الأنعام (6)167165-2H1→W1+W2؛ W67=[H66,H67]؛ H73→W73+W74؛ H161→W162+W163
هود (11)121123+2W54=[H54,H55]؛ H86→W85+W86؛ W118=[H118,H119]؛ W120=[H121,H122]
الرعد (13)4443-1H5→W5+W6؛ H16→W17+W18؛ W25=[H23,H24]
إبراهيم (14)5452-2H1→W1+W2؛ H5→W6+W7؛ H9→W11+W12؛ W22=[H19,H20]
الكهف (18)105110+5W35=[H35,H36]؛ W84=[H85,H86]؛ W87=[H89,H90]؛ W89=[H92,H93]؛ W99=[H103,H104]
الحج (22)7678+2W18=[H18,H19]؛ W19=[H20,H21]
النور (24)6264+2W36=[H36,H37]؛ W42=[H43,H44]
النمل (27)9593-2H33→W33+W34؛ H44→W45+W46
الزمر (39)7275+3H3→W3+W4؛ W12=[H11,H12]؛ W14=[H14,H15]؛ W35=[H36,H37]؛ W37=[H39,H40]
الطور (52)4749+2W1=[H1,H2]؛ W12=[H13,H14]
الرحمن (55)7778+1W1=[H1,H2]
الواقعة (56)9996-3H8→W8+W9؛ H9→W10+W11؛ H18→W20+W21؛ W25=[H22,H23]؛ H41→W43+W44
نوح (71)3028-2H24→W24+W25؛ H25→W26+W27
المزمل (73)1820+2W1=[H1,H2]؛ W16=[H17,H18]
الفجر (89)3230-2H15→W15+W16؛ H16→W17+W18؛ H25→W27+W28؛ W32=[H29,H30]

٥ منهجية التحقق

5.1 التحقق من آخر آية

الاختبار الأكثر موثوقية: التحقق من أن warshToHafsAyahs(W_last) == H_last لكل سورة:

mapped = warsh_to_hafs(exc, w_last)
assert mapped[-1] == h_last  # يجب أن تتطابق

نجح لجميع 56 سورة في WARSH_EXCEPTIONS.

5.2 التحقق من المحتوى

مقارنة نص الآيات الفعلي عند نقاط الانتقال باستخدام تطبيع عربي:

def normalize(text):
    text = re.sub(r'[أإآٱ]', 'ا', text)
    text = text.replace('ى','ي').replace('ے','ي')
    text = re.sub(r'[^\u0621-\u064A\s]', '', text)  # حروف عربية فقط
تنبيه: مقارنة المحتوى بين ورش وحفص تنتج تطابقات وهمية (false positives) بسبب اختلاف الرسم العثماني بين الروايتين. هذا الاختلاف مقصود وموثّق، ليس تصحيفاً. لذا استُخدم التحقق من المحتوى فقط لتأكيد نقاط الانتقال وليس للمقارنة العامة.

٦ الاستدراكات والتصحيحات التقنية

جميع هذه الاستدراكات اكتُشفت عبر فحص المحتوى النصي الفعلي أو عبر فشل اختبار last-aya:

6.1 استدراكات مرحلة بناء الخريطة (الإدخال الأولي)

السورةالقيمة الأوليةالسببالتصحيح
النساء (4)W46=[H46,H47]تقدير أولي للموضعW44=[H44,H45]
الأنبياء (21)W74=[H74,H75]تقدير أولي للموضعW73=[H73,H74]
فاطر (35)H45→W45+W46تقدير أولي للموضعH43→W43+W44
الرحمن (55)W1+W2 دمجان + تقسيم H37تعقيد زائدW1=[H1,H2] فقط
المدثر (74)W41=[H41,H42]تقدير أولي للموضعW40=[H40,H41]
الفجر (89)تقسيمان فقط H15+H16بيانات منقوصة3 تقسيمات + دمج

6.2 استدراكات مرحلة التحقق الشامل

السورةالوضع الأوليالتأثيرالتصحيح
مريم (19)تقسيم واحد H[42] فقطW[41..99] كلها غير صحيحةتقسيمان: H[41] وH[75]؛ + دمج W[1]=[H1,H2]
محمد (47)تقسيم عند H[15]W[5..15] غير صحيحة (10 آيات)تقسيم عند H[4]→W[4]+W[5]

٧ إحصاءات نهائية

114إجمالي سور القرآن
50سورة بفرق في العدّ
6سور بتحولات داخلية (فرق=0)
56سورة في WARSH_EXCEPTIONS
58سورة تطابق 1:1 مباشر
8استدراكات اكتُشفت وصُحّحت

٨ ملاحظات للمطورين

اختلاف الرسم العثماني — طبيعي ومتوقع

عند مقارنة نص ورش بنص حفص برمجياً، الاختلافات التالية في الرسم العثماني طبيعية ومقصودة، لا ينبغي الخلط بينها وبين التصحيف:

ورش (مدني)حفص (كوفي)ملاحظة
السموتالسماواترسم عثماني مختلف
ءامنواآمنوا / امنوااختلاف كتابة الهمزة
اهلكنهااهلكناهااختلاف في ضمير الجمع
يراءونيرائوناختلاف رسم الهمزة
ےيحرف مختلف في مصحف ورش
۞ (ربع الحزب)علامة خاصة بمصحف ورش

منطق الصوتيات

الدالة warshToHafsAyahs() تُستخدم في وضعين:

  1. التفسير: دائماً — مصدر التفاسير يستخدم ترقيم حفص
  2. الصوت (CDN): فقط عند moqriId.startsWith("warsh_") — ملفات الصوت مرقّمة بترقيم حفص حتى للقراء الذين يقرؤون بورش
  3. الصوت (DB): لا يحتاجها — الكوشي والقزابري مرقّمان بترقيم ورش مباشرة

٩ مصادر

  • قاعدة البيانات: نص ورش بالترقيم المدني (العدّ المدني الأخير)
  • نص حفص: نص حفص بالترقيم الكوفي
  • مصدر التفاسير: API خارجي يستخدم ترقيم حفص الكوفي
  • مرجع علمي: علم القراءات — كتب "معرفة عدد آي القرآن" وجداول الفرق بين العدّين المدني والكوفي
This document explains the technical solution implemented in the app to handle the verse-numbering discrepancy between the Warsh narration (UI) and the Hafs narration (commentary/data source). It is primarily aimed at developers building Quran applications.

1 Background and Problem

Why is this rare online?

The difference in verse count between Warsh and Hafs is documented in Quranic sciences literature, but there is no digital resource — within our research — that documents transition points verse-by-verse in a programmable format. Most sources only mention that "the Kufi count differs from the Madani count" without specifying the exact surah and verse.

Technical Context

The app displays the Warsh Mushaf and supports audio playback and commentary display. The commentary source is an external API that uses Hafs numbering (Kufi count). When tapping a verse in Warsh mode, the function was sending the verse number as-is to the API, returning data for a different verse in surahs where the numbering diverges.

NarrationCountUsage
Warsh (Nafi — Last Madani)6,214 versesUI database
Hafs (Asim — Kufi)6,236 versesCommentary/data source
Difference22 versesSpread across 50 surahs

2 Core Concepts

2.1 Types of Divergence

Merge — Warsh combines what Hafs separates:

H[n] + H[n+1]  →  W[k]        (Warsh has fewer verses)

Split — Warsh divides what Hafs keeps as one:

H[n]  →  W[k] + W[k+1]        (Warsh has more verses)

Opening Letters (Fawatih) Merge: Letters like "Alif Lam Meem" and "Ha Meem" are counted in Warsh as one verse with the following text, while Hafs counts them independently. This is the most frequent type, appearing in 16 surahs.

2.2 The Offset

The offset is the difference between a Warsh verse number and its Hafs counterpart:

W[n]  =  H[n + offset]
  • offset = 0: Direct match
  • offset = +1: Warsh had a prior merge (Hafs is ahead by one)
  • offset = -1: Warsh had a prior split (Hafs is behind by one)

The offset changes at every merge or split point and stays constant until the next one.

3 Technical Solution

3.1 Code Structure

// src/utils/tafsir.ts

const WARSH_EXCEPTIONS: Record<number, ExceptionType> = { ... }

export function warshToHafsAyahs(sura: number, warshAya: number): number[]

The function returns an array (not a single number) because a single Warsh verse can correspond to two Hafs verses when merged.

3.2 Exception Types

type ExceptionType =
  | { type: "manual"; map: Record<number, number[]> }
  | { type: "offset"; mergeAtWarshAyah: number; hafsTargetsToMerge: number[]; offsetForSubsequent: number }
  | { type: "advanced"; explicit_map: Record<number, number[]>; ranges: Range[] }
TypeUsage
manualAl-Fatiha only — full manual map
offsetSimple opening-letter merge at W1, fixed +1 offset for all subsequent verses
advancedAny surah with multiple or internal transitions

3.3 Advanced Example — Surah Maryam

// Maryam (19): W=99, H=98 — opening merge + two internal splits
19: {
  type: "advanced",
  explicit_map: {
    1: [1, 2],   // W1 = H1+H2  (Kaf Ha Ya Ain Sad merge)
    40: [41],    // W40 = H41   (first half of split 1)
    41: [41],    // W41 = H41   (second half of split 1)
    75: [75],    // W75 = H75   (first half of split 2)
    76: [75],    // W76 = H75   (second half of split 2)
  },
  ranges: [
    { warshStart: 2,  warshEnd: 39, hafsOffset:  1 },
    { warshStart: 42, warshEnd: 74, hafsOffset:  0 },
    { warshStart: 77, warshEnd: 99, hafsOffset: -1 },
  ],
}

4 Complete Surah Tables

Category 0 — Manual map

SurahWHDetails
Al-Fatiha (1)77Basmala not counted; H7 split into W6+W7

Category 1 — Simple opening merge (fixed offset +1)

SurahWHDiffMerged Opening
Al-Baqarah (2)285286+1Alif Lam Meem + That is the Book
Ta-Ha (20)134135+1Ta Ha + We have not sent down
Ash-Shu'ara (26)226227+1Ta Sin Meem + Those are verses
Ar-Rum (30)5960+1Alif Lam Meem + The Romans have been defeated
Luqman (31)3334+1Alif Lam Meem + These are verses
Ya-Sin (36)8283+1Ya Sin + By the wise Quran
Ghafir (40)8485+1Ha Meem + The revelation
Fussilat (41)5354+1Ha Meem + A Book
Al-Jathiya (45)3637+1Ha Meem + The revelation
Al-Ahqaf (46)3435+1Ha Meem + The revelation
Al-Qari'a (101)1011+1Al-Qari'a + What is Al-Qari'a

Category 1b — Opening merge + internal split (net = 0)

SurahWHMerge PointSplit Point
Al Imran (3)200200W1=[H1,H2]H92→W91+W92
Al-A'raf (7)206206W1=[H1,H2]H137→W136+W137
Al-Qasas (28)8888W1=[H1,H2]H23→W22+W23
Al-Ankabut (29)6969W1=[H1,H2]H29→W28+W29
As-Sajda (32)3030W1=[H1,H2]H10→W9+W10
Az-Zukhruf (43)8989W1=[H1,H2]H52→W51+W52

Category 2 — Multiple opening merges

SurahWHDiffDetails
Sad (38)8688+2W1=[H1,H2]; W83=[H84,H85]
Ash-Shura (42)5053+3W1=[H1,H2,H3] (Ha Meem + Ain Sin Qaf); W30=[H32,H33]
Ad-Dukhan (44)5659+3W1=[H1,H2]; W33=[H34,H35]; W42=[H44,H45]

Category 3 — Single split or merge (diff=±1)

SurahWHDiffDetails
Maryam (19)9998-1W1=[H1,H2]; H41→W40+W41; H75→W75+W76
Muhammad (47)3938-1H4→W4+W5
Al-Alaq (96)2019-1H15→W15+W16
Az-Zalzala (99)98-1H6→W6+W7
Quraysh (106)54-1H4→W4+W5
Al-Ma'un (107)67+1W6=[H6,H7]

Category 4 — Single internal merge (diff=+1)

SurahWHMerge Point
An-Nisa (4)175176W44=[H44,H45]
Al-Isra (17)110111W107=[H107,H108]
Al-Anbiya (21)111112W73=[H73,H74]
An-Najm (53)6162W28=[H28,H29]
Al-Hadid (57)2829W13=[H13,H14]
Al-Mujadila (58)2122W20=[H20,H21]
Al-Muddaththir (74)5556W40=[H40,H41]
Al-Qiyama (75)3940W18=[H18,H19]
An-Nazi'at (79)4546W37=[H37,H38]

Category 4b — Single internal split (diff=-1)

SurahWHSplit Point
Al-Mu'minun (23)119118H47→W47+W48
Fatir (35)4645H43→W43+W44
Al-Mulk (67)3130H10→W10+W11
Al-Anfal (8)7675H47→W47+W48
At-Tawba (9)130129H72→W72+W73

Category 5 — Multiple structural shifts

SurahWHDiffDetails
Al-Ma'ida (5)122120-2H1→W1+W2; H15→W16+W17
Al-An'am (6)167165-2H1→W1+W2; W67=[H66,H67]; H73→W73+W74; H161→W162+W163
Hud (11)121123+2W54=[H54,H55]; H86→W85+W86; W118=[H118,H119]; W120=[H121,H122]
Ar-Ra'd (13)4443-1H5→W5+W6; H16→W17+W18; W25=[H23,H24]
Ibrahim (14)5452-2H1→W1+W2; H5→W6+W7; H9→W11+W12; W22=[H19,H20]
Al-Kahf (18)105110+5W35=[H35,H36]; W84=[H85,H86]; W87=[H89,H90]; W89=[H92,H93]; W99=[H103,H104]
Al-Hajj (22)7678+2W18=[H18,H19]; W19=[H20,H21]
An-Nur (24)6264+2W36=[H36,H37]; W42=[H43,H44]
An-Naml (27)9593-2H33→W33+W34; H44→W45+W46
Az-Zumar (39)7275+3H3→W3+W4; W12=[H11,H12]; W14=[H14,H15]; W35=[H36,H37]; W37=[H39,H40]
At-Tur (52)4749+2W1=[H1,H2]; W12=[H13,H14]
Ar-Rahman (55)7778+1W1=[H1,H2]
Al-Waqi'a (56)9996-3H8→W8+W9; H9→W10+W11; H18→W20+W21; W25=[H22,H23]; H41→W43+W44
Nuh (71)3028-2H24→W24+W25; H25→W26+W27
Al-Muzzammil (73)1820+2W1=[H1,H2]; W16=[H17,H18]
Al-Fajr (89)3230-2H15→W15+W16; H16→W17+W18; H25→W27+W28; W32=[H29,H30]

5 Verification Methodology

5.1 Last-Verse Validation

The most reliable test: verifying that warshToHafsAyahs(W_last) == H_last for every surah:

mapped = warsh_to_hafs(exc, w_last)
assert mapped[-1] == h_last  # must match

Passed for all 56 surahs in WARSH_EXCEPTIONS.

5.2 Content Validation

Comparing actual verse text at transition points using Arabic normalization:

def normalize(text):
    text = re.sub(r'[أإآٱ]', 'ا', text)
    text = text.replace('ى','ي').replace('ے','ي')
    text = re.sub(r'[^\u0621-\u064A\s]', '', text)  # Arabic letters only
Important: Direct content comparison between Warsh and Hafs produces many false positives due to intentional differences in the Uthmanic script between the two narrations (e.g., "السموت" in Warsh vs "السماوات" in Hafs). This is a deliberate and documented scriptural difference, not a transcription issue. Content validation was used only to confirm transition points, not for general comparison.

6 Technical Corrections & Revisions

All of the following were discovered through actual text content inspection or through last-aya test failures:

6.1 Revisions During Initial Map Construction

SurahInitial ValueReasonCorrection
An-Nisa (4)W46=[H46,H47]Initial position estimateW44=[H44,H45]
Al-Anbiya (21)W74=[H74,H75]Initial position estimateW73=[H73,H74]
Fatir (35)H45→W45+W46Initial position estimateH43→W43+W44
Ar-Rahman (55)Two merges + H37 splitOvercomplicatedW1=[H1,H2] only
Al-Muddaththir (74)W41=[H41,H42]Initial position estimateW40=[H40,H41]
Al-Fajr (89)Two splits only H15+H16Incomplete data3 splits + 1 merge

6.2 Revisions During Full Verification Pass

SurahInitial StateImpactCorrection
Maryam (19)Single split at H[42] onlyW[41..99] all incorrectTwo splits: H[41] and H[75]; + merge W[1]=[H1,H2]
Muhammad (47)Split at H[15]W[5..15] incorrect (10 verses)Split at H[4]→W[4]+W[5]

7 Final Statistics

114Total Quran surahs
50Surahs with count difference
6Surahs with internal shifts (diff=0)
56Surahs in WARSH_EXCEPTIONS
58Surahs with direct 1:1 match
8Revisions discovered & corrected

8 Developer Notes

Uthmanic Script Differences — Normal & Expected

When comparing Warsh and Hafs text programmatically, the following script differences are intentional and documented. Do not treat them as transcription issues:

Warsh (Madani)Hafs (Kufi)Note
السموتالسماواتDifferent Uthmanic script
ءامنواآمنوا / امنواHamza writing variation
اهلكنهااهلكناهاPlural pronoun difference
يراءونيرائونHamza script variation
ےيDifferent letter form in Warsh
۞ (Rub' al-Hizb)Warsh-specific mark

Audio Logic

The function warshToHafsAyahs() is used in two modes:

  1. Commentary: Always — the commentary source uses Hafs numbering
  2. Audio (CDN): Only when moqriId.startsWith("warsh_") — CDN audio files are numbered with Hafs numbering even for Warsh reciters
  3. Audio (DB): Not needed — local DB reciters are already numbered with Warsh numbering

9 Sources

  • Database: Warsh text with Madani numbering (Last Madani Count)
  • Hafs text: Hafs text with Kufi numbering
  • Commentary source: External API using Hafs/Kufi numbering
  • Academic reference: Quranic sciences — works on verse counting and tables comparing the Madani and Kufi counts