Parser CSV yang digunakan dalam plug-in jquery-csv
Ini adalah pengurai tata bahasa Chomsky Type III dasar .
Sebuah tokenizer regex digunakan untuk mengevaluasi data berdasarkan char-by-char. Ketika char kontrol ditemukan, kode dilewatkan ke pernyataan switch untuk evaluasi lebih lanjut berdasarkan keadaan awal. Karakter non-kontrol dikelompokkan dan disalin secara bersamaan untuk mengurangi jumlah operasi penyalinan string yang diperlukan.
Tokenizer:
var tokenizer = /("|,|\n|\r|[^",\r\n]+)/;
Set pertandingan pertama adalah karakter kontrol: pemisah nilai (") pemisah nilai (,) dan pemisah entri (semua variasi baris baru). Pertandingan terakhir menangani pengelompokan karakter non-kontrol.
Ada 10 aturan yang harus dipenuhi oleh parser:
- Aturan # 1 - Satu entri per baris, setiap baris diakhiri dengan baris baru
- Aturan # 2 - Mengejar baris baru di akhir file yang dihilangkan
- Aturan # 3 - Baris pertama berisi data tajuk
- Aturan # 4 - Spasi dianggap data dan entri tidak boleh mengandung tanda koma
- Aturan # 5 - Baris mungkin atau mungkin tidak dibatasi oleh tanda kutip ganda
- Aturan # 6 - Bidang yang berisi jeda baris, tanda kutip ganda, dan koma harus dilampirkan dalam tanda kutip ganda
- Aturan # 7 - Jika tanda kutip ganda digunakan untuk menyertakan bidang, maka tanda kutip ganda yang muncul di dalam bidang harus diloloskan dengan mendahului dengan tanda kutip ganda lainnya.
- Amandemen # 1 - Bidang yang tidak dikutip dapat atau mungkin
- Amandemen # 2 - Bidang yang dikutip mungkin atau tidak
- Amandemen # 3 - Bidang terakhir dalam entri mungkin mengandung nilai nol atau tidak
Catatan: 7 aturan teratas berasal langsung dari IETF RFC 4180 . 3 terakhir ditambahkan untuk menutupi kasus tepi yang diperkenalkan oleh aplikasi spreadsheet modern (ex Excel, Google Spreadsheet) yang tidak membatasi (yaitu kutipan) semua nilai secara default. Saya mencoba menyumbang kembali perubahan pada RFC tetapi belum mendengar tanggapan atas pertanyaan saya.
Cukup dengan wind-up, inilah diagram:
Menyatakan:
- keadaan awal untuk entri dan / atau nilai
- kutipan pembuka telah ditemukan
- kutipan kedua telah ditemukan
- nilai yang tidak dikutip telah ditemukan
Transisi:
- Sebuah. memeriksa kedua nilai yang dikutip (1), nilai yang tidak dikutip (3), nilai nol (0), entri nol (0), dan entri baru (0)
- b. memeriksa tanda kutip kedua (2)
- c. memeriksa kutipan yang lolos (1), nilai akhir (0), dan akhir entri (0)
- d. memeriksa nilai akhir (0), dan akhir entri (0)
Catatan: Sebenarnya tidak ada status. Harus ada garis dari 'c' -> 'b' yang ditandai dengan status '1' karena pembatas kedua yang lolos berarti pembatas pertama masih terbuka. Bahkan, mungkin akan lebih baik untuk menggambarkannya sebagai transisi lain. Menciptakan ini adalah seni, tidak ada cara yang benar.
Catatan: Ini juga kehilangan keadaan keluar tetapi pada data yang valid parser selalu berakhir pada transisi 'a' dan tidak ada negara yang mungkin karena tidak ada yang tersisa untuk diurai.
Perbedaan antara Negara dan Transisi:
Suatu negara terbatas, artinya hanya dapat disimpulkan sebagai satu hal.
Transisi mewakili aliran antar negara sehingga dapat berarti banyak hal.
Pada dasarnya, hubungan transisi state-> adalah 1 -> * (yaitu satu-ke-banyak). Negara mendefinisikan 'apa adanya' dan transisi mendefinisikan 'bagaimana penanganannya'.
Catatan: Jangan khawatir jika penerapan status / transisi tidak terasa intuitif, itu tidak intuitif. Butuh korespondensi yang luas dengan seseorang yang jauh lebih pintar daripada saya sebelum akhirnya saya memiliki konsep untuk bertahan.
Pseudo-Code:
csv = // csv input string
// init all state & data
state = 0
value = ""
entry = []
output = []
endOfValue() {
entry.push(value)
value = ""
}
endOfEntry() {
endOfValue()
output.push(entry)
entry = []
}
tokenizer = /("|,|\n|\r|[^",\r\n]+)/gm
// using the match extension of string.replace. string.exec can also be used in a similar manner
csv.replace(tokenizer, function (match) {
switch(state) {
case 0:
if(opening delimiter)
state = 1
break
if(new-line)
endOfEntry()
state = 0
break
if(un-delimited data)
value += match
state = 3
break
case 1:
if(second delimiter encountered)
state = 2
break
if(non-control char data)
value += match
state = 1
break
case 2:
if(escaped delimiter)
state = 1
break
if(separator)
endOfValue()
state = 0
break
if(newline)
endOfEntry()
state = 0
break
case 3:
if(separator)
endOfValue()
state = 0
break
if(newline)
endOfEntry()
state = 0
break
}
}
Catatan: Ini intinya, dalam praktiknya ada banyak lagi yang perlu dipertimbangkan. Misalnya, pengecekan kesalahan, nilai null, garis akhir kosong (yaitu yang valid), dll.
Dalam hal ini, negara adalah kondisi ketika blok pencocokan regex menyelesaikan iterasi. Transisi direpresentasikan sebagai pernyataan kasus.
Sebagai manusia, kita memiliki kecenderungan untuk menyederhanakan operasi tingkat rendah ke tingkat yang lebih tinggi abstrak tetapi bekerja dengan FSM yang bekerja dengan operasi tingkat rendah. Sementara negara dan transisi sangat mudah untuk dikerjakan secara individual, secara inheren sulit untuk memvisualisasikan semuanya sekaligus. Saya merasa paling mudah untuk mengikuti jalur eksekusi individu berulang-ulang sampai saya bisa melihat bagaimana transisi berlangsung. Ini seperti belajar matematika dasar, Anda tidak akan dapat mengevaluasi kode dari tingkat yang lebih tinggi sampai detail tingkat rendah mulai menjadi otomatis.
Selain: Jika Anda melihat implementasi yang sebenarnya, ada banyak detail yang hilang. Pertama, semua jalur yang mustahil akan menghasilkan pengecualian khusus. Seharusnya tidak mungkin mengenai mereka, tetapi jika ada kerusakan mereka akan benar-benar memicu pengecualian pada pelari uji. Kedua, aturan parser untuk apa yang diizinkan dalam string data CSV 'legal' cukup longgar sehingga kode yang diperlukan untuk menangani banyak kasus tepi tertentu. Terlepas dari fakta itu, ini adalah proses yang digunakan untuk mengejek FSM sebelum semua perbaikan bug, ekstensi, dan fine tuning.
Seperti kebanyakan desain, itu bukan representasi yang tepat dari implementasi tetapi menguraikan bagian-bagian penting. Dalam praktiknya, sebenarnya ada 3 fungsi parser berbeda yang berasal dari desain ini: pemisah garis csv-spesifik, parser garis tunggal, dan parser multi-garis lengkap. Mereka semua beroperasi dengan cara yang sama, mereka berbeda dalam cara mereka menangani karakter baris baru.