Tuesday, January 12, 2010

Regex

Regex kepanjangan dari Regular Expression, juga biasa disingkat RE, saya ingat mendapatkannya di Otomata atau Matematika Diskrit ketika kuliah. Pada dasarnya regex adalah suatu pola yang menggambarkan suatu text. Namanya muncul dari teori matematika. Kebanyakan dari post ini di copy dari http://www.regular-expressions.info, dan posting ini saya terjemahkan dari postingan saya sebelumnya Regex dalam Bahasa Inggris.

Regex adalah alat yang ampuh ketika kita sudah mempelajarinya, di project saya yg terdahulu menggunakan regex sebagai validator dari pojo-pojo saya dan sebagai validator dari nama file yg diapload ke aplikasi. Fyi, di aplikasi itu terdapat bagian upload file, dan file itu difilter terlebih dahulu oleh aplikasi, karena merupakan aplikasi report bank bulanan, jadi butuh mengetahui bahwa file yang diupload itu di bulan apa, maksud saya file itu harus diberi nama dengan pola tahun dan bulan di belakangnya.

11 Karakter Khusus (metacharacters)
- kurung kotak [
- backslash \
- the caret ^
- dollar $
- titik .
- the vertical bar or pipe symbol |
- tanda tanya ?
- tanda bintang *
- tambah +
- kurung buka (
- kurung tutup )
Jika ingin menggunakan metacharachter itu perlu didahului backslash, atau dua backslash kalo di program java.

Karakter yang tidak bisa dicetak
- \t untuk mencocoki karakter tab (ASCII 0x09)
- \r untuk enter (0x0D)
- \n untuk baris baru (0x0A)
- lebih jauh ada \a (bell, 0x07), \e (escape, 0x1B), \f (form feed, 0x0C) dan \v (vertical tab, 0x0B).
Dan sekali lagi kita butuh double backslash di java, satu backslash untuk regex biasa.

Mesin Regex
Merupakan software yang bisa memproses regex, mencoba untuk mencocokkan pola dari string yang diberikan.

Terdapat dua macam mesin regex: mesin text-directed dan mesin regex-directed. Jeffrey Friedl menyebutnya mesin DFA and NFA. http://www.regular-expressions.info berdasarkan mesin regex-directed, dan situs itu berdasarkan bahasa pearl. Pertama, saya mengira java juga regex-directed, tapi ternyata tidak, saya membuat program mini seperti yang ditulis di situs untuk mengetahui apakah java itu text-directed atau regex-directed, dan ternyata text-directed. Regexnya berupa regex|regex not dan stringnya berupa regex not, jika hasil pencocokannya regex maka mesin regex-directed. Jika hasilnya berupa regex not maka tex-directed. Dibawah ini code program saya dan outputnya berupa "Found 'regex not' at position 0":


public static void main(String[] args) {
Pattern p = Pattern.compile ( "regex|regex not", Pattern.CASE_INSENSITIVE ) ;
String text = "regex not";
Matcher m = p.matcher ( text ) ;
if ( m.matches() ) {
System.out.println ( "Found '" + m.group ( 0 ) + "' at position " + m.start ( 0 ) ) ;
}
else {
System.err.println("No found match");
}
}

Regex-directed selalu mengembalikan leftmost match, jika kita menerapkan regex cat ke string He captured a catfish for his cat, mesin akan mencoba mencocokkan token yang pertama di regex c ke karakter pertama di match H. Dan gagal. Tidak ada kemungkinan permutasi lain di regex ini, karena terdiri atas urutan karakter huruf. Sehingga mesin regex mencoba untuk mencocokkan c dengan e. Gagal juga, sampai mencocokkan dengan spasi. Sampai karakter keempat pencocokkan, c mencocoki c. Mesin akan mencoba mencocokkan token kedua a kekarakter kelima, a. Sukses juga. Namun kemudian t gagal mencocoki p. Di titik itu mesin mengetahui bahwa regex tidak bisa match dimulai dari karakter keempat. Maka berlanjut ke karakter kelima: a. Lagi c gagal cocok dan mesin berlanjut. Di posisi karakter ke15 dalam pencocokan, c bertemu c dan match. Mesin kemudian mencoba untuk match regex pada karakter 15 dan menemukan bahwa a match dengan a dan t match dengan t.

Regex tadi bisa match diposisi karakter 15. Mesin melaporkan match. Kemudian akan melaporkan tiga karakter dari catfish sebagai match yang valid. Dari titik ini mesin tidak pernah lagi memproses untuk melihat apakah ada match yang lebih baik.

Tapi di java yang termasuk text-directed, dia melaporkan tidak ada yg match, karena dia string yang dicocokkan ke regex, saya sudah mencoba regex cat dengan He captured a catfish for his cat, dan dia melaporkan bahwa tidak ada match, dia hanya menerima string cat.

Himpunan Karakter atau Kelas Karakter "[ ]"
Dengan himpunan karakter kita bisa memberi tahu mesin regex untuk match hanya satu dari beberapa karakter. Hanya dengan cara menempatkan karakter yang ingin dicocokkan diantara kurung kotak. Kita bisa menggunakan tanda hubung didalam kelas karakter untuk menentukan range karakter. [0-9] mencocokkan satu digit antara 0 sampai 9.

- \d untuk [0-9]
- \w singkatan untuk "word character", biasanya [A-Za-z0-9_]. Termasuk didalamnya garis bawah dan angka.
- \s untuk spasi
- Negasi, \D sama dengan [^\d], \W is untuk [^\w] and \S untuk [^\s]

Hati-hati ketika menggunakan negasi didalam kurung kotak. [\D\S] tidak sama dengan [^\d\s]. Yang terakhir akan match semua karakter yang bukan angka atau spasi. Sehingga akan match x, tapi tidak 8. Yang sebelumnya akan match semua karakter yang bukan angka, atau bukan spasi. Karena angka bukan spasi dan spasi bukan angka, [\D\S] akan match dengan semua karakter, angka, spasi atau lainnya.

Jika kita mengulangi satu kelas karakter dengan menggunakan operator ?, * atau +, kita akan mengulangi seluruh kelas karakter, dan bukan hanya karakter yang match. Regex [0-9]+ bisa match 837 juga 222. Tapi jika kita ingin mengulangi karakter, bukannya class, maka dibutuhkan backreference. ([0-9])\1+ akan match 222 dan bukan 837.

Negasi Kelas Karakter
Menulis ^ setelah kurung kotak akan menegasi kelas karakter. Hasilnya kelas karakter akan match dengan karakter apapun yang tidak ada di kelas karakter. Penting untuk diingat bahwa negasi kelas karakter masih harus matsh satu karakter. q[^u] tidak berarti: "sebuah q tidak diikuti sebuah u". Tapi artinya "Sebuah q diikuti oleh satu karakter yang bukan u". Ini akan tidak match q di Iraq. Akan match dengan q dan sapsi setelah q di Iraq is a country. Begitu pula spasi akan menjadi bagian dari match keseluruhan, karena merupakan "karakter yang bukan u" yang match dengan negasi kelas karakter regex diatas.

Metakarakter didalam Kelas Karakter
Bahwa karakter khusus atau metakarakter didalam kelas karakter adalah tutup kurung kotak (]), backslash (\), karet (^), dan tanda hubung (-). Metakarakter reguler adalah karakter normal didalam kelas karakter, dan tidak butuh didahului dengan backslash. Untuk mencari bintang atau plus, gunakan [+*]. Regex kamu akan berfungsi baik jika diberi backslash metakarakter didalam kelas karakter, tapi melakukannya akan membuat susah untuk dibaca.

