prpm-dbp 0.2.2 → 0.3.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/SKILL.md +18 -0
- package/bin/cli.js +8 -1
- package/package.json +1 -1
- package/src/tukar.js +86 -18
package/SKILL.md
CHANGED
|
@@ -73,6 +73,24 @@ Jawi Melayu menulis vokal dengan alif, wau dan ya, tidak seperti Arab. Kerana it
|
|
|
73
73
|
|
|
74
74
|
`rumi` bekerja dari cache sahaja dan tidak menyentuh rangkaian. Perkataan yang belum pernah disemak dalam arah Rumi ke Jawi tidak akan dikenali. Jalankan `warm` atau `audit` dahulu pada kosa kata yang berkaitan.
|
|
75
75
|
|
|
76
|
+
## Istilah Arab
|
|
77
|
+
|
|
78
|
+
Kebanyakan istilah agama ialah **kata Melayu**, bukan kata asing. Diuji terhadap PRPM: 29 daripada 30 istilah lazim ada entri, termasuk `yasin`, `fatihah`, `baqarah`, `rahman`, `rasulullah`. Jangan andaikan sesuatu itu Arab semata-mata kerana ia berbunyi Arab; tanya DBP dahulu.
|
|
79
|
+
|
|
80
|
+
Tiga keadaan berbeza, dilaporkan berasingan:
|
|
81
|
+
|
|
82
|
+
| Keadaan | Contoh | Maksud |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| Ada entri dan ejaan Jawi | `solat` صلاة, `zakat` زکاة | guna terus |
|
|
85
|
+
| `adaEntriTanpaJawi` | `yasin`, `fatihah`, `baqarah`, `wuduk` | DBP iktiraf sebagai kata Melayu, cuma tiada ejaan Jawi disimpan |
|
|
86
|
+
| `belumDisahkan` | `ahzab` | bukan kata Melayu; nama khas Arab |
|
|
87
|
+
|
|
88
|
+
Ejaan Jawi bagi pinjaman Arab **mengekalkan ejaan Arab asal**, bukan dieja secara fonetik: `صلاة` bukan `سولت`, `زکاة` bukan `زکات`. Ta marbutah dan alef maksura dikekalkan. Sebab itu ta marbutah tidak pernah dilipat semasa perbandingan.
|
|
89
|
+
|
|
90
|
+
Sesetengah entri memulangkan **dua perkataan Jawi untuk satu perkataan Rumi**: `rasulullah` → `رسول الله`. Pembacaan songsang memadan jujukan, jadi ia tidak pecah menjadi `rasul allah`.
|
|
91
|
+
|
|
92
|
+
**Teks Arab yang dipetik dibiarkan sepenuhnya.** Jawi Melayu tidak menggunakan baris tanda vokal, jadi kehadiran fathah, kasrah, dammah, sukun atau tanwin bermakna potongan itu Arab, bukan Jawi. Ayat al-Quran dan hadis dalam dokumen Melayu lulus tanpa disentuh dan dilaporkan dalam medan `arab`.
|
|
93
|
+
|
|
76
94
|
## Rantai kepercayaan
|
|
77
95
|
|
|
78
96
|
Setiap ejaan Jawi yang dikeluarkan membawa asal-usulnya. Jangan runtuhkan lapisan ini.
|
package/bin/cli.js
CHANGED
|
@@ -154,6 +154,12 @@ async function main() {
|
|
|
154
154
|
if (h.belumDisahkan.length) {
|
|
155
155
|
console.error(`\nbelum disahkan (${h.belumDisahkan.length}): ${h.belumDisahkan.join(', ')}`)
|
|
156
156
|
}
|
|
157
|
+
if (h.adaEntriTanpaJawi.length) {
|
|
158
|
+
console.error(
|
|
159
|
+
`DBP ada entri tetapi tiada ejaan Jawi (${h.adaEntriTanpaJawi.length}): ` +
|
|
160
|
+
h.adaEntriTanpaJawi.join(', '),
|
|
161
|
+
)
|
|
162
|
+
}
|
|
157
163
|
if (h.terbitanSah.length) {
|
|
158
164
|
console.error(
|
|
159
165
|
`terbitan sah tanpa ejaan Jawi (${h.terbitanSah.length}): ` +
|
|
@@ -165,13 +171,14 @@ async function main() {
|
|
|
165
171
|
|
|
166
172
|
case 'rumi': {
|
|
167
173
|
const teks = await readFile(baki[0], 'utf8')
|
|
168
|
-
const h = keRumi(teks, cache)
|
|
174
|
+
const h = keRumi(teks, cache, { dataset: await muatDataset() })
|
|
169
175
|
if (pilihan.json) { keluar(h); break }
|
|
170
176
|
console.log(h.teks)
|
|
171
177
|
console.error(`\npeta songsang: ${h.saizPeta} ejaan`)
|
|
172
178
|
if (h.tidakDikenali.length) {
|
|
173
179
|
console.error(`tidak dikenali (${h.tidakDikenali.length}): ${h.tidakDikenali.join(', ')}`)
|
|
174
180
|
}
|
|
181
|
+
if (h.arab.length) console.error(`teks Arab dibiarkan (${h.arab.length}): ${h.arab.join(' ')}`)
|
|
175
182
|
for (const k of h.kabur) console.error(`kabur: ${k.jawi} -> ${k.calon.join(', ')}`)
|
|
176
183
|
break
|
|
177
184
|
}
|
package/package.json
CHANGED
package/src/tukar.js
CHANGED
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
import { lookup } from './prpm.js'
|
|
12
12
|
import { normalisasiJawi } from './kamus.js'
|
|
13
|
-
import { cariDataset } from './dataset.js'
|
|
13
|
+
import { cariDataset, muatDataset } from './dataset.js'
|
|
14
|
+
|
|
15
|
+
// Baris tanda vokal Arab (fathah, kasrah, dammah, sukun, syaddah, tanwin,
|
|
16
|
+
// alef superscript). Jawi Melayu TIDAK menggunakannya; teks al-Quran dan
|
|
17
|
+
// hadis yang dipetik dalam dokumen Melayu menggunakannya. Kehadirannya
|
|
18
|
+
// bermakna potongan itu bahasa Arab, bukan Jawi, dan tidak boleh ditukar.
|
|
19
|
+
const HARAKAT = /[\u064B-\u0652\u0670\u0653-\u0655]/
|
|
14
20
|
|
|
15
21
|
const JAWI_JULAT = '-ۿݐ-ݿﭐ-﷿ﹰ-'
|
|
16
22
|
// Token dipecah supaya pemisah dikekalkan, jadi tanda baca, baris baru dan
|
|
@@ -80,15 +86,36 @@ export async function cariDasar(kata, cache, opts = {}) {
|
|
|
80
86
|
return null
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
/**
|
|
84
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Bina peta Jawi -> Rumi. Menggabungkan cache DBP dengan dataset yang
|
|
91
|
+
* diimport, jika ada: tanpa dataset, apa-apa yang ditukar melalui lapisan
|
|
92
|
+
* ketiga tidak boleh dipusingkan balik langsung.
|
|
93
|
+
*
|
|
94
|
+
* Sesetengah nilai mengandungi RUANG (rasulullah -> رسول الله). Peta menjejaki
|
|
95
|
+
* bilangan perkataan terpanjang supaya pembacaan songsang boleh memadan
|
|
96
|
+
* jujukan, bukan token tunggal sahaja.
|
|
97
|
+
*/
|
|
98
|
+
export function petaSongsang(cache, dataset = null) {
|
|
85
99
|
const peta = new Map()
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
let maxKata = 1
|
|
101
|
+
|
|
102
|
+
const tambah = (jawi, rumi) => {
|
|
103
|
+
const kunci = normalisasiJawi(jawi)
|
|
104
|
+
if (!kunci) return
|
|
105
|
+
const bilangan = kunci.split(/\s+/).length
|
|
106
|
+
if (bilangan > maxKata) maxKata = bilangan
|
|
89
107
|
if (!peta.has(kunci)) peta.set(kunci, [])
|
|
90
|
-
if (!peta.get(kunci).includes(
|
|
108
|
+
if (!peta.get(kunci).includes(rumi)) peta.get(kunci).push(rumi)
|
|
91
109
|
}
|
|
110
|
+
|
|
111
|
+
for (const rec of Object.values(cache)) {
|
|
112
|
+
if (rec?.jawi) tambah(rec.jawi, rec.kata)
|
|
113
|
+
}
|
|
114
|
+
for (const [rumi, ejaan] of Object.entries(dataset?.peta ?? {})) {
|
|
115
|
+
for (const j of ejaan) tambah(j, rumi)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
peta.maxKata = maxKata
|
|
92
119
|
return peta
|
|
93
120
|
}
|
|
94
121
|
|
|
@@ -102,6 +129,7 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
102
129
|
const terbitanSah = []
|
|
103
130
|
const dariDataset = []
|
|
104
131
|
const dbp = []
|
|
132
|
+
const adaEntriTanpaJawi = []
|
|
105
133
|
const bahagian = teks.split(PECAH_RUMI)
|
|
106
134
|
const keluar = []
|
|
107
135
|
|
|
@@ -111,6 +139,11 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
111
139
|
const rec = await lookup(cebis.toLowerCase(), cache, opts)
|
|
112
140
|
if (rec?.jawi) { keluar.push(rec.jawi); dbp.push(cebis); continue }
|
|
113
141
|
|
|
142
|
+
// Keadaan berbeza daripada tiada entri: DBP mengiktiraf perkataan ini
|
|
143
|
+
// sebagai Melayu, cuma tidak menyimpan ejaan Jawinya. Lazim bagi nama
|
|
144
|
+
// surah dan istilah agama seperti yasin, fatihah, baqarah.
|
|
145
|
+
if (rec?.dijumpai) adaEntriTanpaJawi.push(cebis)
|
|
146
|
+
|
|
114
147
|
// Lapisan ketiga: dataset yang diimport pengguna. Berlabel, tidak pernah
|
|
115
148
|
// menyamar sebagai ejaan DBP.
|
|
116
149
|
if (gunaDataset) {
|
|
@@ -141,6 +174,7 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
141
174
|
teks: keluar.join(''),
|
|
142
175
|
disahkanDbp: dbp.length,
|
|
143
176
|
dariDataset,
|
|
177
|
+
adaEntriTanpaJawi: [...new Set(adaEntriTanpaJawi)],
|
|
144
178
|
belumDisahkan: [...new Set(belumDisahkan)],
|
|
145
179
|
terbitanSah,
|
|
146
180
|
lengkap: belumDisahkan.length === 0 && terbitanSah.length === 0,
|
|
@@ -156,32 +190,66 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
156
190
|
* menggugurkannya. Kerana itu perlanggaran jauh lebih jarang daripada yang
|
|
157
191
|
* disangka. Bila ia berlaku, SEMUA calon dilaporkan, bukan dipilih satu.
|
|
158
192
|
*/
|
|
159
|
-
export function keRumi(teksJawi, cache, { penanda = ['«', '»'] } = {}) {
|
|
160
|
-
const peta = petaSongsang(cache)
|
|
193
|
+
export function keRumi(teksJawi, cache, { penanda = ['«', '»'], dataset = null } = {}) {
|
|
194
|
+
const peta = petaSongsang(cache, dataset)
|
|
161
195
|
const tidakDikenali = []
|
|
162
196
|
const kabur = []
|
|
197
|
+
const arab = []
|
|
163
198
|
const bahagian = teksJawi.split(PECAH_JAWI)
|
|
164
199
|
const keluar = []
|
|
165
200
|
|
|
166
|
-
for (
|
|
167
|
-
if (i % 2 === 0) { keluar.push(
|
|
201
|
+
for (let i = 0; i < bahagian.length; i++) {
|
|
202
|
+
if (i % 2 === 0) { keluar.push(bahagian[i]); continue }
|
|
203
|
+
|
|
204
|
+
// Teks Arab bertanda baris dibiarkan seadanya. Cuba menukarnya akan
|
|
205
|
+
// merosakkan petikan al-Quran dan hadis dalam dokumen Melayu.
|
|
206
|
+
if (HARAKAT.test(bahagian[i])) {
|
|
207
|
+
arab.push(bahagian[i])
|
|
208
|
+
keluar.push(bahagian[i])
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Padanan jujukan terpanjang dahulu, supaya رسول الله ditemui sebagai
|
|
213
|
+
// satu perkataan dan bukan dipecah jadi rasul + allah.
|
|
214
|
+
let padanan = null
|
|
215
|
+
let hujung = i
|
|
216
|
+
// Had atas MESTI integer. Nilai pecahan menyebabkan n melompat 3.5, 2.5,
|
|
217
|
+
// 1.5 dan tidak pernah menyentuh bilangan perkataan sebenar, lalu hujung
|
|
218
|
+
// jatuh pada pemisah dan bukan token.
|
|
219
|
+
const maxN = Math.min(peta.maxKata, Math.floor((bahagian.length - i + 1) / 2))
|
|
220
|
+
for (let n = maxN; n >= 1; n--) {
|
|
221
|
+
const akhir = i + (n - 1) * 2
|
|
222
|
+
if (akhir >= bahagian.length || akhir % 2 === 0) continue
|
|
223
|
+
// Hanya gabung apabila pemisah antara token benar-benar ruang.
|
|
224
|
+
let bolehGabung = true
|
|
225
|
+
for (let k = i + 1; k < akhir; k += 2) {
|
|
226
|
+
if (!/^\s+$/.test(bahagian[k])) { bolehGabung = false; break }
|
|
227
|
+
}
|
|
228
|
+
if (!bolehGabung) continue
|
|
229
|
+
const frasa = bahagian.slice(i, akhir + 1).join(' ').replace(/\s+/g, ' ')
|
|
230
|
+
const jumpa = peta.get(normalisasiJawi(frasa))
|
|
231
|
+
if (jumpa) { padanan = jumpa; hujung = akhir; break }
|
|
232
|
+
}
|
|
168
233
|
|
|
169
|
-
const padanan = peta.get(normalisasiJawi(cebis))
|
|
170
234
|
if (!padanan) {
|
|
171
|
-
tidakDikenali.push(
|
|
172
|
-
keluar.push(penanda[0] +
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
235
|
+
tidakDikenali.push(bahagian[i])
|
|
236
|
+
keluar.push(penanda[0] + bahagian[i] + penanda[1])
|
|
237
|
+
continue
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (padanan.length === 1) keluar.push(padanan[0])
|
|
241
|
+
else {
|
|
242
|
+
kabur.push({ jawi: bahagian.slice(i, hujung + 1).join(''), calon: padanan })
|
|
177
243
|
keluar.push(padanan.join('|'))
|
|
178
244
|
}
|
|
245
|
+
i = hujung
|
|
179
246
|
}
|
|
180
247
|
|
|
181
248
|
return {
|
|
182
249
|
teks: keluar.join(''),
|
|
183
250
|
tidakDikenali: [...new Set(tidakDikenali)],
|
|
184
251
|
kabur,
|
|
252
|
+
arab: [...new Set(arab)],
|
|
185
253
|
saizPeta: peta.size,
|
|
186
254
|
lengkap: tidakDikenali.length === 0,
|
|
187
255
|
}
|