Ders 03 / 25
Belge Nesne Modeli Kavramı
Sözcük birimi akışının düğüm ağacına çevrilmesi, yığıtın işleyişi, düğüm türleri ve kaynak metinle ağaç arasındaki kopukluk.
İçindekiler
Önceki ders ağaç kurma aşamasını bir cümleyle geçmişti: sözcük birimi akışı bir ağaca çevrilir. Bu ders o cümleyi açar. Ağaç, belgenin işlenmesinde bir ara ürün değil, asıl üründür: biçem ona uygulanır, yerleşim onun üzerinden hesaplanır, betikler onu okur ve değiştirir. Kaynak metin, ağaç kurulduktan sonra bir daha kullanılmaz.
Bu ağacın adı Belge Nesne Modeli (Document Object Model) — kısaca DOM’dur. Ad iki şeyi birden söyler: belgenin bir modelidir (kaynak metnin kendisi değil, ondan üretilmiş yapılı bir gösterim) ve nesnelerden oluşur (her düğüm sorgulanabilir ve değiştirilebilir bir birimdir).
Neden Ağaç
Veri Yapıları kursundaki Ağaç Terminolojisi dersinde ağaç, her düğümün tek bir ebeveyni ve sıralı çocukları olan yapı olarak tanımlanmıştı. İşaretlemenin ürettiği yapı tam olarak budur, çünkü işaretlemenin kendisi iç içedir: bir öge ya bütünüyle başka bir ögenin içinde yer alır ya da bütünüyle dışındadır. Kısmi örtüşme —bir ögenin yarısının içeride, yarısının dışarıda kalması— tanımlı değildir.
Bu kısıt, iç içeliği yığıtla izlemeyi olanaklı kılar. Veri Yapıları kursundaki Yığıtlar dersinde son giren ilk çıkar davranışı tanımlanmıştı; ağaç kurucu tam olarak bunu kullanır: açılan öge yığıta konur, kapanan öge yığıttan alınır, yeni düğüm her zaman yığıtın tepesindeki ögenin çocuğu olur.
Kurucu Algoritma
Aşağıdaki betik, sözcük birimi akışından ağaç kuran algoritmanın çekirdeğini gerçekleştirir. Önceki dersteki çözümleyiciye iki kural eklenmiştir: boş ögeler yığıta girmez, bazı ögeler başka bir öge açıldığında kendiliğinden kapanır.
// agac.mjs — birim akisindan dugum agaci kurar const BOS_ETIKET = new Set(["br", "hr", "img", "input", "link", "meta"]); const KAPATAN = { li: ["li"], p: ["p"], h1: ["p"], div: ["p"], ul: ["p"] }; function birimleriAyir(metin) { const birimler = []; let i = 0; while (i < metin.length) { const k = metin.indexOf("<", i); if (k === -1) { birimler.push({ tur: "metin", deger: metin.slice(i) }); break; } if (k > i) birimler.push({ tur: "metin", deger: metin.slice(i, k) }); const son = metin.indexOf(">", k); const ic = metin.slice(k + 1, son); if (ic.startsWith("!")) birimler.push({ tur: "belge-turu", deger: ic.slice(1) }); else if (ic.startsWith("/")) birimler.push({ tur: "kapanis", ad: ic.slice(1).toLowerCase() }); else { const bosluk = ic.search(/[\s/]/); birimler.push({ tur: "acilis", ad: (bosluk === -1 ? ic : ic.slice(0, bosluk)).toLowerCase() }); } i = son + 1; } return birimler; } function agacKur(metin) { const kok = { tur: "belge", ad: "#belge", cocuklar: [] }; const yigit = [kok]; const tepe = () => yigit[yigit.length - 1]; for (const birim of birimleriAyir(metin)) { if (birim.tur === "metin") { if (birim.deger.trim() === "") continue; tepe().cocuklar.push({ tur: "metin", deger: birim.deger.trim(), cocuklar: [] }); } else if (birim.tur === "acilis") { while (yigit.length > 1 && (KAPATAN[birim.ad] ?? []).includes(tepe().ad)) yigit.pop(); const dugum = { tur: "eleman", ad: birim.ad, cocuklar: [] }; tepe().cocuklar.push(dugum); if (!BOS_ETIKET.has(birim.ad)) yigit.push(dugum); } else if (birim.tur === "kapanis") { const yer = yigit.map((d) => d.ad).lastIndexOf(birim.ad); if (yer > 0) yigit.length = yer; } } return kok; } function yaz(dugum, derinlik = 0) { const girinti = " ".repeat(derinlik); if (dugum.tur === "metin") console.log(`${girinti}"${dugum.deger}"`); else console.log(`${girinti}${dugum.ad}`); for (const cocuk of dugum.cocuklar) yaz(cocuk, derinlik + 1); } function say(dugum, sayac = { eleman: 0, metin: 0, derinlik: 0 }, derinlik = 0) { if (dugum.tur === "eleman") sayac.eleman += 1; if (dugum.tur === "metin") sayac.metin += 1; sayac.derinlik = Math.max(sayac.derinlik, derinlik); for (const cocuk of dugum.cocuklar) say(cocuk, sayac, derinlik + 1); return sayac; } const belge = `<!doctype html> <html> <head><title>Kuzey Yamac Istasyonu</title></head> <body> <h1>Kuzey Yamac Istasyonu</h1> <p>Deniz seviyesinden 1840 metre yukseklikte. <ul> <li>Sicaklik <li>Bagil nem </ul> </body> </html>`; const agac = agacKur(belge); yaz(agac); console.log("---"); console.log(say(agac));
#belge
html
head
title
"Kuzey Yamac Istasyonu"
body
h1
"Kuzey Yamac Istasyonu"
p
"Deniz seviyesinden 1840 metre yukseklikte."
ul
li
"Sicaklik"
li
"Bagil nem"
---
{ eleman: 9, metin: 5, derinlik: 5 }
Kaynak metinde p ögesinin kapanış etiketi yoktur ve iki li ögesinin de yoktur. Ağaçta
buna karşın üçü de kapanmış, doğru yerlerde durmaktadır. Bunu sağlayan KAPATAN
tablosudur: ul açıldığında açık p kapanır, li açıldığında açık li kapanır. Bu
kurallar isteğe bağlı bir kolaylık değil, dilin tanımının parçasıdır; her ayrıştırıcı
aynılarını uygular.
Düğüm Türleri
Çıktıda üç tür düğüm görünür ve dördüncüsü belgenin kökünde durur.
Belge düğümü, ağacın köküdür. Bir ögeye karşılık gelmez; ağacın tamamının tutamağıdır.
Öge düğümleri, açılış etiketlerinden üretilir. Adı, öznitelikleri ve sıralı çocukları vardır.
Metin düğümleri, etiketler arasındaki karakterlerden üretilir. Çocukları yoktur; ağacın yaprağıdır. Yukarıdaki sayımdaki beş metin düğümü, belgedeki beş metin parçasına karşılık gelir.
Yorum düğümleri (<!-- ... --> yazımı), belgede durur ve çizilmez. Betikler bunları
okuyabilir; işaretlemeye gömülmüş bilgiyi gizli saymamak gerekir.
Sayımda eleman: 9 değeri, kaynak metindeki dokuz açılış etiketine karşılık gelir.
derinlik: 5, kökten en uzak yaprağa kadar geçilen kenar sayısıdır: belge → html →
body → ul → li → metin düğümü.
Kaynak Metinde Bulunmayan Ögeler
Yukarıdaki örnekte html, head ve body ögeleri kaynak metinde yazılıydı. Yazılmasalardı
da ağaçta olurlardı. Bu, önceki derste kurulan etiket–öge ayrımının en somut sonucudur:
ağaçtaki her ögenin kaynak metinde bir etiketi yoktur.
Ters yönlü bir sonuç da vardır: kaynak metindeki her etiket ağaçta bir ögeye karşılık
gelmez. Eşleşen açılışı olmayan bir kapanış etiketi yok sayılır; yukarıdaki algoritmada
lastIndexOf çağrısı -1 döndürdüğünde hiçbir şey yapılmaması bunun karşılığıdır.
Bu iki yönlü kopukluk, bir belgeyi doğrulamanın neden kaynak metne bakarak yapılamayacağını açıklar. Sorulması gereken soru “ne yazdım” değil, “ne kuruldu”dur.
Boş Ögeler
Bazı ögelerin içeriği olamaz ve bu nedenle kapanış etiketleri yoktur: br, hr, img,
input, link, meta ve birkaçı daha. Bunlara boş öge (void element) denir.
Algoritmada bu ögeler yığıta hiç konmaz; ağaca çocuk olarak eklenir ve yığıtın tepesi
değişmez.
Boş ögeler için <br/> biçiminde kapatma yazımı ayrıştırıcı tarafından kabul edilir ancak
bir etkisi yoktur; öge zaten boştur. Boş olmayan bir öge için aynı yazım —örneğin
<div/>— beklenen etkiyi yapmaz: öge kapanmaz, içerik onun içine akmayı sürdürür.
Ağaç Yaşayan Bir Yapıdır
Ağaç kurulduktan sonra donmaz. Betikler düğüm ekleyebilir, çıkarabilir, özniteliklerini değiştirebilir; her değişiklik biçem ve yerleşim aşamalarının ilgili bölümünü yeniden çalıştırır. Bu nedenle bir sayfanın belirli bir andaki ağacı ile sunucudan gelen kaynak metin birbirinden ayrılabilir: kaynak metin ilk durumu, ağaç o andaki durumu anlatır.
Bu kursta betik yazılmayacak; ancak yazılan işaretlemenin doğrudan bir veri yapısına çevrildiğini bilmek, sonraki kursların dayanağıdır. İşaretleme yazmak, bir metin dizmek değil, bir ağaç bildirmektir.
Özet
- Belge Nesne Modeli, sözcük birimi akışından kurulan düğüm ağacıdır; biçem, yerleşim ve betikler kaynak metne değil bu ağaca uygulanır.
- Ağaç kurucu yığıt kullanır: açılan öge yığıta girer, kapanan öge çıkar, yeni düğüm yığıtın tepesindeki ögenin çocuğu olur.
- Kapanış etiketi yazılmayan bazı ögeler, başka bir öge açıldığında kendiliğinden kapanır; bu kurallar dilin tanımının parçasıdır.
- Dört düğüm türü vardır: belge, öge, metin ve yorum. Yorum düğümleri çizilmez ama belgede durur.
- Kaynak metinle ağaç bire bir örtüşmez: yazılmayan ögeler ağaçta belirebilir, yazılan bazı etiketler ağaca hiç geçmeyebilir.
- Boş ögelerin içeriği olamaz ve yığıta girmezler; kapatma yazımı onlarda etkisiz, boş olmayan ögelerde yanıltıcıdır.
Sonraki Adım
Bu ders ağacın belge metninden nasıl kurulduğunu gösterdi ve ayrıştırmanın kesintisiz ilerlediğini varsaydı. Gerçekte belge, kendisinden başka kaynaklara başvurur: biçem dosyaları, betikler, görseller. Bu başvurular ayrıştırma sürerken ortaya çıkar ve bir bölümü ayrıştırmayı durdurur. Sonraki ders, hangi kaynağın hangi aşamayı beklettiğini ve bunun belgede nereye yazıldığıyla nasıl değiştiğini bir olay çizelgesi üzerinden inceler.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.