Cara terbaik untuk mem-parsing file

9

Saya mencoba mencari solusi yang lebih baik untuk membuat parser ke beberapa format file terkenal di luar sana seperti: EDIFACT dan TRADACOMS .

Jika Anda tidak terbiasa dengan standar ini maka lihat contoh ini dari Wikipedia:

Lihat di bawah untuk contoh pesan EDIFACT yang digunakan untuk menjawab permintaan ketersediaan produk: -

UNA:+.? '
UNB+IATB:1+6XPPC+LHPPC+940101:0950+1'
UNH+1+PAORES:93:1:IA'
MSG+1:45'
IFT+3+XYZCOMPANY AVAILABILITY'
ERC+A7V:1:AMD'
IFT+3+NO MORE FLIGHTS'
ODI'
TVL+240493:1000::1220+FRA+JFK+DL+400+C'
PDI++C:3+Y::3+F::1'
APD+714C:0:::6++++++6X'
TVL+240493:1740::2030+JFK+MIA+DL+081+C'
PDI++C:4'
APD+EM2:0:130::6+++++++DA'
UNT+13+1'
UNZ+1+1'

Segmen UNA adalah opsional. Jika ada, itu menentukan karakter khusus yang akan digunakan untuk menafsirkan sisa pesan. Ada enam karakter yang mengikuti UNA dalam urutan ini:

  • pemisah elemen data komponen (: dalam sampel ini)
  • pemisah elemen data (+ dalam sampel ini)
  • pemberitahuan desimal (. dalam sampel ini)
  • rilis karakter (? dalam sampel ini)
  • dipesan, harus spasi
  • terminator segmen ('dalam sampel ini)

Seperti yang Anda lihat itu hanya beberapa data yang diformat dengan cara khusus menunggu untuk diuraikan (seperti file XML ).

Sekarang sistem saya dibangun di PHP dan saya bisa membuat parser menggunakan ekspresi reguler untuk setiap segmen, tetapi masalahnya tidak semua orang mengimplementasikan standar dengan sempurna.

Beberapa pemasok cenderung mengabaikan segmen dan bidang opsional sepenuhnya. Orang lain mungkin memilih untuk mengirim lebih banyak data daripada yang lain. Itu sebabnya saya terpaksa membuat validator untuk segmen dan bidang untuk menguji apakah file itu benar atau tidak.

Anda dapat membayangkan mimpi buruk ekspresi reguler yang saya alami sekarang. Selain itu setiap pemasok membutuhkan banyak modifikasi pada ekspresi reguler yang saya cenderung buat parser untuk setiap pemasok.


Pertanyaan:

1- Apakah ini praktik terbaik untuk mem-parsing file (menggunakan ekspresi reguler)?

2- Apakah ada solusi yang lebih baik untuk mem-parsing file (mungkin ada solusi yang sudah jadi di luar sana)? Apakah dapat menampilkan segmen apa yang hilang atau jika file rusak?

3 - Jika saya harus membuat parser saya, pola atau metodologi desain apa yang harus saya gunakan?

Catatan:

Saya membaca di suatu tempat tentang yacc dan ANTLR, tetapi saya tidak tahu apakah mereka cocok dengan kebutuhan saya atau tidak!

Songo
sumber
Setelah melihat tata bahasa EDIFACT ini , parser dan pustaka (Java) saya ingin tahu apakah menggunakan lexer / parser akan berhasil. Jika itu aku, aku akan mencoba combinator pengurai terlebih dahulu. :)
Guy Coder

Jawaban:

18

Yang Anda butuhkan adalah parser sejati. Ekspresi reguler menangani lexing, bukan parsing. Yaitu, mereka mengidentifikasi token dalam aliran input Anda. Parsing adalah konteks token, yaitu IE yang pergi ke mana dan dalam urutan apa.

Alat parsing klasik adalah yacc / bison . Lexer klasik adalah lex / flex . Karena php memungkinkan untuk mengintegrasikan kode C , Anda dapat menggunakan flex dan bison untuk membangun parser Anda, minta php menyebutnya pada file input / stream, dan kemudian dapatkan hasilnya.

Ini akan sangat cepat , dan jauh lebih mudah untuk dikerjakan setelah Anda memahami alat-alatnya . Saya sarankan membaca Lex dan Yacc 2nd Ed. dari O'Reilly. Sebagai contoh, saya telah membuat proyek flex dan bison di github , dengan makefile. Dapat dikompilasi silang untuk windows jika perlu.

Ini adalah kompleks, tetapi karena Anda tahu, apa yang Anda perlu lakukan adalah kompleks. Ada banyak "hal" yang harus dilakukan untuk parser yang berfungsi dengan baik, dan flex dan bison berurusan dengan bit mekanik. Jika tidak, Anda menemukan diri Anda dalam posisi yang tidak diinginkan dari penulisan kode pada lapisan abstraksi yang sama dengan perakitan.

