İçeriğe geç
academia.sh

Ders 04 / 26

Birleştirici Seçiciler

Alt öge, çocuk, bitişik kardeş ve genel kardeş birleştiricileri; bir seçicinin sağdan sola değerlendirilmesi ve anahtar seçici kavramı.

İçindekiler

Önceki dersin son kuralında .kunye .ozet yazılmıştı ve iki seçici arasındaki boşluğun bir işleç olduğu söylenip geçilmişti. O boşluk birleştirici (combinator) adını taşır ve seçiciye ağaçtaki konum sorusunu ekler: ögenin kendisi değil, nerede durduğu sorulur.

Bu ders dört birleştiriciyi tanımlar ve bir seçicinin nasıl değerlendirildiğini gösterir. Değerlendirme sırası, hem sonucu hem de bir seçicinin maliyetini anlamak için gereklidir.

Dört Birleştirici

Yazım Ad Sorduğu soru
A B alt öge birleştiricisi (descendant) B’nin atalarından biri A mı?
A > B çocuk birleştiricisi (child) B’nin doğrudan üstü A mı?
A + B bitişik kardeş (adjacent sibling) B’nin bir önceki kardeşi A mı?
A ~ B genel kardeş (general sibling) B’nin önceki kardeşlerinden biri A mı?

Dördü de aynı biçimde okunur: sağdaki seçilir, soldaki koşuldur. .olcum-cizelgesi td kuralı td ögelerini seçer; .olcum-cizelgesi yalnızca hangi td ögelerinin seçileceğini daraltır. Sol taraf hiçbir zaman biçimlendirilmez.

Kardeş birleştiricileri yalnızca geriye bakar. A + B yazımı “A’dan sonra gelen B” anlamındadır; “B’den sonra gelen A” diye bir birleştirici yoktur.

Değerlendirme Sağdan Sola Yapılır

Bir seçici soldan sağa okunur ama sağdan sola değerlendirilir. Sebep, iki yönün maliyet farkındadır.

Soldan sağa gidilseydi .olcum-cizelgesi td için önce bütün belgede o sınıfı taşıyan ögeler bulunur, sonra her birinin bütün alt ağacı taranırdı. Sağdan sola gidildiğinde ise her td ögesi için yalnızca atalar zinciri yukarı doğru izlenir; zincirin uzunluğu ağacın derinliği kadardır. İkinci yol, bir ögenin biçemi hesaplanırken sorulan sorunun da doğal yönüdür: “bu ögeye hangi kurallar uyuyor?”

Sağdaki bileşik seçiciye anahtar seçici (key selector) denir. Bir kuralın hangi ögeler için sınanacağını belirleyen odur.

Aşağıdaki program dört birleştiriciyi sağdan sola değerlendirir. Ağaç, ölçüm çizelgesinin gerçek yapısını taşır: satırlar thead ve tbody içinde gruplanmıştır.

// birlestirici.mjs — dort birlestiriciyi sagdan sola degerlendirir
const agac = { ad: "body", sinif: [], cocuk: [
  { ad: "main", sinif: [], cocuk: [
    { ad: "table", sinif: ["olcum-cizelgesi"], cocuk: [
      { ad: "thead", sinif: [], cocuk: [
        { ad: "tr", sinif: [], cocuk: [{ ad: "th", sinif: [], cocuk: [] }] },
      ]},
      { ad: "tbody", sinif: [], cocuk: [
        { ad: "tr", sinif: [], cocuk: [
          { ad: "td", sinif: ["ad"], cocuk: [] },
          { ad: "td", sinif: ["deger"], cocuk: [] },
          { ad: "td", sinif: ["deger", "eksik"], cocuk: [] },
        ]},
      ]},
    ]},
    { ad: "p", sinif: ["not"], cocuk: [] },
    { ad: "table", sinif: [], cocuk: [
      { ad: "tbody", sinif: [], cocuk: [
        { ad: "tr", sinif: [], cocuk: [{ ad: "td", sinif: ["deger"], cocuk: [] }] },
      ]},
    ]},
  ]},
]};

// her dugume ust ve kardes bilgisi ekle
function bagla(d, ust = null) {
  d.ust = ust;
  d.cocuk.forEach((c, i) => { c.oncekiKardes = i > 0 ? d.cocuk[i - 1] : null; bagla(c, d); });
  return d;
}
bagla(agac);

