Groovy menyertakan dua mesin template (template engine) dalam distribusi intinya, yaitu SimpleTemplateEngine untuk teks dan MarkupTemplateEngine untuk markup terstruktur. Setelah membaca topik ini, pembaca dapat membuat template dari string maupun berkas, mengisinya dengan model data, merender laporan teks dan dokumen HTML, serta mengatur escaping dan bentuk keluarannya melalui TemplateConfiguration. Escaping sendiri berarti pengubahan karakter khusus seperti < menjadi <. Kedua mesin berada di paket groovy.text yang sudah menjadi bagian pemasangan Groovy, sehingga seluruh contoh di topik ini dapat langsung dijalankan tanpa dependensi tambahan.
SimpleTemplateEngine untuk teks#
Kelas SimpleTemplateEngine mengubah kerangka teks yang memuat placeholder, yaitu tempat penulisan nilai, menjadi objek Template yang siap diisi data. Placeholder berbentuk <%= ekspresi %> mencetak nilai ekspresi ke keluaran, sedangkan tanda <% pernyataan %> menjalankan kode tanpa mencetak apa pun. Template dibuat dengan createTemplate, diisi lewat make yang menerima satu map dan mengembalikan objek Writable, lalu dirender dengan memanggil toString() pada objek tersebut.
Skrip berikut membuat surat pengumuman piket dari sebuah string. Data penerima tidak direkatkan ke dalam teks, melainkan dikirim sebagai model saat make dipanggil, sehingga satu template dapat dipakai berulang untuk banyak penerima.
import groovy.text.SimpleTemplateEngine
def teks = '''Undangan piket kelas
Kepada : <%= nama %>
Kelas : <%= kelas %>
Hari : <%= hari %>
<%= nama %> terjadwal piket kelas <%= kelas %> pada hari <%= hari %>.
Mohon membawa alat piket masing-masing.
Wali kelas
'''
def template = new SimpleTemplateEngine().createTemplate(teks)
def surat = template.make(nama: 'Budi', kelas: 'XI-A', hari: 'Senin')
print surat.toString()Undangan piket kelas
Kepada : Budi
Kelas : XI-A
Hari : Senin
Budi terjadwal piket kelas XI-A pada hari Senin.
Mohon membawa alat piket masing-masing.
Wali kelasMetode createTemplate menerima String, Reader, File, maupun URL, sehingga sumber template bisa berupa teks di dalam skrip atau berkas tersendiri. Pemanggilan make tanpa argumen sah untuk template yang tidak membutuhkan data. Perlu dicatat bahwa isi template diterjemahkan menjadi skrip Groovy sungguhan, sehingga kode di dalamnya benar-benar dieksekusi ketika perenderan berlangsung.
Scriptlet dalam template#
Bagian <% %> yang disebut scriptlet memuat pernyataan Groovy biasa, sehingga perulangan dan percabangan dapat ditulis langsung di dalam template. Teks yang berada di antara scriptlet pembuka dan penutup menjadi bagian dari blok tersebut, sehingga teks itu dicetak ulang untuk setiap data. Variabel yang dideklarasikan di dalam satu scriptlet juga tetap terbaca di scriptlet berikutnya karena seluruhnya menyatu dalam satu skrip.
Skrip berikut mencetak daftar jadwal piket dengan perulangan each, lalu menampilkan jumlah sesinya lewat ekspresi jadwal.size().
import groovy.text.SimpleTemplateEngine
def teks = '''Jadwal piket <%= periode %>
<% jadwal.each { s -> %>- <%= s.hari %>: <%= s.petugas %>
<% } %>Total sesi: <%= jadwal.size() %>
'''
def template = new SimpleTemplateEngine().createTemplate(teks)
def jadwal = [[hari: 'Senin', petugas: 'Ali'], [hari: 'Selasa', petugas: 'Budi']]
print template.make(periode: 'minggu ke-3', jadwal: jadwal).toString()Jadwal piket minggu ke-3
- Senin: Ali
- Selasa: Budi
Total sesi: 2Perhatikan tanda %> yang dirapatkan dengan teks sesudahnya, misalnya %>- pada baris perulangan. Baris yang hanya memuat scriptlet tidak muncul pada keluaran, tetapi baris baru di sekitarnya tetap merupakan teks template, sehingga penulisan yang longgar akan menimbulkan baris kosong pada keluaran. Percabangan memakai pola yang sama, yaitu scriptlet <% if (...) { %>, diikuti teks yang bersyarat, lalu scriptlet <% } %> sebagai penutup. Scriptlet juga dapat mendeklarasikan variabel, misalnya <% def jumlah = jadwal.size() %>, yang hasilnya dicetak lewat <%= jumlah %> di baris berikutnya.
MarkupTemplateEngine untuk HTML#
MarkupTemplateEngine pada subpaket groovy.text.markup memperlakukan template sebagai kode Groovy. Setiap tag ditulis sebagai pemanggilan metode, isi tag dikirim sebagai argumen atau blok kurung kurawal, dan penyarangan tag dinyatakan dengan menumpuk blok tersebut. Variabel model yang dikirim lewat make dapat dipanggil langsung sebagai variabel biasa, sedangkan teks lepas dicetak dengan metode yield. Template semacam ini dikompilasi statis oleh mesinnya, dan objek Template yang sudah dibuat dapat dipakai berulang kali dengan memanggil make bersama model yang berbeda, tanpa kompilasi ulang.
Skrip berikut merender daftar piket yang sama dengan bagian sebelumnya, kali ini sebagai HTML.
import groovy.text.markup.MarkupTemplateEngine
def template = new MarkupTemplateEngine().createTemplate('''html {
head { title('Jadwal piket') }
body {
h1('Jadwal piket minggu ke-3')
ul {
jadwal.each { s ->
li("${s.hari}: ${s.petugas}")
}
}
}
}''')
def hasil = template.make(jadwal: [[hari: 'Senin', petugas: 'Ali'], [hari: 'Selasa', petugas: 'Budi']])
print hasil.toString()<html><head><title>Jadwal piket</title></head><body><h1>Jadwal piket minggu ke-3</h1><ul><li>Senin: Ali</li><li>Selasa: Budi</li></ul></body></html>Keluaran berbentuk satu baris karena mesin ini secara default tidak menyisipkan baris baru maupun indentasi di antara tag. Browser tidak keberatan dengan bentuk demikian, tetapi keluaran yang panjang menjadi sulit diperiksa mata. Tag juga menerima atribut melalui argumen map, misalnya table(border: 1), sehingga struktur dokumen beserta atributnya tetap ditulis dalam kode Groovy yang sama. Penataan ulang keluaran dan opsi lainnya ditangani TemplateConfiguration, kelas konfigurasi yang dipakai pada contoh berikutnya.
Template dari berkas#
Template untuk laporan biasanya lebih panjang daripada contoh sejauh ini, sehingga menyimpannya sebagai berkas tersendiri lebih mudah dirawat. Berkas rekap.template berikut merupakan template MarkupTemplateEngine untuk laporan rekap nilai ujian. Isinya tetap kode Groovy, hanya saja letaknya terpisah dari skrip pemanggilnya.
html {
head { title('Rekap nilai ujian') }
body {
h1('Rekap nilai ujian')
table {
tr { th('Peserta'); th('Skor') }
nilai.each { n ->
tr {
td(n.nama)
td(n.skor)
}
}
}
}
}Skrip render-rekap.groovy menuliskan template tersebut bila berkasnya belum ada, sehingga contoh ini tetap dapat dijalankan berdiri sendiri. Isinya dibaca kembali lewat createTemplate yang menerima objek File. Pemisahan kedua berkas ini memungkinkan isi laporan disunting tanpa menyentuh skrip pemanggilnya, dan satu template dapat dipakai bersama oleh beberapa skrip.
// render-rekap.groovy: merender rekap.template menjadi laporan HTML
import groovy.text.markup.MarkupTemplateEngine
import groovy.text.markup.TemplateConfiguration
// tulis berkas template bila belum ada agar skrip dapat dijalankan sendiri
def berkasTemplate = new File('rekap.template')
if (!berkasTemplate.exists()) {
berkasTemplate.text = '''html {
head { title('Rekap nilai ujian') }
body {
h1('Rekap nilai ujian')
table {
tr { th('Peserta'); th('Skor') }
nilai.each { n ->
tr {
td(n.nama)
td(n.skor)
}
}
}
}
}'''
}
def konfigurasi = new TemplateConfiguration(autoIndent: true, autoNewLine: true)
def engine = new MarkupTemplateEngine(konfigurasi)
def template = engine.createTemplate(berkasTemplate)
def model = [nilai: [[nama: 'Ali', skor: 85], [nama: 'Budi', skor: 78], [nama: 'Citra', skor: 91]]]
print template.make(model).toString()<html>
<head><title>Rekap nilai ujian</title></head><body>
<h1>Rekap nilai ujian</h1><table>
<tr><th>Peserta</th><th>Skor</th></tr><tr>
<td>Ali</td><td>85</td>
</tr><tr>
<td>Budi</td><td>78</td>
</tr><tr>
<td>Citra</td><td>91</td>
</tr>
</table>
</body>
</html>TemplateConfiguration berperan menata bentuk keluaran. Opsi autoNewLine menyisipkan baris baru mengikuti letak kurung kurawal pada sumber template, sedangkan autoIndent menambahkan indentasinya, sehingga hasilnya terbaca sebagai dokumen bertingkat. Pemeriksaan exists() menjaga berkas template yang sudah ada tetap dipakai tanpa ditimpa, sedangkan skrip yang disalin sendirian tetap berjalan karena template ikut dituliskan. Contoh utuh ini dijalankan dengan perintah groovy render-rekap.groovy dari folder tempat kedua berkas berada, dan keluarannya siap ditulis ke berkas HTML.
Escaping otomatis dan TemplateConfiguration#
MarkupTemplateEngine menulis nilai model apa adanya secara default. Tanda < yang lolos tanpa di-escape dapat merusak struktur dokumen atau menyisipkan elemen yang tidak diinginkan, dan pada halaman web kondisi ini dikenal sebagai celah cross-site scripting (XSS). TemplateConfiguration menutup masalah itu lewat opsi autoEscape yang meng-escape setiap nilai model bertipe CharSequence secara otomatis.
Skrip berikut merender satu catatan yang sama dua kali, pertama dengan mesin default lalu dengan autoEscape aktif.
import groovy.text.markup.MarkupTemplateEngine
import groovy.text.markup.TemplateConfiguration
def isi = 'body { p(catatan) }'
def catatan = 'Ambang remedi: skor < 80'
def tanpaEscaping = new MarkupTemplateEngine().createTemplate(isi)
print tanpaEscaping.make(catatan: catatan).toString()
println ''
def denganEscaping = new MarkupTemplateEngine(new TemplateConfiguration(autoEscape: true)).createTemplate(isi)
print denganEscaping.make(catatan: catatan).toString()
println ''<body><p>Ambang remedi: skor < 80</p></body>
<body><p>Ambang remedi: skor < 80</p></body>Baris pertama memperlihatkan tanda < yang lolos mentah, sedangkan baris kedua menunjukkan nilai model yang sudah di-escape menjadi <. Opsi autoEscape hanya menyentuh nilai model bertipe CharSequence, sedangkan nilai lain seperti angka ditulis langsung tanpa diubah karena tidak membawa markah. Di dalam template, metode yield selalu meng-escape argumennya, sedangkan yieldUnescaped menulis apa adanya, sehingga penulisan nilai mentah selalu merupakan keputusan yang disengaja. Selain autoEscape, TemplateConfiguration juga menyediakan declarationEncoding yang dituliskan oleh xmlDeclaration pada header dokumen, useDoubleQuotes untuk mengganti tanda kutip tunggal atribut menjadi kutip ganda, serta expandEmptyElements untuk memperluas bentuk elemen kosong.
Memilih mesin template#
SimpleTemplateEngine dan MarkupTemplateEngine menyelesaikan pekerjaan yang berbeda, dan pilihannya mengikuti bentuk keluaran yang diinginkan. SimpleTemplateEngine tepat untuk surat, laporan teks, dan kerangka pesan yang strukturnya sederhana, sedangkan MarkupTemplateEngine tepat untuk dokumen HTML dan XML yang sarat tag serta membutuhkan pengendalian escaping. Tabel berikut merangkum perbedaan keduanya.
| Aspek | SimpleTemplateEngine | MarkupTemplateEngine |
|---|---|---|
| Paket | groovy.text | groovy.text.markup |
| Bentuk template | teks dengan <%= %> dan <% %> | kode Groovy berupa pemanggilan tag |
| Keluaran utama | teks bebas | HTML dan XML |
| Escaping | tidak ada, nilai ditulis langsung | diatur lewat TemplateConfiguration |
| Bentuk keluaran | mengikuti teks template | diatur autoIndent dan autoNewLine |
Keduanya sama-sama mengeksekusi isi template sebagai kode Groovy, sehingga peringatan pada bagian pertama tetap berlaku. Perbedaan praktisnya ada pada sumber template. Template MarkupTemplateEngine biasanya berupa berkas milik pengembang, sedangkan teks template SimpleTemplateEngine sering disusun di dalam program, sehingga peluang masuknya template tak tepercaya lebih besar pada mesin teks.
Kesalahan umum#
Dua kesalahan yang paling sering muncul berkaitan dengan tanda kutip dan nama model. Keduanya baru terlihat saat skrip dijalankan, sehingga keluaran pada contoh berikut perlu dicermati.
Kesalahan pertama adalah menulis template di dalam string berkutip ganda. Pada string semacam itu, tanda ${} diinterpolasi oleh skrip luarnya, sehingga template yang diterima mesin sudah berisi nilai variabel skrip dan model yang dikirim menjadi sia-sia. Kesalahan seperti ini sulit disadari karena tidak ada error yang muncul.
import groovy.text.SimpleTemplateEngine
def nama = 'Ahmad'
def teks = "Halo ${nama}!"
def template = new SimpleTemplateEngine().createTemplate(teks)
print template.make(nama: 'Ali').toString()
println ''
print 'Keluaran yang diminta: Halo Ali!'Halo Ahmad!
Keluaran yang diminta: Halo Ali!Perbaikannya adalah menulis template dengan tanda kutip tunggal, misalnya '''...''' untuk teks beberapa baris, sehingga interpolasi ${} baru terjadi di dalam template. ${ekspresi} memang dikenali SimpleTemplateEngine sebagai bentuk lain dari <%= ekspresi %>, sehingga pemakaian tanda dolar pada template sah selama berada di sisi yang benar.
Kesalahan kedua adalah kunci model yang tidak ada. Ekspresi pada template baru dihitung saat perenderan, sehingga kunci yang salah eja lolos dari kompilasi dan baru memunculkan error ketika objek Writable dirender.
import groovy.text.SimpleTemplateEngine
def template = new SimpleTemplateEngine().createTemplate('Nilai <%= skor %> belum terisi.')
def writable = template.make()
try {
writable.toString()
} catch (MissingPropertyException e) {
println "Error: properti model '${e.property}' tidak ada."
}Error: properti model 'skor' tidak ada.Perbaikannya adalah menyamakan kunci model dengan nama yang dipakai template. MarkupTemplateEngine berperilaku berbeda pada kasus yang sama, yaitu kunci map yang tidak ada diperlakukan sebagai null, sehingga tag yang memakainya dirender kosong tanpa satu pun pesan error, misalnya <p/> untuk p(nama) yang tidak terisi. Karena perbedaan itu, keluaran kosong pada laporan markup juga perlu dicurigai sebagai nama variabel yang keliru.
Ringkasan#
- SimpleTemplateEngine mengubah teks berplaceholder
<%= %>dan scriptlet<% %>menjadi Template, yang diisi lewatmake(Map)dan dirender lewat objekWritable. - MarkupTemplateEngine memperlakukan template sebagai kode Groovy berupa pemanggilan tag, menerima model sebagai variabel biasa, dan menghasilkan HTML yang bentuknya diatur TemplateConfiguration.
- Escaping nilai model tidak aktif secara default dan diaktifkan lewat
autoEscape, sedangkanyieldselalu meng-escape danyieldUnescapedmenulis tanpa di-escape. - Isi template dieksekusi sebagai kode Groovy, sehingga template hanya boleh berasal dari sumber yang dikendalikan dan data dari luar dikirim sebagai model.
Referensi#
- Template engines — Apache Groovy — dokumentasi resmi kerangka template Groovy yang membahas SimpleTemplateEngine, MarkupTemplateEngine, dan TemplateConfiguration.
- SimpleTemplateEngine (Groovy 5.1.3) — Apache Groovy — dokumentasi API kelas SimpleTemplateEngine beserta bentuk sumber template yang didukung createTemplate.
- MarkupTemplateEngine (Groovy 5.1.3) — Apache Groovy — dokumentasi API mesin template markup beserta metode yang tersedia di dalam template.