@quran.ws/tajwid-rules 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +35 -0
- package/README.md +113 -0
- package/package.json +33 -0
- package/rules.json +2752 -0
- package/schema/rules.schema.json +306 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://quran.ws/schema/tajweed-rules-0.4.json",
|
|
4
|
+
"title": "Tajweed Rule Corpus",
|
|
5
|
+
"description": "A scholar-authored corpus of tajweed rules, organised as topics > categories > ahkam > rules. Each rule carries a CASE pattern that a conforming engine compiles into a matcher over normalised Quranic text.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"version",
|
|
9
|
+
"riwayah",
|
|
10
|
+
"topics",
|
|
11
|
+
"categories",
|
|
12
|
+
"hukums",
|
|
13
|
+
"rules"
|
|
14
|
+
],
|
|
15
|
+
"additionalProperties": false,
|
|
16
|
+
"properties": {
|
|
17
|
+
"$schema": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
},
|
|
20
|
+
"version": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
|
23
|
+
"description": "Semantic version of the corpus. A change in matching behaviour of any stable rule is a MAJOR bump — consumers pin this."
|
|
24
|
+
},
|
|
25
|
+
"riwayah": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"const": "hafs-an-asim",
|
|
28
|
+
"description": "The riwayah these rules describe. Tajweed rulings and the underlying orthography both differ between riwayat; applying this corpus to a Warsh or Qalun text is a data error, not a configuration option."
|
|
29
|
+
},
|
|
30
|
+
"provenance": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"additionalProperties": true,
|
|
33
|
+
"properties": {
|
|
34
|
+
"source": {
|
|
35
|
+
"type": "string"
|
|
36
|
+
},
|
|
37
|
+
"url": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"format": "uri"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"topics": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"items": {
|
|
46
|
+
"$ref": "#/$defs/topic"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"categories": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"$ref": "#/$defs/category"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"hukums": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": {
|
|
58
|
+
"$ref": "#/$defs/hukum"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"rules": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"items": {
|
|
64
|
+
"$ref": "#/$defs/rule"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"$defs": {
|
|
69
|
+
"slug": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
|
|
72
|
+
},
|
|
73
|
+
"label": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"description": "Human-readable text, keyed by BCP-47 language tag. Arabic is authoritative; other languages are translations of it.",
|
|
76
|
+
"required": [
|
|
77
|
+
"ar"
|
|
78
|
+
],
|
|
79
|
+
"additionalProperties": {
|
|
80
|
+
"type": "string"
|
|
81
|
+
},
|
|
82
|
+
"properties": {
|
|
83
|
+
"ar": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"minLength": 1
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"topic": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"required": [
|
|
92
|
+
"id",
|
|
93
|
+
"label"
|
|
94
|
+
],
|
|
95
|
+
"additionalProperties": false,
|
|
96
|
+
"properties": {
|
|
97
|
+
"id": {
|
|
98
|
+
"$ref": "#/$defs/slug"
|
|
99
|
+
},
|
|
100
|
+
"label": {
|
|
101
|
+
"$ref": "#/$defs/label"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"category": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"required": [
|
|
108
|
+
"id",
|
|
109
|
+
"topic",
|
|
110
|
+
"label"
|
|
111
|
+
],
|
|
112
|
+
"additionalProperties": false,
|
|
113
|
+
"properties": {
|
|
114
|
+
"id": {
|
|
115
|
+
"$ref": "#/$defs/slug"
|
|
116
|
+
},
|
|
117
|
+
"topic": {
|
|
118
|
+
"$ref": "#/$defs/slug"
|
|
119
|
+
},
|
|
120
|
+
"label": {
|
|
121
|
+
"$ref": "#/$defs/label"
|
|
122
|
+
},
|
|
123
|
+
"empty": {
|
|
124
|
+
"type": "boolean",
|
|
125
|
+
"description": "This category carries no ahkam. Kept so the hierarchy round-trips; consumers should hide it."
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"hukum": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"required": [
|
|
132
|
+
"id",
|
|
133
|
+
"category",
|
|
134
|
+
"label"
|
|
135
|
+
],
|
|
136
|
+
"additionalProperties": false,
|
|
137
|
+
"properties": {
|
|
138
|
+
"id": {
|
|
139
|
+
"$ref": "#/$defs/slug"
|
|
140
|
+
},
|
|
141
|
+
"category": {
|
|
142
|
+
"$ref": "#/$defs/slug"
|
|
143
|
+
},
|
|
144
|
+
"label": {
|
|
145
|
+
"$ref": "#/$defs/label"
|
|
146
|
+
},
|
|
147
|
+
"school": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"description": "Where ahkam differ between scholars, each position is modelled as its own hukum rather than one being silently preferred. Consumers that want a single answer must choose a school; consumers teaching tajweed may want to show both.",
|
|
150
|
+
"required": [
|
|
151
|
+
"id",
|
|
152
|
+
"scholar"
|
|
153
|
+
],
|
|
154
|
+
"additionalProperties": false,
|
|
155
|
+
"properties": {
|
|
156
|
+
"id": {
|
|
157
|
+
"$ref": "#/$defs/slug"
|
|
158
|
+
},
|
|
159
|
+
"scholar": {
|
|
160
|
+
"type": "string"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"rule": {
|
|
167
|
+
"type": "object",
|
|
168
|
+
"required": [
|
|
169
|
+
"id",
|
|
170
|
+
"hukum",
|
|
171
|
+
"case",
|
|
172
|
+
"scope",
|
|
173
|
+
"status",
|
|
174
|
+
"label"
|
|
175
|
+
],
|
|
176
|
+
"additionalProperties": false,
|
|
177
|
+
"properties": {
|
|
178
|
+
"id": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*\\.\\d+$",
|
|
181
|
+
"description": "Stable public identifier, `<hukum-slug>.<n>`."
|
|
182
|
+
},
|
|
183
|
+
"hukum": {
|
|
184
|
+
"$ref": "#/$defs/slug"
|
|
185
|
+
},
|
|
186
|
+
"case": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"minLength": 1,
|
|
189
|
+
"description": "The CASE pattern: a compact Arabic notation a non-programmer can author. Space-separated alternatives inside [ ], groups joined by +, `ــ` as a wildcard, and the literal names الألف الخنجرية / واو صغيرة / ياء صغيرة for marks that are hard to type. Compiled to a regular expression by the engine — see packages/core."
|
|
190
|
+
},
|
|
191
|
+
"scope": {
|
|
192
|
+
"enum": [
|
|
193
|
+
"within-word",
|
|
194
|
+
"across-words",
|
|
195
|
+
"either"
|
|
196
|
+
],
|
|
197
|
+
"description": "Whether the groups of a multi-group CASE may span a word boundary. This also determines what may appear between groups, so there is no separate whitespace field: within-word permits no space at all, across-words requires one, either permits one.",
|
|
198
|
+
"$comment": "within-word => no whitespace; across-words => \\s+; either => \\s*"
|
|
199
|
+
},
|
|
200
|
+
"matchAgainst": {
|
|
201
|
+
"enum": [
|
|
202
|
+
"normalized",
|
|
203
|
+
"original"
|
|
204
|
+
],
|
|
205
|
+
"default": "normalized",
|
|
206
|
+
"description": "Which text the pattern is written against. Almost every rule matches the normalised form. A rule declares `original` when it concerns a mark that normalisation removes as decoration — the maddah that identifies المد اللازم الحرفي sits over a consonant only in the disjoined letters, and is stripped before an ordinary rule could see it."
|
|
207
|
+
},
|
|
208
|
+
"status": {
|
|
209
|
+
"enum": [
|
|
210
|
+
"stable",
|
|
211
|
+
"disputed",
|
|
212
|
+
"disabled"
|
|
213
|
+
],
|
|
214
|
+
"description": "How settled the rule is. Three values, and the difference between the last two is what happens to the spans.\n stable — published as correct.\n disputed — the pattern works and the spans are still published, but a qualified reader has questioned the RULING and it is unresolved. Requires `disputed`. This exists because the alternatives both lie: `stable` says the question was settled, and `disabled` requires a `gap`, every value of which describes a limitation of the CASE notation — which a questioned ruling is not.\n disabled — not compiled into the default engine, so its spans do not ship. Requires `gap`.\nNote that the engine excludes `disabled` and nothing else, so moving a rule to `disputed` removes no annotation from any consumer. Removing the spans of a questioned rule is a decision about the Qur'an and is not made by changing a status."
|
|
215
|
+
},
|
|
216
|
+
"gap": {
|
|
217
|
+
"enum": [
|
|
218
|
+
"not-a-pattern",
|
|
219
|
+
"needs-rule-composition",
|
|
220
|
+
"unsupported-optional-group",
|
|
221
|
+
"unsupported-token",
|
|
222
|
+
"unsupported-negation",
|
|
223
|
+
"unsupported-exception",
|
|
224
|
+
"ambiguous-wildcard"
|
|
225
|
+
],
|
|
226
|
+
"description": "For disabled rules: which limitation of the CASE notation prevents this rule from being expressed. Together these form the backlog for the next version of the notation."
|
|
227
|
+
},
|
|
228
|
+
"statusReason": {
|
|
229
|
+
"type": "string"
|
|
230
|
+
},
|
|
231
|
+
"needsReview": {
|
|
232
|
+
"type": "boolean",
|
|
233
|
+
"description": "This rule was altered during migration, or is otherwise awaiting sign-off from a qualified reviewer. It still matches; the flag exists so the change is not invisible."
|
|
234
|
+
},
|
|
235
|
+
"corrections": {
|
|
236
|
+
"type": "array",
|
|
237
|
+
"description": "Changes made to this rule relative to the source it was migrated from, with the evidence for each.",
|
|
238
|
+
"items": {
|
|
239
|
+
"type": "object",
|
|
240
|
+
"required": [
|
|
241
|
+
"field",
|
|
242
|
+
"was",
|
|
243
|
+
"reason"
|
|
244
|
+
],
|
|
245
|
+
"additionalProperties": false,
|
|
246
|
+
"properties": {
|
|
247
|
+
"field": {
|
|
248
|
+
"enum": [
|
|
249
|
+
"case"
|
|
250
|
+
]
|
|
251
|
+
},
|
|
252
|
+
"was": {
|
|
253
|
+
"type": "string"
|
|
254
|
+
},
|
|
255
|
+
"reason": {
|
|
256
|
+
"type": "string",
|
|
257
|
+
"minLength": 1
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
"label": {
|
|
263
|
+
"$ref": "#/$defs/label"
|
|
264
|
+
},
|
|
265
|
+
"notes": {
|
|
266
|
+
"$ref": "#/$defs/label"
|
|
267
|
+
},
|
|
268
|
+
"startFrom": {
|
|
269
|
+
"type": "string",
|
|
270
|
+
"description": "Editorial annotation, NOT a pattern — do not compile it. Depending on the rule it marks where a highlight should visually begin (typically the first group of `case`), or gives the plain undiacritised spelling of the target word."
|
|
271
|
+
},
|
|
272
|
+
"disputed": {
|
|
273
|
+
"type": "object",
|
|
274
|
+
"description": "A qualified reader has questioned this ruling, and it has not been resolved. Recorded on the rule so the question travels with the thing it is about, rather than living in a review thread. A rule carrying this MUST NOT be `status: \"stable\"` — see scripts/validate-rules.ts. Note that `gap` cannot express this: every value there is a limitation of the CASE notation, and a disputed ruling is not a notation problem.",
|
|
275
|
+
"additionalProperties": false,
|
|
276
|
+
"required": [
|
|
277
|
+
"finding",
|
|
278
|
+
"raised_by",
|
|
279
|
+
"occurrences"
|
|
280
|
+
],
|
|
281
|
+
"properties": {
|
|
282
|
+
"finding": {
|
|
283
|
+
"type": "string",
|
|
284
|
+
"description": "What is questioned and why, in enough detail to act on without the original thread."
|
|
285
|
+
},
|
|
286
|
+
"raised_by": {
|
|
287
|
+
"type": "string",
|
|
288
|
+
"description": "Who raised it, and whether they cited a source or reasoned from the standard treatment."
|
|
289
|
+
},
|
|
290
|
+
"occurrences": {
|
|
291
|
+
"type": "array",
|
|
292
|
+
"items": {
|
|
293
|
+
"type": "string"
|
|
294
|
+
},
|
|
295
|
+
"description": "The places the dispute bears on, by ayah reference — not necessarily everywhere the rule fires. A list, never a count, so it survives being copied and re-run."
|
|
296
|
+
},
|
|
297
|
+
"resolved_by": {
|
|
298
|
+
"type": "string",
|
|
299
|
+
"description": "Left absent until a qualified reviewer has ruled."
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|