const yol = (d) => (d.ust ? yol(d.ust) + " " : "") + d.ad + d.sinif.map((s) => "." + s).join("");
function* hepsi(d) { yield d; for (const c of d.cocuk) yield* hepsi(c); }

const basitEsler = (s, d) =>
  s === "*" ? true : s.startsWith(".") ? d.sinif.includes(s.slice(1)) : d.ad === s;

// bilesik secici: bosluksuz basit seciciler
function bilesikEsler(bilesik, d) {
  const parcalar = bilesik.match(/(^[a-z]+|\.[a-z-]+|\*)/g) ?? [];
  return parcalar.every((p) => basitEsler(p, d));
}

// secici: bilesik (birlestirici bilesik)*  — sagdan sola cozulur
function secERler(secici, dugum) {
  const jetonlar = secici.trim().split(/\s+/);
  let d = dugum;
  let i = jetonlar.length - 1;
  if (!bilesikEsler(jetonlar[i], d)) return false;
  i--;
  while (i >= 0) {
    const birlestirici = ">+~".includes(jetonlar[i]) ? jetonlar[i--] : " ";
    const hedef = jetonlar[i--];
    if (birlestirici === ">") { d = d.ust; if (!d || !bilesikEsler(hedef, d)) return false; }
    else if (birlestirici === "+") { d = d.oncekiKardes; if (!d || !bilesikEsler(hedef, d)) return false; }
    else if (birlestirici === "~") {
      let k = d.oncekiKardes;
      while (k && !bilesikEsler(hedef, k)) k = k.oncekiKardes;
      if (!k) return false; d = k;
    } else {
      let a = d.ust;
      while (a && !bilesikEsler(hedef, a)) a = a.ust;
      if (!a) return false; d = a;
    }
  }
  return true;
}

const denemeler = [
  "table td",
  ".olcum-cizelgesi td",
  ".olcum-cizelgesi > td",
  "tbody > tr > td",
  "td.ad + td",
  "td.ad ~ td",
  ".olcum-cizelgesi + p",
];
for (const s of denemeler) {
  const bulunan = [...hepsi(agac)].filter((d) => secERler(s, d));
  console.log(`${s.padEnd(26)} -> ${bulunan.length}`);
  for (const b of bulunan) console.log(`  ${yol(b)}`);
}
table td                   -> 4
  body main table.olcum-cizelgesi tbody tr td.ad
  body main table.olcum-cizelgesi tbody tr td.deger
  body main table.olcum-cizelgesi tbody tr td.deger.eksik
  body main table tbody tr td.deger
.olcum-cizelgesi td        -> 3
  body main table.olcum-cizelgesi tbody tr td.ad
  body main table.olcum-cizelgesi tbody tr td.deger
  body main table.olcum-cizelgesi tbody tr td.deger.eksik
.olcum-cizelgesi > td      -> 0
tbody > tr > td            -> 4
  body main table.olcum-cizelgesi tbody tr td.ad
  body main table.olcum-cizelgesi tbody tr td.deger
  body main table.olcum-cizelgesi tbody tr td.deger.eksik
  body main table tbody tr td.deger
td.ad + td                 -> 1
  body main table.olcum-cizelgesi tbody tr td.deger
td.ad ~ td                 -> 2
  body main table.olcum-cizelgesi tbody tr td.deger
  body main table.olcum-cizelgesi tbody tr td.deger.eksik
.olcum-cizelgesi + p       -> 1
  body main p.not

Bu gerçekleştirim, alt öge ve genel kardeş adımlarında geriye dönüş yapmaz: eşleşen ilk atayı ya da ilk kardeşi seçer ve orada kalır. Gerçek bir eşleyici, sonraki adım başarısızsa geri döner ve başka bir ata dener. Yukarıdaki yedi örnekte iki davranış aynı sonucu verir.

Sıfır Öge Döndüren Seçici

Üçüncü satır dikkat ister: .olcum-cizelgesi > td hiçbir öge döndürmedi, oysa .olcum-cizelgesi td üç öge döndürdü. Aradaki tek fark bir işarettir.

Sebep ağaçtadır. Hücreler tablonun doğrudan çocuğu değildir; aradaki zincir table > tbody > tr > td biçimindedir. Çocuk birleştiricisi tam bir adım ister ve o adım tbody ögesine denk gelir.

