prpm-dbp 0.2.2 → 0.4.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/README.md +1 -0
- package/SKILL.md +43 -0
- package/bin/cli.js +28 -1
- package/package.json +1 -1
- package/src/nama.js +70 -0
- package/src/tukar.js +105 -20
package/README.md
CHANGED
|
@@ -128,6 +128,7 @@ Pengguna kemudian menarik versi baharu dengan `npx skills update`.
|
|
|
128
128
|
| `jawi <fail>` | tukar teks Rumi ke Jawi ikut ejaan DBP |
|
|
129
129
|
| `rumi <fail>` | tukar teks Jawi ke Rumi dari cache |
|
|
130
130
|
| `import-tsv <fail>` | import dataset rumi/jawi tambahan |
|
|
131
|
+
| `nama <rumi> <jawi>` | sahkan satu nama khas |
|
|
131
132
|
| `banding <kata...>` | silang semak PRPM lawan KamusDBP |
|
|
132
133
|
| `cache` | status cache, `--buang <kata>` untuk padam satu |
|
|
133
134
|
|
package/SKILL.md
CHANGED
|
@@ -73,6 +73,49 @@ 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
|
+
|
|
94
|
+
## Nama khas
|
|
95
|
+
|
|
96
|
+
Diuji terhadap PRPM: **nama tokoh dan nama surah tiada sumber berwibawa.** `muhammad`, `ibrahim`, `musa`, `umar`, `aisyah`, `khadijah` semuanya tiada entri. Nama surah seperti `fatihah`, `baqarah`, `yasin` ada entri tetapi tanpa ejaan Jawi. Dataset komuniti meliputi nama tempat (`mekah` مکة, `mesir` مسير, `pahang` ڤاهڠ) tetapi bukan nama orang.
|
|
97
|
+
|
|
98
|
+
Alat ini **tidak mereka ejaan nama**. Sebaliknya:
|
|
99
|
+
|
|
100
|
+
- Perkataan berhuruf besar di **tengah** ayat dikira nama khas dan dilaporkan dalam medan `namaKhas`, berasingan daripada `belumDisahkan`. Nama bukan salah eja.
|
|
101
|
+
- Sahkan sekali, guna selamanya:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx prpm-dbp nama Muhammad <ejaan-jawi>
|
|
105
|
+
npx prpm-dbp nama # senaraikan yang sudah disahkan
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Ejaan yang disimpan begini ditandakan datang daripada pengguna, bukan daripada DBP. Sahkan dengan guru atau rujukan bertulis dahulu.
|
|
109
|
+
|
|
110
|
+
**Jangan keliru antara dua benda:**
|
|
111
|
+
|
|
112
|
+
| | Layanan |
|
|
113
|
+
|---|---|
|
|
114
|
+
| Petikan bahasa Arab (ayat, doa, hadis) | kekal Arab, tidak disentuh |
|
|
115
|
+
| Nama khas dalam ayat Melayu | **ditulis dalam Jawi**, bukan dikekalkan |
|
|
116
|
+
|
|
117
|
+
Bagi nama berasal Arab, ejaan Jawinya memang ejaan Arab asal, jadi ia nampak seperti dikekalkan. Bagi nama bukan Arab ia ditransliterasi penuh: `Kuala Lumpur` menjadi `کوالا لومڤور`.
|
|
118
|
+
|
|
76
119
|
## Rantai kepercayaan
|
|
77
120
|
|
|
78
121
|
Setiap ejaan Jawi yang dikeluarkan membawa asal-usulnya. Jangan runtuhkan lapisan ini.
|
package/bin/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { pasangSkill } from '../src/install.js'
|
|
|
6
6
|
import { banding, cariKamus } from '../src/kamus.js'
|
|
7
7
|
import { keJawi, keRumi } from '../src/tukar.js'
|
|
8
8
|
import { huraiTsv, muatDataset, simpanDataset } from '../src/dataset.js'
|
|
9
|
+
import { NAMA_PATH, muatNama, tambahNama } from '../src/nama.js'
|
|
9
10
|
import {
|
|
10
11
|
CACHE_PATH,
|
|
11
12
|
auditJawi,
|
|
@@ -26,6 +27,8 @@ const BANTUAN = `prpm-dbp — semak Bahasa Melayu dan Jawi terhadap PRPM DBP
|
|
|
26
27
|
npx prpm-dbp jawi <fail.txt> tukar teks Rumi ke Jawi ikut ejaan DBP
|
|
27
28
|
npx prpm-dbp rumi <fail.txt> tukar teks Jawi ke Rumi dari cache
|
|
28
29
|
npx prpm-dbp import-tsv <fail.tsv> import dataset rumi<TAB>jawi (dilabel bukan DBP)
|
|
30
|
+
npx prpm-dbp nama <rumi> <jawi> sahkan satu nama khas (tokoh, surah, tempat)
|
|
31
|
+
npx prpm-dbp nama senaraikan nama khas yang disahkan
|
|
29
32
|
npx prpm-dbp banding <kata...> silang semak PRPM lawan KamusDBP
|
|
30
33
|
npx prpm-dbp cache [--buang <kata>] status cache
|
|
31
34
|
npx prpm-dbp install [--link] pasang sebagai Claude Code skill
|
|
@@ -154,6 +157,18 @@ async function main() {
|
|
|
154
157
|
if (h.belumDisahkan.length) {
|
|
155
158
|
console.error(`\nbelum disahkan (${h.belumDisahkan.length}): ${h.belumDisahkan.join(', ')}`)
|
|
156
159
|
}
|
|
160
|
+
if (h.adaEntriTanpaJawi.length) {
|
|
161
|
+
console.error(
|
|
162
|
+
`DBP ada entri tetapi tiada ejaan Jawi (${h.adaEntriTanpaJawi.length}): ` +
|
|
163
|
+
h.adaEntriTanpaJawi.join(', '),
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
if (h.namaKhas.length) {
|
|
167
|
+
console.error(
|
|
168
|
+
`nama khas belum disahkan (${h.namaKhas.length}): ${h.namaKhas.join(', ')}\n` +
|
|
169
|
+
` sahkan dengan: npx prpm-dbp nama <rumi> <jawi>`,
|
|
170
|
+
)
|
|
171
|
+
}
|
|
157
172
|
if (h.terbitanSah.length) {
|
|
158
173
|
console.error(
|
|
159
174
|
`terbitan sah tanpa ejaan Jawi (${h.terbitanSah.length}): ` +
|
|
@@ -165,17 +180,29 @@ async function main() {
|
|
|
165
180
|
|
|
166
181
|
case 'rumi': {
|
|
167
182
|
const teks = await readFile(baki[0], 'utf8')
|
|
168
|
-
const h = keRumi(teks, cache)
|
|
183
|
+
const h = keRumi(teks, cache, { dataset: await muatDataset() })
|
|
169
184
|
if (pilihan.json) { keluar(h); break }
|
|
170
185
|
console.log(h.teks)
|
|
171
186
|
console.error(`\npeta songsang: ${h.saizPeta} ejaan`)
|
|
172
187
|
if (h.tidakDikenali.length) {
|
|
173
188
|
console.error(`tidak dikenali (${h.tidakDikenali.length}): ${h.tidakDikenali.join(', ')}`)
|
|
174
189
|
}
|
|
190
|
+
if (h.arab.length) console.error(`teks Arab dibiarkan (${h.arab.length}): ${h.arab.join(' ')}`)
|
|
175
191
|
for (const k of h.kabur) console.error(`kabur: ${k.jawi} -> ${k.calon.join(', ')}`)
|
|
176
192
|
break
|
|
177
193
|
}
|
|
178
194
|
|
|
195
|
+
case 'nama': {
|
|
196
|
+
if (baki.length >= 2) {
|
|
197
|
+
const rec = await tambahNama(baki[0], baki[1], { jenis: pilihan.jenis ?? null })
|
|
198
|
+
keluar({ disimpan: rec, fail: NAMA_PATH, nota: 'Ejaan ini datang dari anda, bukan dari DBP.' })
|
|
199
|
+
break
|
|
200
|
+
}
|
|
201
|
+
const peta = await muatNama()
|
|
202
|
+
keluar({ fail: NAMA_PATH, jumlah: Object.keys(peta).length, nama: peta })
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
|
|
179
206
|
case 'import-tsv': {
|
|
180
207
|
if (!baki[0]) throw new Error('perlu fail TSV: rumi<TAB>jawi setiap baris')
|
|
181
208
|
const { peta, dilangkau, jumlah } = huraiTsv(await readFile(baki[0], 'utf8'))
|
package/package.json
CHANGED
package/src/nama.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simpanan nama khas yang disahkan pengguna.
|
|
3
|
+
*
|
|
4
|
+
* Kenapa ini wujud: nama tokoh dan nama surah tiada sumber berwibawa yang
|
|
5
|
+
* boleh dicapai. Diuji terhadap PRPM, muhammad, ibrahim, musa, umar, aisyah
|
|
6
|
+
* dan khadijah semuanya TIADA entri. Dataset komuniti pula meliputi nama
|
|
7
|
+
* tempat (مکة, مسير, ڤاهڠ) tetapi bukan nama orang atau nama surah.
|
|
8
|
+
*
|
|
9
|
+
* Alat ini TIDAK mereka ejaan nama. Ia menyimpan apa yang pengguna sahkan
|
|
10
|
+
* sendiri, dengan sumber dicatat, supaya kerja pengesahan itu dibuat sekali
|
|
11
|
+
* sahaja dan boleh diaudit kemudian.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
15
|
+
import { dirname, join } from 'node:path'
|
|
16
|
+
|
|
17
|
+
import { CACHE_PATH } from './prpm.js'
|
|
18
|
+
|
|
19
|
+
export const NAMA_PATH = process.env.PRPM_NAMA ?? join(dirname(CACHE_PATH), 'nama-khas.json')
|
|
20
|
+
|
|
21
|
+
let dimuat = null
|
|
22
|
+
|
|
23
|
+
export async function muatNama() {
|
|
24
|
+
if (dimuat !== null) return dimuat
|
|
25
|
+
try {
|
|
26
|
+
dimuat = JSON.parse(await readFile(NAMA_PATH, 'utf8'))
|
|
27
|
+
} catch {
|
|
28
|
+
dimuat = {}
|
|
29
|
+
}
|
|
30
|
+
return dimuat
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function simpanNama(peta) {
|
|
34
|
+
await mkdir(dirname(NAMA_PATH), { recursive: true })
|
|
35
|
+
const tmp = `${NAMA_PATH}.tmp`
|
|
36
|
+
await writeFile(tmp, JSON.stringify(peta, null, 1), 'utf8')
|
|
37
|
+
await rename(tmp, NAMA_PATH)
|
|
38
|
+
dimuat = peta
|
|
39
|
+
return NAMA_PATH
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function tambahNama(rumi, jawi, { sumber = 'pengguna', jenis = null } = {}) {
|
|
43
|
+
const peta = { ...(await muatNama()) }
|
|
44
|
+
const kunci = String(rumi).trim().toLowerCase()
|
|
45
|
+
if (!kunci || !jawi) throw new Error('perlu rumi dan jawi')
|
|
46
|
+
peta[kunci] = { rumi: kunci, jawi: String(jawi).trim(), sumber, jenis }
|
|
47
|
+
await simpanNama(peta)
|
|
48
|
+
return peta[kunci]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function cariNama(kata) {
|
|
52
|
+
const peta = await muatNama()
|
|
53
|
+
const rec = peta[String(kata).trim().toLowerCase()]
|
|
54
|
+
if (!rec) return null
|
|
55
|
+
return { ...rec, dijumpai: true, disahkanDbp: false }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Teka sama ada perkataan itu nama khas.
|
|
60
|
+
*
|
|
61
|
+
* Huruf besar di TENGAH ayat ialah isyarat yang munasabah dalam teks Melayu.
|
|
62
|
+
* Ia sengaja tidak digunakan pada perkataan pertama ayat, kerana di situ
|
|
63
|
+
* huruf besar tidak bermakna apa-apa.
|
|
64
|
+
*/
|
|
65
|
+
export function mungkinNamaKhas(kata, sebelumnya) {
|
|
66
|
+
if (!/^[A-Z]/.test(kata)) return false
|
|
67
|
+
// Awal teks, atau selepas noktah, tanda soal, tanda seru, atau baris baru.
|
|
68
|
+
const awalAyat = !sebelumnya || /[.!?:\n]\s*$/.test(sebelumnya)
|
|
69
|
+
return !awalAyat
|
|
70
|
+
}
|
package/src/tukar.js
CHANGED
|
@@ -10,7 +10,14 @@
|
|
|
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
|
+
import { cariNama, mungkinNamaKhas } from './nama.js'
|
|
15
|
+
|
|
16
|
+
// Baris tanda vokal Arab (fathah, kasrah, dammah, sukun, syaddah, tanwin,
|
|
17
|
+
// alef superscript). Jawi Melayu TIDAK menggunakannya; teks al-Quran dan
|
|
18
|
+
// hadis yang dipetik dalam dokumen Melayu menggunakannya. Kehadirannya
|
|
19
|
+
// bermakna potongan itu bahasa Arab, bukan Jawi, dan tidak boleh ditukar.
|
|
20
|
+
const HARAKAT = /[\u064B-\u0652\u0670\u0653-\u0655]/
|
|
14
21
|
|
|
15
22
|
const JAWI_JULAT = '-ۿݐ-ݿﭐ-﷿ﹰ-'
|
|
16
23
|
// Token dipecah supaya pemisah dikekalkan, jadi tanda baca, baris baru dan
|
|
@@ -80,15 +87,36 @@ export async function cariDasar(kata, cache, opts = {}) {
|
|
|
80
87
|
return null
|
|
81
88
|
}
|
|
82
89
|
|
|
83
|
-
/**
|
|
84
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Bina peta Jawi -> Rumi. Menggabungkan cache DBP dengan dataset yang
|
|
92
|
+
* diimport, jika ada: tanpa dataset, apa-apa yang ditukar melalui lapisan
|
|
93
|
+
* ketiga tidak boleh dipusingkan balik langsung.
|
|
94
|
+
*
|
|
95
|
+
* Sesetengah nilai mengandungi RUANG (rasulullah -> رسول الله). Peta menjejaki
|
|
96
|
+
* bilangan perkataan terpanjang supaya pembacaan songsang boleh memadan
|
|
97
|
+
* jujukan, bukan token tunggal sahaja.
|
|
98
|
+
*/
|
|
99
|
+
export function petaSongsang(cache, dataset = null) {
|
|
85
100
|
const peta = new Map()
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
let maxKata = 1
|
|
102
|
+
|
|
103
|
+
const tambah = (jawi, rumi) => {
|
|
104
|
+
const kunci = normalisasiJawi(jawi)
|
|
105
|
+
if (!kunci) return
|
|
106
|
+
const bilangan = kunci.split(/\s+/).length
|
|
107
|
+
if (bilangan > maxKata) maxKata = bilangan
|
|
89
108
|
if (!peta.has(kunci)) peta.set(kunci, [])
|
|
90
|
-
if (!peta.get(kunci).includes(
|
|
109
|
+
if (!peta.get(kunci).includes(rumi)) peta.get(kunci).push(rumi)
|
|
91
110
|
}
|
|
111
|
+
|
|
112
|
+
for (const rec of Object.values(cache)) {
|
|
113
|
+
if (rec?.jawi) tambah(rec.jawi, rec.kata)
|
|
114
|
+
}
|
|
115
|
+
for (const [rumi, ejaan] of Object.entries(dataset?.peta ?? {})) {
|
|
116
|
+
for (const j of ejaan) tambah(j, rumi)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
peta.maxKata = maxKata
|
|
92
120
|
return peta
|
|
93
121
|
}
|
|
94
122
|
|
|
@@ -102,6 +130,9 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
102
130
|
const terbitanSah = []
|
|
103
131
|
const dariDataset = []
|
|
104
132
|
const dbp = []
|
|
133
|
+
const adaEntriTanpaJawi = []
|
|
134
|
+
const dariNama = []
|
|
135
|
+
const namaKhas = []
|
|
105
136
|
const bahagian = teks.split(PECAH_RUMI)
|
|
106
137
|
const keluar = []
|
|
107
138
|
|
|
@@ -111,6 +142,11 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
111
142
|
const rec = await lookup(cebis.toLowerCase(), cache, opts)
|
|
112
143
|
if (rec?.jawi) { keluar.push(rec.jawi); dbp.push(cebis); continue }
|
|
113
144
|
|
|
145
|
+
// Keadaan berbeza daripada tiada entri: DBP mengiktiraf perkataan ini
|
|
146
|
+
// sebagai Melayu, cuma tidak menyimpan ejaan Jawinya. Lazim bagi nama
|
|
147
|
+
// surah dan istilah agama seperti yasin, fatihah, baqarah.
|
|
148
|
+
if (rec?.dijumpai) adaEntriTanpaJawi.push(cebis)
|
|
149
|
+
|
|
114
150
|
// Lapisan ketiga: dataset yang diimport pengguna. Berlabel, tidak pernah
|
|
115
151
|
// menyamar sebagai ejaan DBP.
|
|
116
152
|
if (gunaDataset) {
|
|
@@ -122,6 +158,14 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
122
158
|
}
|
|
123
159
|
}
|
|
124
160
|
|
|
161
|
+
// Nama khas yang pengguna sudah sahkan sendiri.
|
|
162
|
+
const nm = await cariNama(cebis)
|
|
163
|
+
if (nm?.jawi) {
|
|
164
|
+
dariNama.push({ kata: cebis, jawi: nm.jawi })
|
|
165
|
+
keluar.push(nm.jawi)
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
|
|
125
169
|
if (cubaDasar) {
|
|
126
170
|
const dasar = await cariDasar(cebis.toLowerCase(), cache, opts)
|
|
127
171
|
if (dasar) {
|
|
@@ -133,7 +177,11 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
133
177
|
}
|
|
134
178
|
}
|
|
135
179
|
|
|
136
|
-
|
|
180
|
+
// Nama khas yang belum disahkan bukan salah eja. Ia dilaporkan
|
|
181
|
+
// berasingan supaya pengguna tahu apa yang perlu disahkan sekali, dan
|
|
182
|
+
// bukan mengulang menyemak ejaan yang memang betul.
|
|
183
|
+
if (mungkinNamaKhas(cebis, bahagian[i - 1])) namaKhas.push(cebis)
|
|
184
|
+
else belumDisahkan.push(cebis)
|
|
137
185
|
keluar.push(penanda[0] + cebis + penanda[1])
|
|
138
186
|
}
|
|
139
187
|
|
|
@@ -141,9 +189,12 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
141
189
|
teks: keluar.join(''),
|
|
142
190
|
disahkanDbp: dbp.length,
|
|
143
191
|
dariDataset,
|
|
192
|
+
dariNama,
|
|
193
|
+
namaKhas: [...new Set(namaKhas)],
|
|
194
|
+
adaEntriTanpaJawi: [...new Set(adaEntriTanpaJawi)],
|
|
144
195
|
belumDisahkan: [...new Set(belumDisahkan)],
|
|
145
196
|
terbitanSah,
|
|
146
|
-
lengkap: belumDisahkan.length === 0 && terbitanSah.length === 0,
|
|
197
|
+
lengkap: belumDisahkan.length === 0 && terbitanSah.length === 0 && namaKhas.length === 0,
|
|
147
198
|
// Sepenuhnya DBP bermakna setiap perkataan datang dari sumber rasmi.
|
|
148
199
|
semuanyaDbp: dariDataset.length === 0 && belumDisahkan.length === 0 && terbitanSah.length === 0,
|
|
149
200
|
}
|
|
@@ -156,32 +207,66 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
156
207
|
* menggugurkannya. Kerana itu perlanggaran jauh lebih jarang daripada yang
|
|
157
208
|
* disangka. Bila ia berlaku, SEMUA calon dilaporkan, bukan dipilih satu.
|
|
158
209
|
*/
|
|
159
|
-
export function keRumi(teksJawi, cache, { penanda = ['«', '»'] } = {}) {
|
|
160
|
-
const peta = petaSongsang(cache)
|
|
210
|
+
export function keRumi(teksJawi, cache, { penanda = ['«', '»'], dataset = null } = {}) {
|
|
211
|
+
const peta = petaSongsang(cache, dataset)
|
|
161
212
|
const tidakDikenali = []
|
|
162
213
|
const kabur = []
|
|
214
|
+
const arab = []
|
|
163
215
|
const bahagian = teksJawi.split(PECAH_JAWI)
|
|
164
216
|
const keluar = []
|
|
165
217
|
|
|
166
|
-
for (
|
|
167
|
-
if (i % 2 === 0) { keluar.push(
|
|
218
|
+
for (let i = 0; i < bahagian.length; i++) {
|
|
219
|
+
if (i % 2 === 0) { keluar.push(bahagian[i]); continue }
|
|
220
|
+
|
|
221
|
+
// Teks Arab bertanda baris dibiarkan seadanya. Cuba menukarnya akan
|
|
222
|
+
// merosakkan petikan al-Quran dan hadis dalam dokumen Melayu.
|
|
223
|
+
if (HARAKAT.test(bahagian[i])) {
|
|
224
|
+
arab.push(bahagian[i])
|
|
225
|
+
keluar.push(bahagian[i])
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Padanan jujukan terpanjang dahulu, supaya رسول الله ditemui sebagai
|
|
230
|
+
// satu perkataan dan bukan dipecah jadi rasul + allah.
|
|
231
|
+
let padanan = null
|
|
232
|
+
let hujung = i
|
|
233
|
+
// Had atas MESTI integer. Nilai pecahan menyebabkan n melompat 3.5, 2.5,
|
|
234
|
+
// 1.5 dan tidak pernah menyentuh bilangan perkataan sebenar, lalu hujung
|
|
235
|
+
// jatuh pada pemisah dan bukan token.
|
|
236
|
+
const maxN = Math.min(peta.maxKata, Math.floor((bahagian.length - i + 1) / 2))
|
|
237
|
+
for (let n = maxN; n >= 1; n--) {
|
|
238
|
+
const akhir = i + (n - 1) * 2
|
|
239
|
+
if (akhir >= bahagian.length || akhir % 2 === 0) continue
|
|
240
|
+
// Hanya gabung apabila pemisah antara token benar-benar ruang.
|
|
241
|
+
let bolehGabung = true
|
|
242
|
+
for (let k = i + 1; k < akhir; k += 2) {
|
|
243
|
+
if (!/^\s+$/.test(bahagian[k])) { bolehGabung = false; break }
|
|
244
|
+
}
|
|
245
|
+
if (!bolehGabung) continue
|
|
246
|
+
const frasa = bahagian.slice(i, akhir + 1).join(' ').replace(/\s+/g, ' ')
|
|
247
|
+
const jumpa = peta.get(normalisasiJawi(frasa))
|
|
248
|
+
if (jumpa) { padanan = jumpa; hujung = akhir; break }
|
|
249
|
+
}
|
|
168
250
|
|
|
169
|
-
const padanan = peta.get(normalisasiJawi(cebis))
|
|
170
251
|
if (!padanan) {
|
|
171
|
-
tidakDikenali.push(
|
|
172
|
-
keluar.push(penanda[0] +
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
252
|
+
tidakDikenali.push(bahagian[i])
|
|
253
|
+
keluar.push(penanda[0] + bahagian[i] + penanda[1])
|
|
254
|
+
continue
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (padanan.length === 1) keluar.push(padanan[0])
|
|
258
|
+
else {
|
|
259
|
+
kabur.push({ jawi: bahagian.slice(i, hujung + 1).join(''), calon: padanan })
|
|
177
260
|
keluar.push(padanan.join('|'))
|
|
178
261
|
}
|
|
262
|
+
i = hujung
|
|
179
263
|
}
|
|
180
264
|
|
|
181
265
|
return {
|
|
182
266
|
teks: keluar.join(''),
|
|
183
267
|
tidakDikenali: [...new Set(tidakDikenali)],
|
|
184
268
|
kabur,
|
|
269
|
+
arab: [...new Set(arab)],
|
|
185
270
|
saizPeta: peta.size,
|
|
186
271
|
lengkap: tidakDikenali.length === 0,
|
|
187
272
|
}
|