Untuk memasukkan backslash sebagai karakter tanpa maksud khusus didalam kelas karakter, perlu diberi backslash lagi. [\\X] match sebuah backslash atau sebuah x. Tutup kurung kotak (]), karet (^) dan tanda hubung (-) bisa dimasukkan dengan ditambah backslash, atau dengan menempatkannya dalam posisi dimana mereka tidak butuh arti khusus. Direkomendasikan metode terakhir karena lebih mudah untuk membacanya. Untuk memasukkan karet, tempatkan dimanapun selain setelah kurung buka. [x^] match x atau karet. Kamu bisa meletakkan kurung tutup setelah kurung buka atau karet negasi. []x][^]x] match semua karakter yang bukan kurung tutup atau x.Tanda hubung bisa diletakkan setelah kurung buka, sebelum kurung tutup, atau setelah karet negasi. Dua duanya [-x] dan [x-] match x atau tanda hubung.

Dot Match (Hampir) Semua Karakter
Dot match satu karakter, tanpa memperhatikan karakter apa itu. Hanya ada satu pengecualian yaitu karakter baris baru. Sehingga dot kependekan dari [^\n].

Anchor

Anchor tidak match karakter apapun, dia match satu posisi sebelum, setelah, atau diantara karakter. Dan mereka match yang Zero-Length.
• karet match posisi sebelum karakter pertama dari string
• $ match setelah karakter terakhir dari string
• \b match di posisi yang disebut "word boundary"

Menerapkan ^a dengan abc match a. ^b tidak match dengan abc karena b tidak bisa dimatch setelah awal dari string, yang dimatch oleh ^. Sedangkan c$ match c di abc, sedangkan a$ tidak match.

Ada empat posisi berbeda yang memenuhi kualifikasi sebagai word boundary:
• sebelum karakter pertama dari string, jika karakter pertama adalah \w
• setelah karakter terakhir dari string, jika karakter terakhir adalah \w
• Diantara sebuah \w dan sebuah karakter non-word mengikuti setelah \w
• Diantara sebuah \W dan sebuah \w mengikuti setelah \W

Di mesin regex, \bis\b akan match This island is beautiful. Spasi adalah karakter non-word, dan yang lainnya karakter word. Mesin dengan sukses match kata is dalam string. Jika ktia menggunakan regex is, ini akan match dengan is di This.

Alternatif "|"
operator alternatif memiliki presedence terbawah dari semua operator regex. Yang mana memberi tahu regex mesin untuk match apakah semua di sebelah kiri vertical bar, atau semua sebelah kanan vertical bar. Jika kita ingin membatasi jangkauan alternasi, maka butuh kurung untuk grouping.

Opsional "?"
Bersifat greedy. Tanda tanya memberi mesin regex dua pilihan: coba match bagian dari tanda tanya, atau tidak mencoba match. Mesin akan selalu mencoba match bagian itu. Hanya jika hal ini menyebabkan keseluruhan regex untuk gagal, maka akan terus mengabaikan bagian dari tanda tanya itu.

Akibatnya adalah jika menerapkan regex Feb 23(rd)? ke string Feb 23rd, match akan selalu Feb 23rd dan bukan Feb 23. Kamu bisa membuat tanda tanya lazy (mematikan greediness) dengan meletakkan tanda tanya kedua setelah yang pertama.

"?,*, +,{}"
- ? opsional, 1 or 0
- * 0 atau lebih repetisi
- + 1 atau lebih repetisi
- {} membatasi repetisi, sintaksnya {min,max}, jika kita ingin membatasi n repetisi maka sintaksnya adalah {n}

Mereka greedy, misalnya kita inginmenerima tag html dengan regex: <.+>. Ketika kita coba test dengan satu string seperti This is a first test. Kita mungkin mengharapkan regex untuk match dan ketika melanjutkan setelah match itu. Namun tidak. Regex akan match dulu. Yang jelas bukan yang kita inginkan. Alasannya adalah bahwa plus bersifat greedy. Yang mana plus menyebabkan mesin regex untuk mengulang token sebelumnya sesering mungkin. Kecuali jika regex gagal, maka mesin regex akan backtrack. Yang mana, hal ini kembali ke plus, membuatnya memberi iterasi yang terakhir, dan memprosesnya dengan sisa regex.

Untuk memperbaikinya:
• laziness, dengan menambahkan "?", maka regex akan menjadi <.+?>
• solusi yang lebih baik tanpa backtracking: Menggunakan greedy plus dan negasi kelas karakter <[^>]+>

Grouping dan Backreference "()"
Selain mengelompokkan regex, tanda kurung juga membuat backreference. Backreference menyimpan bagian dari string yang match dengan bagian dari regex didalam tanda kurung.

([a-c])x\1x\1 match axaxa, bxbxb and cxcxc. \1 disebut backreference ([a-c]). dan di java butuh dua backslash. Contoh yang lainnya adalah <([A-Z][A-Z0-9]*)\b[^>]*>.*? yang match sepasang buka dan tutup tag HTML, dan text diantaranya, seperti text bold atau italic.

Java juga mendukung forward reference, yaitu: Kita bisa menggunakan backreference untuk satu grup yang muncul setelah regex. Biasanya hanya berguna jika mereka didalam group yang berulang. Misalnya (\2two|(one))+ akan match oneonetwo.