Web’in Temelleri ve HTML kursunda ayrıştırıcının kaynakta yazılmamış ögeleri kurduğu anlatılmıştı; tbody bunların en sık karşılaşılanıdır. Kaynak metinde tbody etiketi yazılmamış olsa bile öge belgede vardır ve seçici o ögeyi görür. Bu, “etiket” ile “öge” ayrımının biçem tarafındaki ilk somut sonucudur: seçici kaynak metne değil, ayrıştırma sonrası ağaca bakar.

Çocuk Birleştiricisi Ne Zaman Gerekir

Alt öge birleştiricisi derinlik gözetmez ve bu çoğu zaman istenen davranıştır. Ancak iç içe geçmiş aynı türden yapılarda istenmeyen sonuç verir.

İstasyon sayfasının gezinme bölümü iç içe listeler taşıyorsa, .gezinme li seçicisi hem üst düzey maddeleri hem alt maddeleri döndürür. Yalnızca üst düzeyi hedeflemek için .gezinme > ul > li yazılır. Kural şudur: derinlik anlam taşıyorsa çocuk birleştiricisi, taşımıyorsa alt öge birleştiricisi kullanılır.

Kardeş Birleştiricileri Sıraya Bağlıdır

td.ad + td bir hücre döndürdü: ad hücresinden hemen sonraki değer hücresi. td.ad ~ td ise ikisini birden döndürdü, çünkü genel kardeş birleştiricisi araya kaç kardeş girdiğine bakmaz.

Kardeş birleştiricilerinin tipik kullanımı, iki ögenin arasındaki ilişkiyi biçimlendirmektir. Ard arda gelen iki paragraf arasına boşluk koymak için:

p + p { margin-block-start: 0.75em; }

Bu yazım, bir bölümün ilk paragrafına boşluk eklemez — çünkü onun önünde bir paragraf kardeş yoktur. Aynı sonucu her paragrafa boşluk verip ilkini geri almakla da elde etmek mümkündür; kardeş birleştiricisi ile yazılan biçim, kuralı tek bir yerde ve istisnasız tutar.

İstasyon Sayfasının Kuralları

Biçem dosyası bu dersle birlikte ağaçtaki konumları kullanmaya başlar:

/* istasyon.css — 3. adım: ilişkiye bağlı kurallar */
.olcum-cizelgesi tbody td   { padding: 8px 12px; }
.olcum-cizelgesi tbody > tr { border-block-end: 1px solid #d5dbe0; }
.gezinme > ul > li          { display: inline-block; }
.olcum-cizelgesi + p        { font-size: 0.875rem; }

Son kural, çizelgeden hemen sonra gelen açıklama paragrafını küçültür. Paragrafa ayrı bir sınıf vermek de aynı sonucu verirdi; aradaki fark, kuralın belgeye bir sınıf ekleme gerektirip gerektirmemesidir. Belgeye dokunmadan çözülebilen bir biçem sorunu, biçem dosyasında çözülür.

Özet

  • Birleştirici, seçiciye ağaçtaki konum sorusunu ekler; dördü de aynı biçimde okunur — sağdaki seçilir, soldaki koşuldur.
  • Alt öge birleştiricisi derinlik gözetmez, çocuk birleştiricisi tam bir adım ister; bitişik kardeş bir önceki kardeşe, genel kardeş önceki kardeşlerin herhangi birine bakar.
  • Seçici sağdan sola değerlendirilir; en sağdaki bileşik seçici anahtar seçicidir ve kuralın hangi ögeler için sınanacağını belirler.
  • Seçici, kaynak metne değil ayrıştırma sonrası ağaca bakar; kaynakta yazılmayan tbody gibi ögeler zincire dâhildir ve çocuk birleştiricisini etkiler.
  • Kardeş birleştiricileri yalnızca geriye bakar; “sonraki kardeşin öncesi” diye bir birleştirici yoktur.

Sonraki Adım

Seçiciler şimdiye kadar öge adına, sınıfa, kimliğe ve ağaçtaki konuma baktı. Belgede bir bilgi daha var ve henüz kullanılmadı: öznitelik değerleri. Form alanlarının türü, bir bağlantının hedef adresi, veri öznitelikleriyle iliştirilen ölçüm birimleri — hepsi özniteliklerde durur. Sonraki ders öznitelik seçicilerini ve değer eşleme biçimlerini tanımlar.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat