Selektory#
Struktura i składnia#
W specyfikacji selektorów utworzono specjalny rozdział "Selector Syntax and Structure", w którym umieszczono najważniejsze pojęcia, scharakteryzowano podział selektorów i kilka innych kluczowych rzeczy. W moim kursie część z tych informacji umieściłem w innych podrozdziałach, zostawiając w tym miejscu tylko to, co najistotniejsze.
Terminologia i struktura#
Termin selector # (selector) może odnosić się do selektora prostego, selektora złożonego, selektora kompleksowego oraz listy selektorów.
Lista selektorów jest listą selektorów oddzielonych od siebie przecinkami.
Selektor kompleksowy # (complex selector) jest łańcuchem jednego lub większej liczby selektorów złożonych rozdzielonych za pomocą kombinatorów.
Selektor złożony # (compound selector) jest łańcuchem prostych selektorów, które nie są rozdzielone za pomocą kombinatora. Zawsze rozpoczyna się od selektora typu lub (ewentualnie domyślnie stosowanego) selektora uniwersalnego. Żaden inny selektor typu lub selektor uniwersalny nie jest dozwolony w sekwencji.
Selektor prosty # (simple selector) jest selektorem typu, selektorem uniwersalnym, selektorem atrybutu, selektorem klasy, selektorem identyfikatora lub pseudoklasą.
Kombinator # (combinator) jest znakiem interpunkcyjnym, który reprezentuje szczególny rodzaj relacji między selektorami złożonymi po obu stronach. W aktualnej specyfikacji do kombinatorów zaliczamy: znak większości (U+003E, >
), znak plusa (U+002B, +
) i tyldę (U+007E, ~
). Białe znaki mogą pojawić się między kombinatorem i selektorem prostym, jak i wokół nich.
Nie wiem czemu Selektory podają w tym miejscu tylko trzy kombinatory. W rzeczywistości jest ich więcej.
Selektor pusty, czyli nie zawierający selektora złożonego, jest nieprawidłowym selektorem.
Określanie selektora#
Selektor jest określany (evaluating) na pewnej początkowej liście elementów, jak wskazuje kontekst, w którym selektor jest określany: liście dopasowania selektora # (selector match list). Selektor jest przetwarzany w kolejności od lewej do prawej, gdzie selektory proste filtrują zestaw dopasowania selektora, i kombinatory zmieniają zestaw dopasowania selektora na coś nowego. Gdy ten proces zostanie zakończony, to elementy w zestawie dopasowania selektora są elementami pasującymi do tego selektora.
Dla przykładu, aby określić selektor "div > i.name"
względem dokumentu, lista dopasowania selektora początkowo wskazuje na wszystkie elementy w całym dokumencie. Następnie selektor typu "div"
jest określany, co skutkuje przefiltrowaniem listy dopasowania selektora tak, aby zawierała jedynie elementy z nazwą tagu "div"
. Następnie kombinator dziecka ">"
jest określany, co skutkuje przekształceniem listy dopasowania selektora na zasadzie zastąpienia każdego elementu w niej zawartego przez dzieci tego elementów. Następnie selektor typu "i"
jest określany, co skutkuje przefiltrowaniem listy dopasowania selektora tak, aby zawierała jedynie elementy z nazwą tagu "i"
. W końcu selektor klasy ".class"
jest określany, co skutkuje przefiltrowaniem listy dopasowania selektora tak, aby zawierała jedynie elementy z klasą "name"
.
Należy podkreślić, że wiele implementacji selektorów preferuje określanie od prawej do lewej, gdyż w wielu przypadkach jest ono wydajniejsze. Zachowanie to jest w pełni zgodne tak długo, jak rezultatem będą te same elementy, które zostałyby zwrócone przez algorytm specyfikacji.
Każdy kontekst, na którym selektor może być określany, musi definiować początkową listę dopasowania selektora # (initial selector match list), czyli listę elementów, dla której każdy selektor w tym kontekście ma swój początek.
W przypadku HTML-a, kiedy następuje określanie selektora względem dokumentu, początkową listę dopasowania selektora stanowi element będący korzeniem oraz wszyscy jego potomkowie, zgodnie z porządkiem drzewa.
Model danych#
Selektory są dopasowywane względem drzewa elementu # (element tree), które jest abstrakcyjną strukturą drzewa składającą się z elementów. Każdy element może mieć następujące cechy:
- Typ, który jest łańcuchem znakowym.
- Unikatowy identyfikator (ID), który jest łańcuchem znakowym.
- Klasy, które są łańcuchami znakowymi.
- Atrybuty, które są parą łańcuchów znakowych zawierających nazwę atrybutu oraz wartość atrybutu.
- Listę swoich elementowych dzieci.
- Arbitralne informacje dodatkowe, które nie są bezpośrednio dostępne, ale mogą być użyte w czasie dopasowania pseudoklas i pseudoelementów.
W przypadku HTML-a drzewo elementu wyrażane jest za pomocą DOM. Każdy element DOM reprezentuje elementem w drzewie elementu, z nazwą tagu elementu DOM jako typem, oraz jego unikatowym identyfikatorem, listą klas, atrybutami i elementowymi dziećmi odpowiadającymi unikatowemu identyfikatorowi, klasom, atrybutom i elementowym dzieciom w drzewie elementu. Reszta informacji w DOM jest również dostępna jako arbitralne dodatkowe informacje w drzewie elementu.
Selektory zawężone#
Niektóre aplikacje gospodarza (np. nasze programy w przeglądarkach) mogą wybierać zawężenie # (scope) selektorów na danym poddrzewie lub fragmencie dokumentu. Korzeń zawężanego poddrzewa jest nazywany zawężającym korzeniem # (scoping root) i może być prawdziwym elementem określanym jako zawężający element # (scoping element), lub wirtualnym # (virtual), jak chociażby w przypadku DocumentFragment.
Istnieją dwie metody zawężania (scoping methods) dla selektorów:
- obejmujące zawężenie selektorów (scope-contained selectors) #
W tej metodzie zawężania, selektory dopasowują elementy tak, jakby zawężającym korzeniem był korzeń dokumentu: wszystkie selektory złożone muszą być reprezentowane w zawężeniu. Jednakże peudoklasa
:root
wciąż dopasowuje aktualny korzeń dokumentu.- filtrujące zawężenie selektorów (scope-filtered selectors) #
W tej metodzie zawężania, selektor dopasowuje elementy w chwili, kiedy temat selektora znajduje się w zawężeniu, nawet jeśli inne części składowe selektora są poza zawężeniem. Zawężający element jest traktowany tak, jakby był w zawężeniu.
Dla przykładu, metoda element.querySelector()
wykorzystuje filtrujące zawężenie selektorów, kiedy selektory w zawężonych arkuszach stylów (np. w przypadku elementu style
) wykorzystują obejmujące zawężenie selektorów.
Jeśli nie określono inaczej, to zawężający korzeń również kształtuje zestaw elementów referencyjnych i pasuje do pseudoklasy :scope
.
Z praktycznego punktu widzenia stosowanie zawężeń CSS nie stanowi żadnej trudności. Prawdę mówiąc oryginalne definicje, jak i moje tłumaczenia, nie do końca do mnie przemawiają (ciężko ustalić prawidłowy kontekst). Całość zostanie zweryfikowana w niedalekiej przyszłości.
Selektory relatywne#
Pewne konteksty mogą akceptować selektory relatywne # (relative selectors), które są skrótami dla selektorów reprezentujących elementy w stosunku do elementu referencyjnego (czyli elementu, który pasuje do pseudoklasy :scope
). W selektorze relatywnym człon ":scope "
(tzn. pseudoklasa :scope
z dodanym znakiem spacji) jest domyślnie dołączany na początku każdego selektora złożonego, który nie zawiera jeszcze pseudoklasy :scope
. W ujęciu składniowym pozwala to na rozpoczęcie selektora przy użyciu kombinatora. Jednakże przed wykonaniem dopasowania selektor musi zostać zabsolutyzowany.
Selektory relatywne, po abosolutyzacji, mogą być dodatkowo obejmująco zawężone (jak w pseudoelemencie ::distributed()
) lub filtrująco zawężone.
Absolutyzacja selektora relatywnego#
Algorytm absolutyzacji selektora relatywnego # (absolutize a relative selector) jest następujący:
Jeśli zestaw elementów referencyjnych jest pusty lub zawiera jeden lub więcej elementów:
- Jeśli selektor zaczyna się kombinatorem innym niż kombinator potomka to dodaj na początku
:scope
, który stanowił będzie początkowy selektor złożony. - W przeciwnym razie, jeśli selektor nie zawiera żadnej instancji pseudoklasy
:scope
(na najwyższym poziomie lub jako argument w pseudoklasie funkcyjnej), to dodaj na początku:scope
, po którym następuje kombinator potomka. - W przeciwnym razie selektor jest już absolutny.
Powyższy algorytm różni się od tego ze specyfikacji "Selectors API Level 2": chodzi o krok 2, który przerywał pracę, kiedy zestaw elementów referencyjnych był pusty, to też został on usunięty. To dlatego, że obsługa wyraźnej pustej listy nie powinna już niczego zwracać; jeśli zestaw elementów referencyjnych nie został w ogóle przekazany, to algorytm powinien powiedzieć, że selektor jest już absolutny. Przykład błędu: polecenie document.find("img", [list-of-links])
powinno wyszukiwać obrazy, które normalnie są potomkami linków, lecz w zamian wyszukuje wszystkie obrazy w dokumencie, jeśli list-of-links
jest pusta.
W przeciwnym razie, jeśli zestaw elementów referencyjnych składa się jedynie z wirtualnego korzenia zawężającego:
- Jeśli selektor zaczyna się kombinatorem dziecka to usuń kombinator dziecka. Selektor jest teraz absolutny, z dodatkowym ograniczeniem, że pierwszy selektor złożony w selektorze pasuje jedynie do elementów bez rodzica.
- W przeciwnym razie, jeśli selektor zaczyna się kombinatorem innym niż kombinator potomka, to zmień selektor na
":not(*)"
. Jest to najkrótszy prawidłowy selektor, ale gwarantujący, że niczego nie dopasuje. - W przeciwnym razie selektor jest już absolutny.
W przeciwnym razie jest to błąd specyfikacji. Należy przesłać raport do odpowiedniego organu standaryzacyjnego.
Aby absolutyzować listę selektorów relatywnych # (absolutize a relative selector list) należy absolutyzować każdy relatywny selektor w tej liście.
Znaki i wielkość liter#
Cała składnia Selektorów jest nieczuła na wielkość liter w zakresie ASCII (czyli [a-z]
i [A-Z]
są równoważne), z wyjątkiem następujących części, które nie są kontrolowane przez Selektory: czułości na wielkość liter zależnej od języka dokumentu dla nazw elementów, nazw atrybutów i wartości atrybutów. Dla przykładu, w HTML-u, nazwy elementów są nieczułe na wielość liter, ale w XML są czułe na wielkość liter. Czułość na wielkość liter w prefiksach przestrzeni nazw jest określona w specyfikacji "CSS Namespaces Module". Czułość na wielkość liter zakresów językowych jest określona w pseudoklasie :lang()
.
Białe znaki # (whitespace) w Selektorach stanowią znaki SPACE (U+0020)
, TAB (U+0009)
, LINE FEED (U+000A)
, CARRIAGE RETURN (U+000D)
i FORM FEED (U+000C)
. Inne znaki przerw, jak np. EM SPACE (U+2003)
i IDEOGRAPHIC SPACE (U+3000)
, nigdy nie są częścią białych znaków.
Znaki w Selektorach można poprzedzać znakiem uniku (\
), na tych samych zasadach jak w regułach CSS 2.1. Należy zauważyć, że znak uniku "anuluje" wszelkie specjalne znaczenia, które mogą występować w selektorach. Dla przykładu, selektor #foo>a
zawiera kombinator, ale selektor #foo\>a
wybiera element z identyfikatorem foo>a
.
Przestrzenie nazw#
Niektóre selektory obsługują prefiksy przestrzeni nazw. Mechanizm za pomocą którego prefiksy przestrzeni nazw są deklarowane (declared) powinien być określany przez język używający Selektorów. Jeśli język nie określa mechanizmu deklaracji prefiksów przestrzeni nazw, to żadne prefiksy nie są deklarowane. W CSS prefiksy przestrzeni nazw są deklarowane za pomocą reguły @namespace
.
Nieprawidłowe selektory i obsługa błędów#
Aplikacje klienckie dla obsługi nieprawidłowych selektorów # (invalid selectors) muszą przestrzegać następujących zasad:
- Parsowanie błędu w selektorze, np. nierozpoznane słowo lub słowo, które nie jest dozwolone w bieżącym momencie analizowania, czyni ten selektor nieprawidłowym.
- Selektor prosty zawierający niezadeklarowany prefiks przestrzeni nazw jest nieprawidłowy.
- Selektor zawierający nieprawidłowy selektor prosty, nieprawidłowy kombinator lub nieprawidłowe słowo, jest nieprawidłowy.
- Lista selektorów zawierająca nieprawidłowy selektor jest nieprawidłowa.
Nieprawidłowy selektor niczego sobą nie reprezentuje.