Spencer Rathbun
sumber
1
+1 Jawaban yang bagus, terutama mengingat bahwa ia disertai dengan sampel parser.
Caleb
@caleb terima kasih, saya sering bekerja dengan flex / bison, tetapi ada sedikit contoh yang layak (baca: kompleks). Ini bukan parser terbaik, karena tidak ada banyak komentar, jadi silakan mengirimkan pembaruan.
Spencer Rathbun
@SpencerRathbun terima kasih banyak atas jawaban dan contoh terperinci Anda. Saya tidak tahu apa tentang terminologi yang Anda sebutkan (yacc / bison, lex / flex, ... dll) karena saya pengalaman saya terutama tentang pengembangan web. Apakah "Lex dan Yacc 2nd Ed" cukup bagi saya untuk memahami segalanya dan membangun parser yang bagus? atau adakah topik dan bahan lain yang harus saya bahas terlebih dahulu?
Songo
@songo Buku ini mencakup semua perincian yang relevan dan cukup singkat, mencakup sekitar 300 halaman ukuran sedang. Itu tidak mencakup menggunakan c, atau desain bahasa . Untungnya, ada banyak referensi c yang tersedia, seperti K&R Bahasa Pemrograman C dan Anda tidak perlu merancang bahasa, cukup ikuti standar yang telah Anda rujuk. Harap dicatat bahwa membaca sampul depan disarankan, karena penulis akan menyebutkan sesuatu sekali, dan menganggap jika Anda membutuhkannya Anda akan kembali dan membaca kembali. Dengan begitu Anda tidak ketinggalan apa pun.
Spencer Rathbun
Saya tidak berpikir lexer standar dapat menangani pemisah dinamis, yang dapat ditentukan oleh garis UNA. Jadi setidaknya Anda memerlukan lexer dengan karakter runtime-customizable untuk 5 pemisah.
Kevin
3

Aduh .. pengurai 'benar'? mesin negara ??

maaf tapi saya sudah dikonversi dari akademik ke hacker sejak saya mulai pekerjaan saya .. jadi saya akan mengatakan ada cara yang lebih mudah .. walaupun mungkin tidak 'halus' secara akademis :)

Saya akan mencoba menawarkan pendekatan alternatif yang beberapa mungkin setuju atau tidak setuju tetapi itu BISA sangat praktis dalam lingkungan kerja.

Saya akan;

loop every line
   X = pop the first 3 letters of line
   Y = rest of line
   case X = 'UNA':
       class init (Y)

dari sana saya akan menggunakan kelas untuk tipe data. memisahkan komponen dan elemen pemisah dan beralih ke array yang dikembalikan.

Bagi saya, ini adalah penggunaan kembali kode, OO, kohesi rendah dan sangat modular .. dan mudah untuk debug dan program. lebih sederhana lebih baik.

untuk mem-parsing file Anda tidak perlu mesin negara atau sesuatu yang sepenuhnya rumit .. mesin negara sangat cocok untuk mengurai kode, Anda akan terkejut melihat betapa kuatnya kode pseduo di atas dapat ketika digunakan dalam konteks OO.

ps. Saya telah bekerja dengan file yang sangat mirip sebelumnya :)


Lebih banyak kode semu yang diposting di sini:

kelas

UNA:

init(Y):
 remove ' from end
 components = Y.split(':') 
 for c in components
     .. etc..

 getComponents():
   logic..
   return

 getSomethingElse():
   logic..
   return

class UNZ:
   ...

Parser(lines):

Msg = new obj;

for line in lines
   X = pop the first 3 letters of line
   Y = rest of line
   case X = 'UNA':
      Msg.add(UNA(Y))

msg.isOK = true
return Msg

Anda bisa menggunakannya seperti ini ..

msg = Main(File.getLines());
// could put in error checking
// if msg.isOK:
msg.UNA.getSomethingElse();

dan katakan Anda memiliki lebih dari satu segmen .. gunakan antrian untuk menambahkannya dan dapatkan yang pertama, kedua dll. sesuai kebutuhan. Anda benar-benar hanya mewakili pesan ke objek dan memberikan metode objek untuk memanggil data. Anda dapat mengambil keuntungan dari ini dengan juga menciptakan metode khusus .. untuk warisan .. baik itu pertanyaan yang berbeda dan saya pikir Anda dapat dengan mudah menerapkannya jika Anda memahaminya

