Ders 06 / 25
Etiketler, Öznitelikler ve Varlıklar
Etiket ve öznitelik yazım kuralları, karakter başvurularının çözülmesi, metin ile öznitelik bağlamı arasındaki fark ve hatalı işaretlemede kurtarma davranışı.
İçindekiler
Önceki ders belgenin en dış kabuğunu kurdu ve etiketleri sözdizimsel ayrıntısına girmeden
kullandı. Bu ders o ayrıntıyı tanımlar: bir etiketin nasıl yazıldığını, özniteliklerin
hangi biçimleri kabul ettiğini, metinde geçen < ve & karakterlerinin nasıl yazılacağını
ve kurala aykırı bir yazımda ayrıştırıcının ne yaptığını.
Bu kurallar ezberlenecek bir liste değildir. Her biri, ayrıştırıcının durum makinesindeki belirli bir geçişe karşılık gelir ve o geçişi bilmek, beklenmedik sonuçların nedenini açıklar.
Etiketin Yazımı
Bir açılış etiketi < karakteriyle başlar, öge adıyla sürer, isteğe bağlı özniteliklerle
devam eder ve > ile biter. Kapanış etiketi </ ile başlar, yalnızca ad taşır.
Öge adları büyük–küçük harf ayrımı gözetmez: <P>, <p> ile aynı ögeyi açar.
Ayrıştırıcı adı küçültür ve ağaca küçük harfli kurar. Ayrım gözetilmemesi, iki farklı
yazımın kullanılabileceği anlamına gelmez; bir belgede tek yazım kullanılır ve küçük harf
yerleşik biçimdir.
Boş olmayan her ögenin kapanış etiketi vardır. Bir bölümünün yazılmaması önceki derste görüldüğü gibi tanımlı bir sonuç verir, ancak yazılmayan her kapanış, okuyan kişinin belgeyi zihninde ayrıştırmasını zorlaştırır.
Öznitelikler
Öznitelik, açılış etiketinin içinde yazılan ve ögeyi niteleyen ad–değer çiftidir.
<img src="yamac.jpg" alt="Ölçüm direği" width="960" height="640">
Öznitelik adları da büyük–küçük harf ayrımı gözetmez ve küçültülür. Sıraları anlamsızdır; aynı ögede aynı adın iki kez yazılması durumunda ilki geçerli olur, ikincisi yok sayılır.
Değer yazımının üç biçimi vardır ve üçü de geçerlidir:
<td colspan="2"> <!-- çift tırnak --> <td colspan='2'> <!-- tek tırnak --> <td colspan=2> <!-- tırnaksız -->
Tırnaksız yazım boşluk, tırnak, =, <, > ve ters tırnak içeremez. Bu kısıt bir tuzak
üretir: class=olcum tablosu yazımında tablosu ayrı bir öznitelik sayılır, değerin
parçası olmaz. Bu nedenle değerler her zaman tırnak içine alınır; kısıtın nerede
başladığını takip etmek, kuralı hep uygulamaktan pahalıdır.
Mantıksal öznitelikler (boolean attributes) ayrı bir sınıftır. Bunlarda anlamı taşıyan şey değer değil, özniteliğin varlığıdır.
<input type="checkbox" checked> <input type="checkbox" checked=""> <input type="checkbox" checked="checked">
Üç yazım da aynı anlama gelir: işaretli. Sezginin tersine, checked="false" de aynı
anlama gelir — öznitelik yazılmıştır, dolayısıyla işaretlidir. Kapatmanın tek yolu
özniteliği hiç yazmamaktır.
Karakter Başvuruları
Metin içinde < karakteri etiket başlangıcı, & karakteri başvuru başlangıcı sayılır. Bu
karakterleri sıradan metin olarak yazmanın yolu karakter başvurusudur (character
reference). Üç biçimi vardır: adlı (<), onluk sayısal (<) ve onaltılık sayısal
(<).
Aşağıdaki betik çözümleme kuralını uygular. Adlı başvurular gerçekte birkaç bine yakın addan oluşan bir tablodan aranır; burada küçük bir alt küme kullanılmıştır.
// varliklar.mjs — karakter basvurularinin cozulmesi const ADLI = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", reg: "®" }; const DESEN = /&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*)(;?)/g; function cozumle(metin, oznitelikMi) { return metin.replace(DESEN, (tam, govde, noktali, konum) => { if (govde.startsWith("#")) { const onaltilik = govde[1] === "x"; return String.fromCodePoint(parseInt(govde.slice(onaltilik ? 2 : 1), onaltilik ? 16 : 10)); } let ad = null; for (let n = govde.length; n > 0; n -= 1) { if (govde.slice(0, n) in ADLI) { ad = govde.slice(0, n); break; } } if (ad === null) return tam; const tamEslesme = ad.length === govde.length && noktali === ";"; const kalan = govde.slice(ad.length) + noktali; if (tamEslesme) return ADLI[ad]; const sonraki = kalan[0] ?? metin[konum + tam.length] ?? ""; if (oznitelikMi && /[=a-zA-Z0-9]/.test(sonraki)) return tam; return ADLI[ad] + kalan; }); } const ornekler = [ "5 < 8 && 8 > 5", "Eğim ısındı", "kod: <p>", "&hava; tabloda yok", ]; for (const ornek of ornekler) { console.log(JSON.stringify(ornek), "->", JSON.stringify(cozumle(ornek, false))); } console.log("--- ayni dizgi, iki baglam ---"); const adres = "/olcum?tur=nem®sayi=3"; console.log("metin icerigi olarak :", cozumle(adres, false)); console.log("oznitelik degeri :", cozumle(adres, true)); console.log("kacirilmis yazim :", cozumle("/olcum?tur=nem&regsayi=3", true));
"5 < 8 && 8 > 5" -> "5 < 8 && 8 > 5" "Eğim ısındı" -> "Eğim ısındı" "kod: <p>" -> "kod: <p>" "&hava; tabloda yok" -> "&hava; tabloda yok" --- ayni dizgi, iki baglam --- metin icerigi olarak : /olcum?tur=nem®sayi=3 oznitelik degeri : /olcum?tur=nem®sayi=3 kacirilmis yazim : /olcum?tur=nem®sayi=3
İlk satır üç adlı başvuruyu çözer. İkincisi sayısal başvuruların onluk ve onaltılık biçimlerini karıştırır; ikisi de aynı kod noktası kümesine başvurur — Bilgisayarlar Nasıl Çalışır kursundaki Karakter Kodlamaları dersinde tanımlanan kod noktaları. Dördüncü satır, tabloda bulunmayan bir adın çözülmediğini ve olduğu gibi bırakıldığını gösterir.
Aynı Dizgi, İki Bağlam
Çıktının ikinci bölümü, kolay gözden kaçan bir kuralı görünür kılar. /olcum?tur=nem®sayi=3
dizgisi metin içeriğinde ® karakteri üreterek bozulur; öznitelik değerinde bozulmaz.
Farkın nedeni, noktalı virgülle bitmeyen adlı başvurular için tanımlı ayrı bir kuraldır:
öznitelik değerinde, başvurunun ardından = veya harf/rakam geliyorsa çözümleme yapılmaz.
Bu kural, tırnak içindeki adreslerin sessizce bozulmasını önlemek için vardır — ama
yalnızca öznitelik bağlamında geçerlidir.
Üçüncü satır doğru yazımı gösterir. Bir adres öznitelik değerine yazılırken parametre
ayıracı & biçiminde kaçırılır. Öznitelik bağlamındaki kural bu kaçırmayı çoğu durumda
gereksiz kılsa da, kuralın “çoğu durumda” işlemesine güvenmek yerine kaçırmak tek biçimli
davranış verir.
Genel kural üç karakteri kapsar: metin içeriğinde < ve & kaçırılır; öznitelik değerinde
ek olarak değeri saran tırnak karakteri kaçırılır. Bunun bir güvenlik boyutu vardır ve
kullanıcıdan gelen metin belgeye yerleştirilirken bu kaçırmanın yapılmaması, en yaygın
web açıklarının kaynağıdır; konu ileri kurslarda ayrıntılandırılır.
Kurtarma Kuralları
İlk derste “geçersiz belge diye bir çıktı yoktur” denmişti. Aşağıdaki betik bu iddiayı somutlaştırır: açık öge yığıtını her sözcük biriminde yazdırır ve kurala aykırı yazımlarda ne yapıldığını gösterir.
// kurtarma.mjs — hatali isaretlemede acik oge yigitinin izi const BOS = new Set(["br", "img", "input", "meta", "link", "hr"]); const KAPATAN = { li: ["li"], dt: ["dt", "dd"], dd: ["dt", "dd"], p: ["p"], ul: ["p"], h1: ["p"] }; function izle(metin) { const yigit = []; console.log("kaynak:", metin); for (const e of metin.matchAll(/<(\/?)([a-z0-9]+)[^>]*>/g)) { const kapanis = e[1] === "/"; const ad = e[2]; let not = ""; if (!kapanis) { const kendiliginden = KAPATAN[ad] ?? []; while (yigit.length > 0 && kendiliginden.includes(yigit[yigit.length - 1])) { not = "(acik " + yigit.pop() + " kendiliginden kapandi)"; } if (BOS.has(ad)) not = "(bos oge, yigita girmez)"; else yigit.push(ad); } else if (BOS.has(ad)) { not = "(bos ogenin kapanisi yok sayilir)"; } else { const yer = yigit.lastIndexOf(ad); if (yer === -1) not = "(eslesen acilis yok, yok sayilir)"; else { if (yer !== yigit.length - 1) not = "(uzerindeki " + yigit.slice(yer + 1).join(", ") + " de kapanir)"; yigit.length = yer; } } console.log((" " + e[0].padEnd(10) + "yigit: [" + yigit.join(" > ") + "] " + not).trimEnd()); } if (yigit.length > 0) console.log(" belge sonu acik kalan: [" + yigit.join(" > ") + "] -> kapatilir"); console.log(); } izle("<p>Ölçüm <b>durdu</b></p>"); izle("<p>Ölçüm <b><i>durdu</b></i>"); izle("<ul><li>ilk<li>ikinci</ul>"); izle("<p>metin</span></p>");
kaynak: <p>Ölçüm <b>durdu</b></p> <p> yigit: [p] <b> yigit: [p > b] </b> yigit: [p] </p> yigit: [] kaynak: <p>Ölçüm <b><i>durdu</b></i> <p> yigit: [p] <b> yigit: [p > b] <i> yigit: [p > b > i] </b> yigit: [p] (uzerindeki i de kapanir) </i> yigit: [p] (eslesen acilis yok, yok sayilir) belge sonu acik kalan: [p] -> kapatilir kaynak: <ul><li>ilk<li>ikinci</ul> <ul> yigit: [ul] <li> yigit: [ul > li] <li> yigit: [ul > li] (acik li kendiliginden kapandi) </ul> yigit: [] (uzerindeki li de kapanir) kaynak: <p>metin</span></p> <p> yigit: [p] </span> yigit: [p] (eslesen acilis yok, yok sayilir) </p> yigit: []
Dört kural görünür duruma gelir. Eşleşen açılışı olmayan kapanış etiketi yok sayılır.
Yanlış sırada kapatılan ögelerde, kapanan ögenin üzerindeki ögeler de kapanır — ikinci
örnekte i ögesi, b kapanınca kapanmıştır ve sonradan gelen </i> boşa düşmüştür.
Belge sonunda açık kalan ögeler kapatılır. Bazı ögeler, kardeşleri açıldığında
kendiliğinden kapanır.
Bu kuralların birlikte üretebildiği sonuç, yazarın niyetiyle uyuşmayabilir. İkinci
örnekte yazar b ve i ögelerinin aynı metni kapsamasını istemiş, ancak sonuçta i
ögesi durdu metnini hiç kapsamamıştır. Ayrıştırıcı hata bildirmez; belge kurulur.
Bu, işaretleme doğrulayan araçların neden gerekli olduğunun gerekçesidir: kusur,
ayrıştırma anında değil, sonucun beklenenden farklı olmasında görünür.
Yorumlar
Yorum, <!-- ile başlayıp --> ile biten bölümdür ve ağaçta bir yorum düğümü üretir.
Çizilmez ama belgede durur ve okunabilir. İçine yazılan hiçbir şey gizli değildir; sunucu
adresleri, kaldırılmış özellik notları ve geçici açıklamalar belgeyle birlikte gönderilir.
Yorum içinde -- dizisi tanımsız davranışa yol açabilir; bu nedenle ayraç olarak
kullanılmaz.
Özet
- Öge ve öznitelik adları büyük–küçük harf ayrımı gözetmez ve küçültülür; öznitelik sırası anlamsızdır, yinelenen adda ilki geçerlidir.
- Öznitelik değerleri tırnaksız da yazılabilir ancak tırnaksız yazım boşlukta biter; bu nedenle değerler her zaman tırnak içine alınır.
- Mantıksal özniteliklerde anlamı taşıyan şey değer değil varlıktır;
checked="false"yazımı da özniteliği açar, kapatmanın yolu yazmamaktır. - Karakter başvurularının üç biçimi vardır; adlı başvurunun noktalı virgülle bitmemesi, metin içeriği ile öznitelik değerinde farklı sonuç verir.
- Ayrıştırıcı hatalı işaretlemede durmaz: eşleşmeyen kapanış yok sayılır, yanlış sırada kapatma üstteki ögeleri de kapatır, açık kalanlar belge sonunda kapanır.
- Yorumlar belgeyle birlikte gönderilir ve okunabilir; gizli bilgi taşımazlar.
Sonraki Adım
Bu ders karakter başvurularını çözerken bir varsayımda bulundu: baytların hangi kurala göre karakterlere çevrildiği belliydi. Oysa bu kural belgenin kendisinde bildirilir ve bildirilmediğinde tahmin edilir. Sonraki ders baş bölümündeki üstveri etiketlerini ele alır ve bunların en önemlisiyle başlar: karakter kümesi bildiriminin baytları nasıl yorumlattığı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.