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.

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:

python
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:

text
<?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:

  1. Baris 9 (ET.Element("penjualan", toko="...")): membuat root bernama penjualan dengan attribute toko — attribute ditulis langsung sebagai keyword argument.
  2. Baris 11 (ET.SubElement(root, "item", id=str(t["id"]))): menempelkan element item sebagai anak root, membawa attribute id yang dikonversi ke string karena attribute XML selalu teks.
  3. 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.
  4. Baris 17-19 (ET.ElementTree sampai pohon.write(...)): ET.indent() merapikan lekukan sejak Python 3.9, lalu write() 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():

python
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:

text
root: penjualan | atribut: {'toko': 'Toko Nusantara'}
1 Budi Kopi Robusta 90000
2 Siti Teh Melati 45000
3 Dewi Susu UHT 1L 36000

Properti .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:

python
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:

text
tag root: {http://www.w3.org/2005/Atom}feed
judul feed: Penjualan Toko Nusantara

Deklarasi 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:

AspekXMLJSON
Strukturtree element dan attributeobject dan array
Tipe datasemuanya teks, divalidasi XSDenam tipe bawaan
Ukuran pesantag pembuka-penutup memakan ruanglebih ringkas
Namespacedukungan bawaantidak ada
Ekosistem khasSOAP, XSLT, feed, sitemapREST 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.