Ross
sumber
3
Saya telah melakukan itu sebelumnya, dan menemukan itu tidak cukup untuk apa pun di luar satu atau dua kasus recognize X token and do Y. Tidak ada konteks, Anda tidak dapat memiliki banyak status, melewati beberapa kasus sepele kode, dan penanganan kesalahan sulit. Saya menemukan bahwa saya membutuhkan fitur-fitur ini di dunia nyata di hampir semua kasus. Itu meninggalkan kesalahan di dalamnya sebagai kompleksitas tumbuh. Bagian tersulit adalah menyiapkan kerangka, dan mempelajari cara alat beroperasi. Lewati itu dan cepat untuk menyiapkan sesuatu.
Spencer Rathbun
itu adalah pesan, status apa yang Anda butuhkan? kelihatannya pesan seperti itu, yang disusun dalam struktur komposit dan segmen akan cocok dengan pendekatan OO ini dengan sempurna. penanganan kesalahan dilakukan per kelas dan dilakukan dengan benar Anda dapat membangun parser yang sangat efisien dan dapat dikembangkan. pesan seperti ini cocok untuk kelas dan fungsi terutama ketika banyak vendor mengirim rasa yang berbeda dari format yang sama. Contohnya adalah fungsi dalam kelas UNA yang mengembalikan nilai tertentu untuk vendor tertentu.
Ross
@Ross jadi pada dasarnya Anda akan memiliki "kelas UNA" untuk segmen "UNA" dan di dalamnya akan ada metode parsing untuk masing-masing vendor ( parseUNAsegemntForVendor1(), parseUNAsegemntForVendor2(), parseUNAsegemntForVendor3(), ... dll), kan?
Songo
2
@Ross Ada bagian pada pesan, valid pada titik yang berbeda selama parsing. Itulah keadaan yang saya bicarakan. Desain OO pintar, dan saya tidak mengatakan itu tidak akan berhasil . Saya mendorong flex dan bison karena seperti konsep pemrograman fungsional, mereka cocok lebih baik daripada alat lain, tetapi kebanyakan orang percaya mereka terlalu rumit untuk repot belajar.
Spencer Rathbun
@Songo .. tidak, Anda akan mengurai secara independen dari vendor (kecuali Anda yang baru). parse akan berada di INIT kelas. Anda mengubah pesan Anda menjadi objek data berdasarkan aturan yang sama yang digunakan untuk membuat pesan. Jika Anda perlu mengambil sesuatu dari pesan namun .. dan itu diwakili secara berbeda di vendor Anda maka Anda akan memiliki fungsi yang berbeda ya .. Tapi mengapa begitu? menggunakan kelas dasar dan memiliki kelas terpisah untuk setiap vendor, hanya mengesampingkan bila perlu, jauh lebih mudah. manfaatkan warisan.
Ross
1

Sudahkah Anda mencoba googling untuk "PHP EDIFACT"? Ini adalah salah satu hasil pertama yang muncul: http://code.google.com/p/edieasy/

Meskipun mungkin tidak cukup untuk kasus penggunaan Anda, Anda mungkin bisa mendapatkan beberapa ide darinya. Saya tidak suka kode dengan banyak bersarang untuk loop dan kondisi, tetapi ini mungkin permulaan.

Chiborg
sumber
1
Saya memeriksa banyak proyek di luar sana, tetapi masalahnya terutama dalam implementasi yang berbeda dari vendor yang menggunakan standar. Saya mungkin memaksa satu vendor untuk mengirimi saya segmen tertentu, tetapi saya dapat menganggapnya opsional untuk vendor lain. Itu sebabnya saya mungkin perlu membuat parser khusus saya sendiri.
Songo
1

Yah sejak Yacc / Bison + Flex / Lex disebutkan, saya mungkin juga memasukkan salah satu alternatif utama lainnya: parser combinators. Ini populer di pemrograman fungsional seperti dengan Haskell, tetapi jika Anda dapat antarmuka ke kode C Anda dapat menggunakannya dan, apa yang Anda tahu, seseorang menulis satu untuk PHP juga. (Saya tidak punya pengalaman dengan implementasi khusus itu, tetapi jika berfungsi seperti kebanyakan dari mereka, itu pasti cukup bagus.)

Konsep umum adalah bahwa Anda mulai dengan seperangkat parser kecil, mudah didefinisikan, biasanya tokenizer. Seperti Anda akan memiliki satu fungsi pengurai untuk masing-masing dari 6 elemen data yang Anda sebutkan. Kemudian Anda menggunakan kombinator (fungsi yang menggabungkan fungsi) untuk membuat parser yang lebih besar yang mengambil elemen yang lebih besar. Seperti segmen opsional akan menjadi optionalkombinator yang beroperasi pada pengurai segmen.

Tidak yakin seberapa baik kerjanya dalam PHP, tapi ini cara yang menyenangkan untuk menulis parser dan saya sangat menikmati menggunakannya dalam bahasa lain.

CodexArcanum
sumber
0

alih-alih mengutak-atik regex membuat mesin negara Anda sendiri

ini akan lebih mudah dibaca (dan dapat memiliki komentar yang lebih baik) dalam situasi non-sepele dan akan lebih mudah untuk debug bahwa kotak hitam yang regex

aneh ratchet
sumber
5
Catatan singkat, inilah yang dilakukan fleks dan bison di bawah tenda. Hanya mereka yang melakukannya dengan benar .
Spencer Rathbun
0

Saya tidak tahu apa yang ingin Anda lakukan dengan data ini setelahnya dan jika itu bukan godam untuk kacang, tapi saya punya pengalaman bagus dengan eli . Anda menggambarkan frasa leksikal dan kemudian sintaksis konkret / abstrak dan menghasilkan apa yang ingin Anda hasilkan.

Sebastian Bauer
sumber