توثيق تقني شامل لإشكالية الاختلاف في ترقيم الآيات وحلها البرمجي، موجّه للمطورين العاملين على تطبيقات القرآن الكريم. A comprehensive technical document on the verse-numbering discrepancy and its algorithmic solution, aimed at developers building Quran applications.
هذا التباين الرقمي ليس خللاً برمجياً، بل هو اختلاف علمي موثق تاريخياً في مدارس إحصاء فواصل الآيات — أين تنتهي الآية وتبدأ الأخرى. غير أن أثره من الناحية البرمجية البحتة يؤدي إلى كسر المزامنة؛ فاستدعاء الآية بناءً على رقمها الظاهر في واجهة ورش سيجلب بيانات غير متطابقة من قاعدة البيانات المصممة على هيكل حفص، مما ينتج عنه تفاوت (Décalage) في النص المقروء، التفسير المعروض، أو المقطع الصوتي.
تهدف هذه الورقة إلى سدّ واحدة من أندر الفجوات التوثيقية على الإنترنت: تحليل دقيق وحل خوارزمي يوثّق نقاط الاختلاف آية بآية بصيغة قابلة للبرمجة. تم استخراج هذه البيانات لبناء طبقة وسيطة (Middleware) تعالج حالات الدمج، التقسيم، والإزاحة ديناميكياً.
تخيّل النص القرآني كـ"شريط متصل" من الكلمات، وأن أرقام الآيات مجرد فواصل (Markers) نضعها عليه. الاختلاف بين الروايتين ليس في طول الشريط، بل في أماكن وضع هذه الفواصل. ينتج عن ذلك أربع ظواهر برمجية رئيسية:
ورش يعتبر مقطعين آية واحدة، بينما حفص يعدّهما آيتين. يُقلل العدد الإجمالي للآيات.
حفص يعدّ مقطعاً طويلاً آية واحدة، بينما ورش يقسمه إلى آيتين. يزيد العدد الإجمالي.
الفارق الرياضي المستمر بين رقم الآية في ورش ورقمها المقابل في حفص، ينشأ من الدمج أو التقسيم.
سورة تحتوي دمجاً وتقسيماً في آنٍ واحد، مما يُبطل الاعتماد على إزاحة ثابتة.
في قاعدة البيانات (حفص): الآية 1 هي الم والآية 2 هي ذَلِكَ الْكِتَابُ لاَ رَيْبَ فِيهِ...
في الواجهة (ورش): الآية 1 المدموجة هي الم ذَلِكَ الْكِتَابُ لاَ رَيْبَ فِيهِ...
الأثر البرمجي: عند الضغط على الآية (1) في مصحف ورش، يجب إرسال طلب للـ API لجلب تفسير الآيتين (1 و2) معاً ودمجهما.
في قاعدة البيانات (حفص): الآية 4 طويلة تبدأ بـ فَإِذَا لَقِيتُمُ الَّذِينَ كَفَرُوا...
في الواجهة (ورش): مقسّمة إلى الآية 4 والآية 5.
الأثر البرمجي: الآيتان (4) و(5) في ورش تجلبان كلتاهما تفسير الآية (4) من حفص.
الإزاحة هي الفارق الرياضي المستمر: رقم حفص = رقم ورش + الإزاحة
offset = +1: حفص يسبق ورش بآية (بسبب دمج سابق)offset = -1: حفص يتأخر عن ورش بآية (بسبب تقسيم سابق)ما يحدث فعلياً: ورش دمج الفاتحة الم في بداية السورة مما أحدث إزاحة (+1)، ثم قسّم الآية 92 في حفص إلى آيتين (91 و92) في ورش، ليُلغي أثر الدمج الأول. من الآية 93 حتى 200 يعود التطابق. لذا لا يمكن حل هذا بمعادلة بسيطة، بل بـخرائط نطاقات (Range Maps) داخل الكود.
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.
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:
Warsh counts two segments as one verse, while Hafs counts them as two. Reduces total verse count.
Hafs counts a long passage as one verse, while Warsh divides it into two. Increases total verse count.
The running mathematical difference between a verse number in Warsh and its counterpart in Hafs, arising from merges or splits.
A surah containing both merges and splits simultaneously, making a fixed offset approach impossible.
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.
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.
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)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 سورة |
الدمج (Merge) — ورش يجمع ما فرّقه حفص:
H[n] + H[n+1] → W[k] (ورش له آية أقل)
التقسيم (Split) — ورش يفرّق ما جمعه حفص:
H[n] → W[k] + W[k+1] (ورش له آية أكثر)
الفواتح (Fawatih Merge): الحروف المقطعة كـ"الم" و"حم" تُعدّ في ورش آية مع ما بعدها، بينما حفص يعدّها مستقلة. هذا النوع الأكثر تكراراً ويظهر في 16 سورة.
الإزاحة offset هي الفرق بين رقم الآية في ورش ورقمها المقابل في حفص:
W[n] = H[n + offset]
offset = 0: تطابق مباشرoffset = +1: ورش دمج سابقاً (حفص أمامه بآية)offset = -1: ورش قسّم سابقاً (حفص خلفه بآية)الإزاحة تتغير عند كل نقطة دمج أو تقسيم وتبقى ثابتة حتى النقطة التالية.
// src/utils/tafsir.ts
const WARSH_EXCEPTIONS: Record<number, ExceptionType> = { ... }
export function warshToHafsAyahs(sura: number, warshAya: number): number[]
الدالة warshToHafsAyahs تُرجع مصفوفة (وليس رقماً واحداً) لأن آية ورش الواحدة قد تقابل آيتين في حفص عند الدمج.
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 | أي سورة بها انتقالات متعددة أو داخلية |
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 },
],
}
| السورة | W | H | التفصيل |
|---|---|---|---|
| الفاتحة (1) | 7 | 7 | البسملة لا تُعدّ آية؛ H7 مقسّم W6+W7 |
| السورة | W | H | الفرق | الفاتحة المدموجة |
|---|---|---|---|---|
| البقرة (2) | 285 | 286 | +1 | الم + ذلك الكتاب |
| طه (20) | 134 | 135 | +1 | طه + ما أنزلنا |
| الشعراء (26) | 226 | 227 | +1 | طسم + تلك آيات |
| الروم (30) | 59 | 60 | +1 | الم + غلبت الروم |
| لقمان (31) | 33 | 34 | +1 | الم + تلك آيات |
| يس (36) | 82 | 83 | +1 | يس + والقرآن |
| غافر (40) | 84 | 85 | +1 | حم + تنزيل |
| فصلت (41) | 53 | 54 | +1 | حم + كتاب |
| الجاثية (45) | 36 | 37 | +1 | حم + تنزيل |
| الأحقاف (46) | 34 | 35 | +1 | حم + تنزيل |
| القارعة (101) | 10 | 11 | +1 | القارعة + ما القارعة |
| السورة | W | H | نقطة الدمج | نقطة التقسيم |
|---|---|---|---|---|
| آل عمران (3) | 200 | 200 | W1=[H1,H2] | H92→W91+W92 |
| الأعراف (7) | 206 | 206 | W1=[H1,H2] | H137→W136+W137 |
| القصص (28) | 88 | 88 | W1=[H1,H2] | H23→W22+W23 |
| العنكبوت (29) | 69 | 69 | W1=[H1,H2] | H29→W28+W29 |
| السجدة (32) | 30 | 30 | W1=[H1,H2] | H10→W9+W10 |
| الزخرف (43) | 89 | 89 | W1=[H1,H2] | H52→W51+W52 |
| السورة | W | H | الفرق | التفاصيل |
|---|---|---|---|---|
| ص (38) | 86 | 88 | +2 | W1=[H1,H2]؛ W83=[H84,H85] |
| الشورى (42) | 50 | 53 | +3 | W1=[H1,H2,H3] (حم+عسق)؛ W30=[H32,H33] |
| الدخان (44) | 56 | 59 | +3 | W1=[H1,H2]؛ W33=[H34,H35]؛ W42=[H44,H45] |
| السورة | W | H | الفرق | التفصيل |
|---|---|---|---|---|
| مريم (19) | 99 | 98 | -1 | W1=[H1,H2]؛ H41→W40+W41؛ H75→W75+W76 |
| محمد (47) | 39 | 38 | -1 | H4→W4+W5 |
| العلق (96) | 20 | 19 | -1 | H15→W15+W16 |
| الزلزلة (99) | 9 | 8 | -1 | H6→W6+W7 |
| قريش (106) | 5 | 4 | -1 | H4→W4+W5 |
| الماعون (107) | 6 | 7 | +1 | W6=[H6,H7] |
| السورة | W | H | نقطة الدمج |
|---|---|---|---|
| النساء (4) | 175 | 176 | W44=[H44,H45] |
| الإسراء (17) | 110 | 111 | W107=[H107,H108] |
| الأنبياء (21) | 111 | 112 | W73=[H73,H74] |
| النجم (53) | 61 | 62 | W28=[H28,H29] |
| الحديد (57) | 28 | 29 | W13=[H13,H14] |
| المجادلة (58) | 21 | 22 | W20=[H20,H21] |
| المدثر (74) | 55 | 56 | W40=[H40,H41] |
| القيامة (75) | 39 | 40 | W18=[H18,H19] |
| النازعات (79) | 45 | 46 | W37=[H37,H38] |
| السورة | W | H | نقطة التقسيم |
|---|---|---|---|
| المؤمنون (23) | 119 | 118 | H47→W47+W48 |
| فاطر (35) | 46 | 45 | H43→W43+W44 |
| الملك (67) | 31 | 30 | H10→W10+W11 |
| الأنفال (8) | 76 | 75 | H47→W47+W48 |
| التوبة (9) | 130 | 129 | H72→W72+W73 |
| السورة | W | H | الفرق | التفاصيل |
|---|---|---|---|---|
| المائدة (5) | 122 | 120 | -2 | H1→W1+W2؛ H15→W16+W17 |
| الأنعام (6) | 167 | 165 | -2 | H1→W1+W2؛ W67=[H66,H67]؛ H73→W73+W74؛ H161→W162+W163 |
| هود (11) | 121 | 123 | +2 | W54=[H54,H55]؛ H86→W85+W86؛ W118=[H118,H119]؛ W120=[H121,H122] |
| الرعد (13) | 44 | 43 | -1 | H5→W5+W6؛ H16→W17+W18؛ W25=[H23,H24] |
| إبراهيم (14) | 54 | 52 | -2 | H1→W1+W2؛ H5→W6+W7؛ H9→W11+W12؛ W22=[H19,H20] |
| الكهف (18) | 105 | 110 | +5 | W35=[H35,H36]؛ W84=[H85,H86]؛ W87=[H89,H90]؛ W89=[H92,H93]؛ W99=[H103,H104] |
| الحج (22) | 76 | 78 | +2 | W18=[H18,H19]؛ W19=[H20,H21] |
| النور (24) | 62 | 64 | +2 | W36=[H36,H37]؛ W42=[H43,H44] |
| النمل (27) | 95 | 93 | -2 | H33→W33+W34؛ H44→W45+W46 |
| الزمر (39) | 72 | 75 | +3 | H3→W3+W4؛ W12=[H11,H12]؛ W14=[H14,H15]؛ W35=[H36,H37]؛ W37=[H39,H40] |
| الطور (52) | 47 | 49 | +2 | W1=[H1,H2]؛ W12=[H13,H14] |
| الرحمن (55) | 77 | 78 | +1 | W1=[H1,H2] |
| الواقعة (56) | 99 | 96 | -3 | H8→W8+W9؛ H9→W10+W11؛ H18→W20+W21؛ W25=[H22,H23]؛ H41→W43+W44 |
| نوح (71) | 30 | 28 | -2 | H24→W24+W25؛ H25→W26+W27 |
| المزمل (73) | 18 | 20 | +2 | W1=[H1,H2]؛ W16=[H17,H18] |
| الفجر (89) | 32 | 30 | -2 | H15→W15+W16؛ H16→W17+W18؛ H25→W27+W28؛ W32=[H29,H30] |
الاختبار الأكثر موثوقية: التحقق من أن warshToHafsAyahs(W_last) == H_last لكل سورة:
mapped = warsh_to_hafs(exc, w_last)
assert mapped[-1] == h_last # يجب أن تتطابق
نجح لجميع 56 سورة في WARSH_EXCEPTIONS.
مقارنة نص الآيات الفعلي عند نقاط الانتقال باستخدام تطبيع عربي:
def normalize(text):
text = re.sub(r'[أإآٱ]', 'ا', text)
text = text.replace('ى','ي').replace('ے','ي')
text = re.sub(r'[^\u0621-\u064A\s]', '', text) # حروف عربية فقط
جميع هذه الاستدراكات اكتُشفت عبر فحص المحتوى النصي الفعلي أو عبر فشل اختبار last-aya:
| السورة | القيمة الأولية | السبب | التصحيح |
|---|---|---|---|
| النساء (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 تقسيمات + دمج |
| السورة | الوضع الأولي | التأثير | التصحيح |
|---|---|---|---|
| مريم (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] |
عند مقارنة نص ورش بنص حفص برمجياً، الاختلافات التالية في الرسم العثماني طبيعية ومقصودة، لا ينبغي الخلط بينها وبين التصحيف:
| ورش (مدني) | حفص (كوفي) | ملاحظة |
|---|---|---|
| السموت | السماوات | رسم عثماني مختلف |
| ءامنوا | آمنوا / امنوا | اختلاف كتابة الهمزة |
| اهلكنها | اهلكناها | اختلاف في ضمير الجمع |
| يراءون | يرائون | اختلاف رسم الهمزة |
| ے | ي | حرف مختلف في مصحف ورش |
| ۞ (ربع الحزب) | — | علامة خاصة بمصحف ورش |
الدالة warshToHafsAyahs() تُستخدم في وضعين:
moqriId.startsWith("warsh_") — ملفات الصوت مرقّمة بترقيم حفص حتى للقراء الذين يقرؤون بورش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.
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.
| Narration | Count | Usage |
|---|---|---|
| Warsh (Nafi — Last Madani) | 6,214 verses | UI database |
| Hafs (Asim — Kufi) | 6,236 verses | Commentary/data source |
| Difference | 22 verses | Spread across 50 surahs |
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.
The offset is the difference between a Warsh verse number and its Hafs counterpart:
W[n] = H[n + offset]
offset = 0: Direct matchoffset = +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.
// 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.
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[] }
| Type | Usage |
|---|---|
manual | Al-Fatiha only — full manual map |
offset | Simple opening-letter merge at W1, fixed +1 offset for all subsequent verses |
advanced | Any surah with multiple or internal transitions |
// 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 },
],
}
| Surah | W | H | Details |
|---|---|---|---|
| Al-Fatiha (1) | 7 | 7 | Basmala not counted; H7 split into W6+W7 |
| Surah | W | H | Diff | Merged Opening |
|---|---|---|---|---|
| Al-Baqarah (2) | 285 | 286 | +1 | Alif Lam Meem + That is the Book |
| Ta-Ha (20) | 134 | 135 | +1 | Ta Ha + We have not sent down |
| Ash-Shu'ara (26) | 226 | 227 | +1 | Ta Sin Meem + Those are verses |
| Ar-Rum (30) | 59 | 60 | +1 | Alif Lam Meem + The Romans have been defeated |
| Luqman (31) | 33 | 34 | +1 | Alif Lam Meem + These are verses |
| Ya-Sin (36) | 82 | 83 | +1 | Ya Sin + By the wise Quran |
| Ghafir (40) | 84 | 85 | +1 | Ha Meem + The revelation |
| Fussilat (41) | 53 | 54 | +1 | Ha Meem + A Book |
| Al-Jathiya (45) | 36 | 37 | +1 | Ha Meem + The revelation |
| Al-Ahqaf (46) | 34 | 35 | +1 | Ha Meem + The revelation |
| Al-Qari'a (101) | 10 | 11 | +1 | Al-Qari'a + What is Al-Qari'a |
| Surah | W | H | Merge Point | Split Point |
|---|---|---|---|---|
| Al Imran (3) | 200 | 200 | W1=[H1,H2] | H92→W91+W92 |
| Al-A'raf (7) | 206 | 206 | W1=[H1,H2] | H137→W136+W137 |
| Al-Qasas (28) | 88 | 88 | W1=[H1,H2] | H23→W22+W23 |
| Al-Ankabut (29) | 69 | 69 | W1=[H1,H2] | H29→W28+W29 |
| As-Sajda (32) | 30 | 30 | W1=[H1,H2] | H10→W9+W10 |
| Az-Zukhruf (43) | 89 | 89 | W1=[H1,H2] | H52→W51+W52 |
| Surah | W | H | Diff | Details |
|---|---|---|---|---|
| Sad (38) | 86 | 88 | +2 | W1=[H1,H2]; W83=[H84,H85] |
| Ash-Shura (42) | 50 | 53 | +3 | W1=[H1,H2,H3] (Ha Meem + Ain Sin Qaf); W30=[H32,H33] |
| Ad-Dukhan (44) | 56 | 59 | +3 | W1=[H1,H2]; W33=[H34,H35]; W42=[H44,H45] |
| Surah | W | H | Diff | Details |
|---|---|---|---|---|
| Maryam (19) | 99 | 98 | -1 | W1=[H1,H2]; H41→W40+W41; H75→W75+W76 |
| Muhammad (47) | 39 | 38 | -1 | H4→W4+W5 |
| Al-Alaq (96) | 20 | 19 | -1 | H15→W15+W16 |
| Az-Zalzala (99) | 9 | 8 | -1 | H6→W6+W7 |
| Quraysh (106) | 5 | 4 | -1 | H4→W4+W5 |
| Al-Ma'un (107) | 6 | 7 | +1 | W6=[H6,H7] |
| Surah | W | H | Merge Point |
|---|---|---|---|
| An-Nisa (4) | 175 | 176 | W44=[H44,H45] |
| Al-Isra (17) | 110 | 111 | W107=[H107,H108] |
| Al-Anbiya (21) | 111 | 112 | W73=[H73,H74] |
| An-Najm (53) | 61 | 62 | W28=[H28,H29] |
| Al-Hadid (57) | 28 | 29 | W13=[H13,H14] |
| Al-Mujadila (58) | 21 | 22 | W20=[H20,H21] |
| Al-Muddaththir (74) | 55 | 56 | W40=[H40,H41] |
| Al-Qiyama (75) | 39 | 40 | W18=[H18,H19] |
| An-Nazi'at (79) | 45 | 46 | W37=[H37,H38] |
| Surah | W | H | Split Point |
|---|---|---|---|
| Al-Mu'minun (23) | 119 | 118 | H47→W47+W48 |
| Fatir (35) | 46 | 45 | H43→W43+W44 |
| Al-Mulk (67) | 31 | 30 | H10→W10+W11 |
| Al-Anfal (8) | 76 | 75 | H47→W47+W48 |
| At-Tawba (9) | 130 | 129 | H72→W72+W73 |
| Surah | W | H | Diff | Details |
|---|---|---|---|---|
| Al-Ma'ida (5) | 122 | 120 | -2 | H1→W1+W2; H15→W16+W17 |
| Al-An'am (6) | 167 | 165 | -2 | H1→W1+W2; W67=[H66,H67]; H73→W73+W74; H161→W162+W163 |
| Hud (11) | 121 | 123 | +2 | W54=[H54,H55]; H86→W85+W86; W118=[H118,H119]; W120=[H121,H122] |
| Ar-Ra'd (13) | 44 | 43 | -1 | H5→W5+W6; H16→W17+W18; W25=[H23,H24] |
| Ibrahim (14) | 54 | 52 | -2 | H1→W1+W2; H5→W6+W7; H9→W11+W12; W22=[H19,H20] |
| Al-Kahf (18) | 105 | 110 | +5 | W35=[H35,H36]; W84=[H85,H86]; W87=[H89,H90]; W89=[H92,H93]; W99=[H103,H104] |
| Al-Hajj (22) | 76 | 78 | +2 | W18=[H18,H19]; W19=[H20,H21] |
| An-Nur (24) | 62 | 64 | +2 | W36=[H36,H37]; W42=[H43,H44] |
| An-Naml (27) | 95 | 93 | -2 | H33→W33+W34; H44→W45+W46 |
| Az-Zumar (39) | 72 | 75 | +3 | H3→W3+W4; W12=[H11,H12]; W14=[H14,H15]; W35=[H36,H37]; W37=[H39,H40] |
| At-Tur (52) | 47 | 49 | +2 | W1=[H1,H2]; W12=[H13,H14] |
| Ar-Rahman (55) | 77 | 78 | +1 | W1=[H1,H2] |
| Al-Waqi'a (56) | 99 | 96 | -3 | H8→W8+W9; H9→W10+W11; H18→W20+W21; W25=[H22,H23]; H41→W43+W44 |
| Nuh (71) | 30 | 28 | -2 | H24→W24+W25; H25→W26+W27 |
| Al-Muzzammil (73) | 18 | 20 | +2 | W1=[H1,H2]; W16=[H17,H18] |
| Al-Fajr (89) | 32 | 30 | -2 | H15→W15+W16; H16→W17+W18; H25→W27+W28; W32=[H29,H30] |
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.
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
All of the following were discovered through actual text content inspection or through last-aya test failures:
| Surah | Initial Value | Reason | Correction |
|---|---|---|---|
| An-Nisa (4) | W46=[H46,H47] | Initial position estimate | W44=[H44,H45] |
| Al-Anbiya (21) | W74=[H74,H75] | Initial position estimate | W73=[H73,H74] |
| Fatir (35) | H45→W45+W46 | Initial position estimate | H43→W43+W44 |
| Ar-Rahman (55) | Two merges + H37 split | Overcomplicated | W1=[H1,H2] only |
| Al-Muddaththir (74) | W41=[H41,H42] | Initial position estimate | W40=[H40,H41] |
| Al-Fajr (89) | Two splits only H15+H16 | Incomplete data | 3 splits + 1 merge |
| Surah | Initial State | Impact | Correction |
|---|---|---|---|
| Maryam (19) | Single split at H[42] only | W[41..99] all incorrect | Two 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] |
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 |
The function warshToHafsAyahs() is used in two modes:
moqriId.startsWith("warsh_") — CDN audio files are numbered with Hafs numbering even for Warsh reciters