Meski API modern beralih ke JSON, data engineer tetap bertemu XML di banyak tempat: layanan SOAP perbankan dan instansi pemerintahan, berkas konfigurasi warisan seperti pom.xml dan layout Android, sampai feed RSS/Atom dan sitemap.xml. XML (eXtensible Markup Language) menyimpan data sebagai pohon (tree) berisi element dan attribute — struktur yang kaya tetapi lebih bertele-tele daripada JSON. Chapter ini membedah struktur tersebut dengan module xml.etree.ElementTree, memakai data transaksi toko yang sama agar perbandingannya konsisten.
Struktur Tree: Element, Attribute, dan Teks #
Dokumen XML membentuk pohon bersarang. Satu element dibuka dengan tag seperti <item> dan ditutup dengan </item>; element boleh memiliki element anak, teks di dalamnya, dan attribute — pasangan kunci-nilai yang menempel pada tag pembuka, misalnya id="1". Element paling luar disebut root, dan setiap dokumen hanya boleh punya satu root. Aturan lengkapnya dijaga konsorsium W3C
.
Konvensi umumnya: attribute menyimpan metadata pengenal (nomor, kode), sedangkan element anak menyimpan isi data yang nilainya bisa panjang. Konvensi ini tidak dipaksakan spesifikasi, tetapi membuat dokumen lebih seragam.
Menulis XML dengan ElementTree #
Module ElementTree
membangun pohon secara programatik: ET.Element() membuat root, ET.SubElement() menempelkan anak, dan .text mengisi teks. Berikut penulisan tiga transaksi ke berkas transaksi.xml:
import xml.etree.ElementTree as ET
transaksi = [
{"id": 1, "kasir": "Budi", "produk": "Kopi Robusta", "qty": 2, "harga": 45000},
{"id": 2, "kasir": "Siti", "produk": "Teh Melati", "qty": 3, "harga": 15000},
{"id": 3, "kasir": "Dewi", "produk": "Susu UHT 1L", "qty": 2, "harga": 18000},
]
root = ET.Element("penjualan", toko="Toko Nusantara")
for t in transaksi:
item = ET.SubElement(root, "item", id=str(t["id"]))
ET.SubElement(item, "kasir").text = t["kasir"]
ET.SubElement(item, "produk").text = t["produk"]
ET.SubElement(item, "qty").text = str(t["qty"])
ET.SubElement(item, "harga").text = str(t["harga"])
pohon = ET.ElementTree(root)
ET.indent(pohon, space=" ")
pohon.write("transaksi.xml", encoding="utf-8", xml_declaration=True)
print(open("transaksi.xml", encoding="utf-8").read())Output yang Dihasilkan:
<?xml version='1.0' encoding='utf-8'?>
<penjualan toko="Toko Nusantara">
<item id="1">
<kasir>Budi</kasir>
<produk>Kopi Robusta</produk>
<qty>2</qty>
<harga>45000</harga>
</item>
<item id="2">
<kasir>Siti</kasir>
<produk>Teh Melati</produk>
<qty>3</qty>
<harga>15000</harga>
</item>
<item id="3">
<kasir>Dewi</kasir>
<produk>Susu UHT 1L</produk>
<qty>2</qty>
<harga>18000</harga>
</item>
</penjualan>Mari kita telaah baris-baris kode di atas:
- Baris 9 (
ET.Element("penjualan", toko="...")): membuat root bernamapenjualandengan attributetoko— attribute ditulis langsung sebagai keyword argument. - Baris 11 (
ET.SubElement(root, "item", id=str(t["id"]))): menempelkan elementitemsebagai anak root, membawa attributeidyang dikonversi ke string karena attribute XML selalu teks. - Baris 12-15 (
ET.SubElement(item, ...).text = ...): empat element anak (kasir,produk,qty,harga) diisi nilainya melalui properti.text— sekali lagi, angka wajib dikonversi ke string. - Baris 17-19 (
ET.ElementTreesampaipohon.write(...)):ET.indent()merapikan lekukan sejak Python 3.9, laluwrite()menyimpan berkas dengan deklarasi XML di baris pertama.
Parsing XML: parse, findall, findtext, dan get #
Membaca kembali dokumen memakai ET.parse() untuk berkas atau ET.fromstring() untuk string. Pencarian element anak dilakukan dengan findall(), teks anak pertama diambil findtext(), dan attribute dibaca lewat get():
import xml.etree.ElementTree as ET
pohon = ET.parse("transaksi.xml")
root = pohon.getroot()
print("root:", root.tag, "| atribut:", root.attrib)
for item in root.findall("item"):
total = int(item.findtext("qty")) * int(item.findtext("harga"))
print(item.get("id"), item.findtext("kasir"), item.findtext("produk"), total)Output yang Dihasilkan:
root: penjualan | atribut: {'toko': 'Toko Nusantara'}
1 Budi Kopi Robusta 90000
2 Siti Teh Melati 45000
3 Dewi Susu UHT 1L 36000Properti .tag mengembalikan nama element, .attrib berisi dictionary attribute, dan findall("item") memilih seluruh anak bernama item di satu level. Sama seperti CSV, seluruh nilai berupa teks — int() wajib sebelum berhitung.
Namespace dalam XML #
Namespace mencegah benturan nama antar dokumen yang digabung — title milik feed Atom dan title milik ekstensi pihak ketiga tidak akan tertukar. Konsekuensinya, tag element menjadi {alamat-namespace}nama di ElementTree:
import xml.etree.ElementTree as ET
feed = """<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Penjualan Toko Nusantara</title>
<entry>
<title>Struk 1</title>
</entry>
</feed>"""
root = ET.fromstring(feed)
print("tag root:", root.tag)
judul = root.find("{http://www.w3.org/2005/Atom}title")
print("judul feed:", judul.text)Output yang Dihasilkan:
tag root: {http://www.w3.org/2005/Atom}feed
judul feed: Penjualan Toko NusantaraDeklarasi xmlns pada root menjadikan seluruh anak bagian dari namespace Atom, sehingga pencarian find() harus memakai bentuk kurung kurawal penuh — penyebab paling umum pencarian mengembalikan None padahal element ada.
Kapan XML Masih Dipakai #
Empat wilayah ini membuat XML tetap hidup di pipeline data: layanan SOAP di sektor keuangan dan pemerintahan, konfigurasi build dan aplikasi warisan (pom.xml, web.config), feed publikasi (RSS, Atom, sitemap), dan format dokumen kantor berbasis OOXML. Perbandingan ringkas dengan JSON:
| Aspek | XML | JSON |
|---|---|---|
| Struktur | tree element dan attribute | object dan array |
| Tipe data | semuanya teks, divalidasi XSD | enam tipe bawaan |
| Ukuran pesan | tag pembuka-penutup memakan ruang | lebih ringkas |
| Namespace | dukungan bawaan | tidak ada |
| Ekosistem khas | SOAP, XSLT, feed, sitemap | REST API, konfigurasi modern |
XML kaya struktur tetapi boros ruang: setiap nilai dibungkus dua tag. Pipeline modern menuntut format biner yang ringkas dan ber-schema — dunia Avro.