Початок роботи з CSS
У цій статті ми візьмемо простий документ HTML і застосуємо до нього CSS, вивчаючи деякі практичні речі про мову.
| Необхідні знання: | Базова комп'ютерна грамотність, Базове програмне забезпечення, базові знання робота з файлами та базові знання HTML (див. Введення в HTML.) |
|---|---|
| Завдання: | Зрозуміти основи зв'язування документа CSS з HTML-файлом і вміти виконувати просте форматування тексту за допомогою CSS. |
Почнемо з HTML
Нашою відправною точкою є HTML-документ. Ви можете скопіювати код знизу, якщо хочете працювати на своєму комп'ютері. Збережіть наведений нижче код як index.html у папці на комп'ютері.
Примітка: Якщо ви читаєте це на пристрої або в середовищі, де ви не можете легко створювати файли, не турбуйтеся нижче представлені редактори коду, щоб ви могли написати код прямо тут, на сторінці.
Додавання CSS до нашого документа
Найперше, що нам потрібно зробити, це повідомити HTML-документу, що ми маємо деякі правила CSS, які ми хочемо використовувати. Існує три різні способи застосування CSS до документа HTML, з яким ви зазвичай стикаєтеся, проте зараз ми розглянемо найбільш звичайний і корисний спосіб зробити це - зв'язати CSS із заголовком вашого документа.
Створіть файл у тій самій папці, що й HTML документ, і збережіть його як styles.css . Розширення .css показує, що це файл CSS.
Щоб зв'язати styles.css з index.html, додайте наступний рядок десь усередині HTML документа:
Збережіть файли HTML та CSS та перезавантажте сторінку у веб-браузері. Заголовок першого рівня у верхній частині документа тепер має бути червоним.Якщо це станеться, вітаю, ви успішно застосували CSS до документа HTML. Якщо цього не станеться, уважно перевірте, чи правильно ви запровадили все.
Ви можете продовжити роботу в styles.css локально, або ви можете використовувати наш інтерактивний редактор нижче, щоб продовжити цей урок. Інтерактивний редактор діє так, якби CSS на першій панелі був пов'язаний з документом HTML, як це було в нашому документі вище.
Стилізація HTML-елементів
Роблячи наш заголовок червоним, ми вже продемонстрували, що можемо націлювати та стилізувати елемент HTML. Ми робимо це шляхом націлювання на елемент selector — це селектор, який відповідає імені елемента HTML. Щоб націлюватися на всі абзаци в документі, необхідно використовувати селектор p . Щоб зробити всі абзаци зеленими, ви повинні використати:
Ви можете вибрати кілька селекторів одночасно, розділивши їх комами. Якщо я хочу, щоб усі параграфи та всі елементи списку були зеленими, моє правило виглядає так:
Спробуйте це в інтерактивному редакторі нижче (відредагуйте поля коду) або у локальному документі CSS.
Зміна поведінки елементів за промовчанням
Коли ми дивимося на добре розмічений HTML-документ, навіть такий простий як наш приклад, ми можемо побачити, як браузер робить HTML читаним, додавши деякі стилі за замовчуванням. Заголовки великі та жирні, у нашому списку є маркери. Це відбувається тому, що в браузерах є внутрішні таблиці стилів, що містять стандартні стилі, які за замовчуванням застосовуються до всіх сторінок; без них весь текст працював би разом, і ми мали б стилізувати все з нуля.Усі сучасні браузери за промовчанням відображають HTML-контент практично однаково.
-
- Невпорядкований список. Він додає маркери, і якщо я вирішу, що я не хочу ці маркери, я можу видалити їх так:
Спробуйте додати це до свого CSS зараз.
Властивість list-style-type – це гарна властивість, інформацію про яку можна знайти на MDN, щоб побачити, які значення підтримуються. Погляньте на сторінку list-style-type і ви знайдете інтерактивний приклад у верхній частині сторінки, щоб випробувати деякі інші значення, потім всі допустимі значення будуть докладно описані нижче.
Дивлячись на цю сторінку, ви виявите, що, крім видалення маркерів списку, ви можете змінити їх — спробуйте змінити їх на квадратні маркери, використовуючи значення square .
Додавання класу
Поки що у нас є стилізовані елементи, що базуються на їхніх іменах HTML-елементів. Це працює доти, доки ви хочете, щоб усі елементи цього типу у вашому документі виглядали однаково. У більшості випадків це не так, і вам потрібно буде знайти спосіб вибрати підмножину елементів, не змінюючи решту. Найпоширеніший спосіб зробити це – додати клас до вашого HTML-елементу та націлитись на цей клас.
У HTML-документі додайте Атрибут class до другого пункту списку. Ваш список тепер виглядатиме так:
У CSS ви можете вибрати клас special до будь-якого елемента на сторінці, щоб він виглядав так само, як і цей елемент списку. Додайте наступне до файлу CSS:
Збережіть та оновіть, щоб побачити результат.
Ви можете захотіти, щоб абзац також був помаранчевим і жирним. Спробуйте додати клас "special", потім перезавантажте сторінку і подивіться, що вийде.
Іноді ви побачите правила з селектором, який перераховує селектор HTML-елемента разом із класом:
Цей синтаксис означає "призначатися для будь-якого елемента li, який має клас special". Якби ви зробили це, ви більше не змогли б застосувати клас до або іншого елемента, просто додавши до нього клас; ви повинні додати цей елемент до списку селекторів:
li.special, span.special
Як ви можете уявити, деякі класи можуть бути застосовані до багатьох елементів, і вам не потрібно постійно редагувати свій CSS щоразу, коли щось нове має прийняти цей стиль. Тому іноді краще обійти елемент і просто звернутися до класу, якщо ви не знаєте, що хочете створити деякі спеціальні правила для одного елемента і, можливо, хочете переконатися, що вони не застосовуються до інших елементів.
Стилізація елементів на основі їх розташування у документі
Додайте наступне правило до таблиці стилів.
Ще можна спробувати стилізувати абзац, коли він іде відразу після заголовка на тому ж рівні ієрархії в HTML. Для цього помістіть + (сусідній братський комбінатор) між селекторами.
Спробуйте також додати це правило до таблиці стилів:
Приклад нижче включає два правила вище. Спробуйте додати правило, щоб зробити елемент червоний span, якщо він всередині абзацу. Ви дізнаєтеся, чи правильно ви це зробили, оскільки проміжок у першому абзаці буде червоним, але колір у першому елементі списку не змінить колір.
Примітка: Як ви можете бачити, CSS дає нам кілька способів націлювання на елементи, і ми поки що лише трохи вивчили його! Ми будемо уважно дивитися на всі ці селектори і багато іншого в нашій статті Селектори пізніше в нашому курсі.
Стилізація елементів на основі стану
Ви можете змінити зовнішній вигляд посилання, коли користувач наводить на неї курсор, наприклад, видаливши підкреслення, що досягається за допомогою наступного правила:
У наведеному нижче прикладі можна пограти з різними значеннями для різних станів посилання. Я додав до нього правила, наведені вище, і тепер розумію, що рожевий колір досить легкий і важко читається чому б не змінити його на кращий колір? Чи можете ви зробити посилання жирним шрифтом?
Ми видалимо підкреслення на нашому посиланні при наведенні курсору. Ви можете видалити підкреслення зі всіх станів посилання. Однак, варто пам'ятати, що на реальному сайті ви хочете, щоб відвідувачі знали, що посилання є посиланням. Залишивши на місці підкреслення, люди можуть зрозуміти, що на якийсь текст всередині абзацу можна натискати — до такої поведінки вони звикли. Як і все в CSS, існує можливість зробити документ менш доступним із вашими змінами - ми постараємося виділити потенційні підводні камені у відповідних місцях.
Примітка: Ви часто бачитимете згадку про доступність у цих уроках і по всій MDN. Коли ми говоримо про доступність, ми маємо на увазі вимогу, щоб наші веб-сторінки були зрозумілими та доступними для всіх.
Ваш відвідувач може бути на комп'ютері з мишею або сенсорною панеллю або на телефоні з сенсорним екраном. Або вони можуть використовувати програму читання з екрана, яка зчитує вміст документа, або їм може знадобитися використовувати текст значно більшого розміру, або переміщатися сайтом тільки за допомогою клавіатури.
Простий HTML-документ, як правило, доступний кожному - коли ви починаєте оформляти цей документ, важливо, щоб ви не зробили його менш доступним.
Поєднання селекторів та комбінаторів
Варто відзначити, що ви можете комбінувати кілька селекторів та комбінаторів разом. Ось приклад:
-
, що йде відразу після */ h1 + ul + p
Ви також можете поєднати кілька типів разом. Спробуйте додати наступне у ваш код:
body h1 + p.
Це буде стиль будь-якого елемента з класом special, який знаходиться всередині
, що приходить відразу після , що знаходиться всередині . Уф!
В оригінальному HTML, який ми надали, єдиний елемент у стилі .
Не турбуйтеся, якщо це здасться складним - ви скоро почнете розуміти це, коли писатимете більше на CSS.
Завершення
У цьому уроці ми розглянули кілька способів стилізації документа за допомогою CSS. Ми розвиватимемо ці знання в міру проходження інших уроків. Однак ви вже знаєте достатньо, щоб стилізувати текст, застосовувати CSS на основі різних способів націлювання на елементи в документі та шукати властивості та значення документації MDN.
На наступному уроці ми розглянемо структуру CSS.
Розробляємо свій браузер. Частина друга: CSS
Продовжуємо цикл статей з розробки браузерного двигуна.
Так, краще пізно, ніж ніколи. Так, перерва була великою.
Наприкінці статті я опишу, як поживає проект lexbor, що з ним відбувається.
У цій статті я спробую розкрити особливості парсингу Cascading Style Sheets (CSS). Розкажу, як вивернути «їжака» навиворіт і як тестувати отриманий результат.
У спеціфікаціях CSS все розжовано, ну, або майже все, тут я розповім, як все влаштовано, куди дивитися і з чого почати.
Ця стаття більш оглядова, тут не буде дрібних подробиць реалізації, швидше за все, загальні відомості та основні алгоритми. За найдрібнішими подробицями прошу в код на GitHub.
І звичайно, як це зазвичай буває, ми замахнемося на звання найшвидшого парсера CSS.
Куди дивитись?
Є два джерела CSS специфікацій:
- https://www.w3.org/TR/ - консорціум, який «все» інтернет-специфікації тримає.
- https://drafts.csswg.org/ - всі чернетки (Drafts) останніх специфікацій. Це теж W3 лише з іншого боку.
Ми будемо працювати з drafts.csswg.org.
Там все лаконічно, у кожному модулі є посилання на всі версії модуля – від чернетки до рекомендованого до використання.
Як влаштовано?
CSS влаштований у вигляді модулів: Syntax, Namespaces, Selectors, CSSOM, Values тощо. Повний перелік можна знайти на csswg.org.
Кожен модуль, точніше певна його версія, має статус: Working Draft, Candidate Recommendation, Recommendation і так далі, всі етапи можна подивитися на w3.org.
Говорячи народною мовою, у кожному модулі є позначка «рекомендований до використання», тільки-но почали писати – і неясно, що з цього вийде, може, взагалі видалимо цей модуль, і все в цьому дусі, тільки лаконічніше.
Нас цікавить тільки Editor's Draft і Working Draft з оглядкою на Recommendation. Якщо чесно, то W3 - ті ще гальма, і поки у них до Recommendation дійде (мільярди зірок згорять у космосі), то вже застаріє. Тобто ми будемо ставитись до CSS стандартів, як до живих (living), так само як влаштований HTML стандарт.
Приступимо до основ
З усіх модулів CSS є основні, без яких це БАЗА.
Syntax
Модуль синтаксису є основою. У ньому описується структура CSS, принципи побудови токенів та подальший їхній парсинг.
Values
Модуль визначає, як влаштована граматика. У всіх модулях ми зіштовхуватимемося з граматиками та основними типами CSS.
Наприклад, для якості width граматика має вигляд:
width = auto | | min-content | max-content | fit-content()
Крім цього, в модулі описуються основні типи та математичні функції:
, , , min() , max() і так далі.
Розглянемо, як розкладається тип:
Усі типи, що закінчуються -token, є токенами, отриманими з токенизатора, тобто несуть у собі різного роду дані.
У рамках цієї статті ми не розглядатимемо кожен тип. Ви все знайдете у вказаних мною специфікаціях. Я описую загальну картину.
CSSOM
HTML має DOM, CSS має CSSOM. Об'єктна модель дерево. Я не сказав би, що це основа, просто представлення CSS Style Sheets у вигляді набору об'єктів у дереві. Інакше кажучи, дає можливість керувати світом через JavaScript.
Все це основи, щоб не лякатися решти модулів та розуміти, чого в них написано. Ну, і щоб розуміти, з чим нерівний бій ми вестимемо далі.
Syntax: Tokenizer
Власне, як і скрізь, токенізатор приймає потік даних і розбиває його на токени.
Розглянемо приклад, візьмемо CSS:
Токенізатор створить токени:
"div" - " " - " "width" - ":" - " " - "10px" - " " - "!" - "important" - ">" -
Усі види токенів та алгоритм їх створення ви знайдете у специфікації. Тут нічого унікального.
Syntax: Parsing
Парсинг накопичує токени в певні структури передачі на подальшу обробку в різні модулі CSS.
Певна структура – це:
- Stylesheet (ще недавно називалася List of Rules)
- At-Rule
- Qualified Rule
- Block's contents
- Declaration
- Component value
- Simple block
- Function
Для прикладу трохи докладніше розглянемо Qualified Rule.
Знову візьмемо наш приклад:
Qualified Rule може мати прелюдії (Prelude) і правила (Rules).
- Prelude містить у собі Component value.
- Rules містить у собі Lists of Declarations.
width: 10px !important - це декларація. Коли їх багато, це список декларацій ( Lists of Declarations ).
Відповідно, декларація містить у собі:
- width - ім'я name
- 10px - значення value
- !important - important прапор.
Звичайні користувачі описали б це так:
Власне нічого складного. Інші структури влаштовані приблизно так само.
Наприклад, Stylesheet містить у собі Rules, а саме: At-Rule та Qualified Rule у будь-якій кількості.
Як ми вже зрозуміли, створені структури зі списками токенів мають кимось надалі оброблятися.
Цей хтось – інші модулі CSS.
Наприклад, модулю CSS Selectors передаються дані з Qualified Rule Prelude.
Модулю Media Queries на парсинг передається вся At-Rule структура. Кожному своє!
Теорія закінчена, починаємо життя
Давайте візьмемо чимало відомий bootstrap.css, вага якого ≈180KB.
З цього файлу токенізатор створить ≈51700 токенів без урахування коментарів. Кількість не маленька.
А тепер давайте уявімо, що ми першим циклом по них проходимо, коли формуємо структуру в синтаксисі, другим і наступними розбираємо по модулях.
Звичайно, наступні цикли будуть за меншою кількістю токенів, будуть відкинуті < , >, [ , ] і якісь ще залежно від ситуації.
Ось тут ми починаємо замислюватися з вами над питанням: а як оптимізувати?!
Варіантів продати оптимізований CSS парсер багато, розглянемо головні.
SAX Style
Ми можемо встановити зворотні виклики (callbacks) на всі стадії CSS Syntax структур і передавати туди токени.
Виглядатиме це приблизно так:
"div" - callback_qualified_rule_prelude() " " - callback_qualified_rule_prelude() - callback_qualified_rule_prelude() " "width" - callback_declaration_name() ":" - skip " " - skip "10px" - "callback "important" - skip - callback_declaration_important(true) ">" - skip
Як бачимо з невеликого прикладу, зворотних дзвінків буде багато. Стежити за цим зоопарком буде складно.
В даному варіанті дуже багато перекладається на користувача.
Також неабиякою проблемою є відсутність можливості подивитися наступний токен. Ось тут хтось може посперечатися і сказати: «Я ж можу покликати токенізатор і запитати його наступний токен, не порушуючи загального алгоритму». Відповідь буде простою: не можна так робити, ми не знатимемо, чи належить наступний токен до нашої, поточної, стадії чи ні. Відповідно, ми намагаємося взяти на себе подвійну роботу – ще раз аналізувати структуру CSS.
- Фіксована пам'ять для роботи токенізатора та парсера.
- Парсер займатиметься виключно відстеженням структури та викликом зворотних викликів.
- Легко продати підтримку парсингу частинами (chunks).
- Складність у підтримці для користувача. Багато перекладається на користувача.
- Неможливість одержати наступний токен.
Шалена реалізація — найшвидша
А давайте кожен модуль сам стежитиме за структурою CSS!
Взагалі все перекладемо на користувача, нехай розуміється, йому видніше.
Якщо користувач почав аналіз CSS Selectors, то, крім самої граматики селекторів, він повинен відстежувати вкладеність по токенам: < , >, ( , ) , function( .
Якось так це виглядатиме:
"div" — Declarations parse
Парсинг селекторів відбуватиметься так:
Звучить просто, але насправді все складніше:
- Знання про стадії для перемикання доведеться передавати кожному модулю, вони нічого не знають.
- Чи потрібно з'їдати токен перед передачею на наступну стадію?
- Необхідно стежити за глибиною вкладеності. Не можна просто передати керування наступному модулю під час зустрічі, наприклад, > . А якщо було (>)? Завжди необхідно точно розуміти, як описується структура CSS в даний момент. Це дуже проблематично і в рази ускладнює розробку, а налагодження точно, якщо щось піде не так.
- Часто, якщо виникла помилка парсингу, то виникла вона не там, де зупинився парсер або припустився помилки. Швидше за все, помилка відбувається значно раніше: якийсь модуль неправильно підрахував вкладеність чи захопив зайвий токен. Загалом підтримка цього варіанта дуже складна.
- Повний контроль над токенізатором. Токени можна отримувати та дивитися наперед.
- Швидкість, найшвидший варіант. Відсутність будь-яких зворотних дзвінків, парсинг безпосередньо.
Спочатку я «грав» з цим підходом до парсингу CSS.
- Не писати все вручну, а створити генератор Сі коду за граматиками.
- Навчити генератор коду слідкувати за глобальною CSS структурою.
Тобто віддаємо генератору коду граматику модуля Selectors, а він генерує Сі код парсингу з урахуванням глобальної CSS структури, так би мовити, підмішує в граматику Selectors розуміння структури.
Я досяг непоганих результатів у цьому підході, але вирішив зав'язати із цим напрямком. Створення такого генератора коду – завдання дуже велике і непросте, але цікаве. Одна стадія оптимізації чого варта.
Мною було вирішено повернутися до цього в той момент, коли проект lexbor масштабно використовуватиметься і реально буде потрібний сильний буст у швидкості.
Їжак навиворіт
Ми вже визначилися, що для нас дуже важливою є можливість керувати токенами, отримувати їх самостійно.
Коли ми говоримо про парсинг будь-яких даних, завжди представляємо чітку послідовність:
А якщо нам змусити токенізатор стежити за глобальною структурою CSS?
Тобто при запиті токена у токенізатора він десь усередині відстежуватиме CSS структуру.
Такий якийсь парсинг навиворіт.
Даний підхід реалізований у моєму проекті lexbor.
Принцип такий:
Ми задаємо зворотні виклики для різних стадій парсингу CSS структури.
На кожній стадії зворотний виклик зветься лише один раз. Якщо почалася прелюдія, то називається зворотний виклик початку. Не на кожен токен, а лише на початок.
Добре, але як же відстежувати структуру в наших зворотних викликах?
Ми створимо кілька функцій, проксі-функції для функцій токенізатора. Функцію отримання токена lxb_css_syntax_parser_token() та функцію споживання токена lxb_css_syntax_parser_consume() . Користувач буде звертатися не до токенізатора безпосередньо, а до наших проксі-функцій.
Алгоритм роботи функції lxb_css_syntax_parser_token() простий:
- Отримуємо токен із токенізатора.
- Аналізуємо токен у структурі CSS.
- Повертаємо користувачеві токен як він є, якщо він належить до поточної стадії парсингу, інакше повертаємо токен закінчення даних LXB_CSS_SYNTAX_TOKEN__TERMINATED .
При передачі користувачеві токена закінчення даних LXB_CSS_SYNTAX_TOKEN__TERMINATED аналізатор CSS структури перетворюється на стадію очікування рішення від користувача. Рішень може бути кілька:
- lxb_css_parser_success() - все пройшло успішно, дані, які ми очікували.
- lxb_css_parser_failed() — неочікувані дані.
- lxb_css_parser_memory_fail() - трапилася помилка виділення пам'яті, закінчуємо парсинг.
- lxb_css_parser_stop() - просто зупиняємо подальший парсинг.
Інакше кажучи, якщо користувач знаходиться в будь-якій стадії парсингу, наприклад, у стадії Prelude у Qualified Rule , то він з неї не вийде, і аналізатор структури CSS не перейде далі, поки користувач не поверне success або failed .
У цьому функції прийняття рішень можна викликати відразу. Якщо викликати lxb_css_parser_success() , а в поточній стадії будуть токени, що не належать до LXB_CSS_SYNTAX_TOKEN_WHITESPACE , поточна стадія автоматично перейде в failed . Це зручно тим, що нам не потрібно перевіряти всі токени, поки не прийде LXB_CSS_SYNTAX_TOKEN__TERMINATED . Якщо ми впевнені, що в поточній стадії все розпарсили, то повертаємо lxb_css_parser_success() , а якщо вже там будуть якісь токени, то все автоматично вирішиться.
Підсумковий алгоритм виглядає приблизно так:
1. Користувач бажає парсити `Qualified Rule`. Виставляє callback-і на початок `Prelude`, на початок блоку `Rules` і на закінчення парсингу `Qualified Rule`. 2. Запускає парсинг. 3. Початок циклу. 3.1. Ми викликаємо функцію отримання токена `lxb_css_syntax_parser_token()`. 3.1.1. Отримуємо токен від токенізатора. 3.1.2. Аналізуємо у структурі CSS. 3.1.3. Парсеру виставляється користувацький callback залежно від структури CSS. 3.2. Ми викликаємо встановлений callback з передачею йому отриманого токена. 3.3. Користувач у callback-і вигрібає всі токени до `LXB_CSS_SYNTAX_TOKEN__TERMINATED`, використовуючи функцію `lxb_css_syntax_parser_token()`. 3.4.Користувач повернув нам керування, переходимо до пункту 3.1.
Це дуже спрощена картина світу. Всередині функції lxb_css_syntax_parser_token() є фази - це перемикання між структурами CSS Qualified Rule, At-Rule і так далі + різні системні фази. Також там є свій стек, CSS структура рекурсивна, а ми не любимо рекурсії.
- Повне керування токенізатором.
- Швидкість: все відбувається на льоту.
- Безпека користувача. Цілком дотримується структура CSS.
- Простота розробки користувальницьких парсерів.
- Я не знайшов мінусів, на мою думку, це найбільш збалансований підхід парсингу CSS.
Парсинг - це добре, як тестувати будемо?
Щоб зрозуміти масштаб проблеми, розберемо, як улаштований синтаксис граматик. Значення в граматиках можуть мати комбінатори та мультиплікатори.
Комбінатори
Прямий порядок:
може містити такі значення:
Одне значення з перерахованих:
може містити такі значення:
Одне або всі значення з перерахованих у будь-якому порядку:
може містити такі значення:
Усі значення з перерахованих у будь-якому порядку:
може містити такі значення:
Значення можна групувати:
Мультиплікатори
Хто знайомий із regex, той зрозуміє відразу.
Нуль або нескінченна кількість разів:
може містити такі значення:
Один або нескінченне число разів:
може містити такі значення:
Може бути присутнім, а може ні:
може містити такі значення:
Може бути від A до B разів, період:
може містити такі значення:
Один або нескінченне число разів, розділені комою:
може містити такі значення:
Обов'язково одне значення має бути:
У цьому прикладі всередині групи допускається відсутність значення, але знак оклику ! вимагає хоча б одне, інакше помилка.
Мультиплікатори можуть комбінуватися:
Значення a через кому, від одного до п'яти.
Це все, що треба знати про граматику, найважливіше.
Тепер поглянемо на синтаксис граматики кольору:
= | currentcolor | = | | | transparent = | | | | | | | | | = [| ] = [ | ] = rgb(#,?) | rgb(#,?) = rgba(#,?) | rgba(#, ?) = rgb([ | | none] [ / [ | none] ]?) = rgba ([ | | none] [ / [ | none] ]?) . там далі ще багато.
З похмілля не розберешся.
При цьому парсер під це писати нескладно, навіть просто. Але писати всі варіанти тестів під це дуже важко.
А якщо уявити скільки різноманітних CSS декларацій, то зовсім руки опускаються.
Логічна думка - написати генератор тестів з граматики!
Це виявилось простіше сказати, ніж зробити. Не те щоб прямий рокетсайнс, а й людина з морозу задумається.
Основні проблеми, з якими я зіткнувся:
Комбінаторні бомби
Наприклад, якщо взяти таку граматику:
= none | [ underline | | overline || line-through || blink] = solid | double | dotted | dashed | wavy = = | ||
Формування тестів для піде в "нескінченність".
Всі значення повинні бути в різних варіантах пов'язані з і , а це дуже-дуже багато.
Довелося придумати обмежувач варіантів для групи - /1. Коса характеристика і значення, скільки варіантів буде взято з групи.
У результаті було перетворено на:
При цьому як властивість повністю формує свої тести і проходить їх окремо.
Граматики опускають прогалини ( LXB_CSS_SYNTAX_TOKEN_WHITESPACE ).
Зазвичай нижче під граматиками пишуть, що такі значення не повинні мати прогалини між собою.
Нас так не влаштовує, нам необхідно це врахувати безпосередньо у граматиці.
З'явився модифікатор ^WS (Without Spaces):
Перед прогалини неприпустимі.
Порядок парсингу.
Порядок вхідних даних може бути довільним, але після парсингу значення мають свої позиції в структурах і при серіалізації значення мають точний порядок.
Тести сформуються такі:
= a b c = a c b = b a c = b c a = c a b = c b a
Всі ці тести валідні, але результат після парсингу завжди буде = a b c . Виникло питання: як порівнювати з іншими?
Внутрішнє почуття підказувало, що завдання різко ускладнилося, але відчуття відваги (не недоумства) підштовхувало зробити все з розбігу.
І як ви розумієте, з розбігу увірватися та реалізувати не вдалося. Довелося думати!
Давайте подивимося на такий приклад:
= a &&[x || y || [f&&g&&h]]&&c
Стало ясно, що результат формування тестів кожної групи повинен повертати непросто тест, а й правильну відповідь цей тест.
Це трохи стало проблемою. Реалізація ускладнилася.
Були перепробовані різні рішення, я навіть сказав би, милиці, щоб не ускладнювати код. Але всі вони приносили купу винятків і на 10% неробочий код.
Здавалося б, призначити унікальний індекс зростання, залежно від того, де знаходиться значення, а в кінці, коли тест сформований, відсортувати індекси і отримати результат для парсера. Ось вам і тест і який результат поверне парсер.
Теж ні. У кожного значення є свої комбінатори і мультиплікатори, які можуть розставляти різні значення між значеннями.
Наприклад, десь може бути пробіл, десь ні, десь коми між значеннями. Якщо результат просто відсортувати, то вийде каша.
У результаті найнадійніше рішення лише одне – формувати тест та результат окремо. Інакше висловлюючись, формування результату проходитиме ті самі стадії, як і формування тесту.
Накладно, звичайно, але добре, у нас не реалтайм, ми можемо і почекати.
У результаті вийшов чудовий інструмент, який генерує тести з граматики.
Зараз тести для CSS Declaration (властивості) генеруються за 1 секунду, і їх кількість 82911 . На диску займають близько 20MB у форматі json.
Погодьтеся, писати вручну стільки тестів - час витратний.
Даний підхід дозволив виявити чимало проблем із парсингом властивостей, думаю, помилок 10 я впіймав. Зате тепер є 100% впевненість, що валідний CSS буде правильно розібраний.
І відразу виникає актуальне питання: а як тестувати не валідний CSS?
На цьому етапі я активно використовую Clang фазер. Але, звичайно, необхідно реалізувати неправильні/зламані тести, використовуючи граматику. Щоб із неправильних тестів не виходив хибний правильний результат.
Ось так виглядає поточний список гучномовців для декларацій у проекті lexbor.
Взагалі, говорячи про тестування
Написання тестів для коду займає стільки ж часу, скільки його розробка, або навіть більше.
Зараз проект lexbor проходить постійне тестування на приблизно 30 операційних системах з включеними asan, msan, UB, memory leak (де це можливо).
Також постійно працюють Clang фазери. Використовується Cppcheck.
До речі, сьогодні команда PVS-Studio порадувала, що відсипали річний ключ на їхній статичний аналізатор коду. Буду активно використовувати для тестування проекту.
Який статус проекту lexbor?
Незважаючи на довгу перерву, а вона була дійсно довгою, проект активно розвивається. Реалізовано вже багато, зараз триває реалізація layout/renderer tree.
Тобто незабаром проект створюватиме вікна, малюватиме гліфи, малюватиме block, inline і так далі.
Взагалі коду стільки, що статей на 5 вистачить, якщо воду не лити, а то й більше.
Перерва в розробці була пов'язана з роботою в NGINX, там я з головою пішов у розробку JS двигуна NJS. Що тільки на користь для проекту lexbor у плані знань.
На щастя, до мене приєдналася дуже хороша людина (брила англійської та технічної документації), допомагає з документацією до проекту, узяла на себе всю англійську мову і взагалі все, що буде пов'язано з документацією до проекту.
Де бенчмарки?
А їх не буде. Точніше, зараз не буде.
На даний момент проект lexbor підтримує тільки Selectors і Declarations (більше 70 властивостей) для повного парсингу. Точніше негаразд, повністю синтаксис, а вже у ньому Selectors і Declarations. Мені цього достатньо для подальшої розробки двигуна.
Як тільки з'являться @media, змінні та інше, відразу поміряємось «довжиною органу» з іншими.
Все не підтримуване CSS парсером формується значення CUSTOM . Тобто у підсумковому дереві всі дані будуть присутні, просто мати структуру *_custom_t.
Натякну лише, що lightningcss, написаний на Rust, який позиціонується як "An extremely fast CSS parser", мене зовсім не здивував.
Посилання
Перша стаття: HTML.
Посилання на проект: lexbor.
Скромні приклади CSS: CSS Examples.
Приклади використання HTML: HTML + CSS.
