Дескриптивный CSS

9 июля 2008 года, 15:52

Я надеюсь, что вы помните одну из наших забав, где мы писали парсер для дескриптивного HTML? Если помните, тогда я за вас рад и предлагаю вам перейти к созданию нечто аналогичного, а именно дескриптивного CSS.

Постоянные читатели блога знают, что иногда мне прям хочется сделать что-нибудь такое, что будет интересно самому себе и абсолютно не имеет значения, найдут ли такие наработки пользу в будущем или настоящем или же не найдут. В таких статьях важно другое, а именно их существование, их поднаготный смысл, их очарование, в чём-то. Но… предлагаю от пафоса перейти к разбору объекта статьи. Вперёд.

Теоретический смысл

Несмотря на то, что статья не имеет никакого отношения к микроформатам, в этом абзаце их целых два. Найдёте?

Когда мы с Денисом в последнем выпуске W3Cast`а говорили о принципах благоприятного климата CSS-файлов, мне пришла в голову одна интересная идея, которая была связана с обсуждением соблюдения иерархичности блоков CSS-стилей. Я придерживаюсь такой позиции, что CSS не создан для логичного отображения структуры CSS-блоков, то есть с помощью него или каких-то ухищрений достаточно сложно показать саму структуру иерархии CSS-блоков. Пусть данная идея может показаться навя(зч/щ)ивой, но она мне настолько понравилась, что я не пожалел времени реализовать парсер-конвертер из DCSS в CSS.

Я уверен, что вы хотите взглянуть на то, как же собственно выглядит DCSS-файл. Тогда встречайте наш файл-пример:

html, head { margin: 0; padding: 0; } div#hatom { margin: 0; padding: 0; .hentry { .entry-title { a { color: #aaa; } a:hover { color: #666; } } } font-size: 1em; }

Посмотрите же: документ выглядит полностью иерархично, с соблюдением всех канонов вложенности одного уровня иерархии в другой. Замечательно! Данный DCSS-файл в итоге должен превратиться в обычный CSS-файл с подобным содержимым:

html, head { margin: 0; padding: 0; } div#hatom { font-size: 1em; margin: 0; padding: 0; } div#hatom .hentry .entry-title a { color: #aaa; } div#hatom .hentry .entry-title a:hover { color: #666; }

Причём, у DCSS-парсера есть определённый наращиваемый потенциал в области документирования CSS-файлов и в области генерации последних. Так как при обработке DCSS-файла создаётся его структура, её можно вывести в качестве справочного материала в шапке генерируемого CSS-файла, а также создать комментарии-документацию к большинству узлов будущего CSS-файла. Мне кажется, что такие возможности были бы полезны в сложных системах, где важно не запутаться и не допустить каких бы то ни было ошибок. Сегодня мы в качестве примера реализуем объектно-ориентированную систему обработки DCSS-файлов. Читать дальше следует лишь тем, кто хочет заглянуть в непосредственное устройство системы обработки и конвертации.

Технический смысл

Технически говоря, парсер DCSS-файлов очень сильно напоминает парсер файлов дескриптивного HTML. Но мы не будем использовать созданные тогда классы, а попытаемся создать более объектную систему.

Всякая объектная система начинается с самого низа. В самом низу нашей системы у нас расположены узлы дерева (иерархии) DCSS-документа, которое образуется в результате парсинга. Именно поэтому для начала давайте сделаем класс DCSSHierarchyElement:

#Модуль нужен для обособления наших классов и создания единой среды работы с DCSS-файлами module DCSS #Абстрактный блок, реализация иерархии class DCSSHierarchyElement #Доступ к аттрибутам attr_accessor :parent, :children #Инициализация блока def initialize #Родитель блока @parent = false #Потомки блока @children = [] end #Есть ли у элемента дочерний элемент def has_children? @children.length != 0 end #Добавление нового дочернего элемента def child_add child #Setting up parent child.parent = self #Adding to children collection @children << child #Returning child end end end

Абстракция готова. Теперь на основе данной абстракции (которая, теоретически, уже может создавать на основе себя дерево любых размеров) мы можем создать класс CSS-блока, расширив наш класс элемента иерархии необходимыми нам свойствами:

module DCSS #Блок CSS-кода class DCSSBlock < DCSSHierarchyElement #Доступ к аттрибутам attr_accessor :path, :properties #Инициализатор класса def initialize super #Путь CSS-блока @path = "" #Внутренние настройки (имя - значение) @properties = {} end #Проверка существования свойств def has_properties? @properties.length != 0 end #Добавление нового свойства def property_add name, value @properties[name] = value end end end

Готово! Теперь параллельно данному классу нам нужен ещё один класс, но он будет, логически, на один уровень выше предыдущего. Чтобы ответить на вопрос «почему на один уровень выше?», нужно ответить на вопрос «что из себя составляют несколько CSS-блоков?». Ответ очевиден: они составляют CSS-файл, с которым мы и должны работать:

module DCSS #Отдельный DCCS-файл class DCSSFile #Свойства на экспорт attr_reader :filename, :structure #Инициализация def initialize filename #Возбуждаем исключение, если файла не существует raise "File not found" if not File.exist? filename #Устанавливаем имя файла @filename = filename #Устанавливаем начальное содержимое файла @plain_content = File.read @filename #Создаём корень DCSS-дерева @structure = DCSS::DCSSBlock.new @structure.path = "root" #Обрабатываем файл self.parse_to_structure end #Обработка DCSS-файла def parse_to_structure #Текущий блок current = @structure #Текстовый буфер buffer = "" #Проходимся посимвольно @plain_content.each_byte do |char| #Устанавливаем текущий символ symbol = char.chr #Конец определения свойства if symbol == ";" #Разделяем буфер property = buffer.split ":" #Пропускаем, если фальшивое определение if property.length != 2 buffer = "" next end #Добавляем новое определение-свойство current.property_add property[0].strip, property[1].strip #Обнуляем буфер buffer = "" #Переходим к следующему символу next end #Начало секции if symbol == "{" #Создаём новый дочерний блок child = DCSS::DCSSBlock.new child.path = buffer.strip #Добавляем дочерний к текущему current.child_add child #Устанавливаем текущий блок в новый current = child #Обнуляем буфер buffer = "" #Переходим к следующему символу next end #Окончание секции if symbol == "}" #Переходим на уровень выше current = current.parent #Переходим к следующему символу next end #Добавляем новый символ в буфер buffer += symbol end end end end

Данный класс включает в себя метод обработки DCSS-файла в обычную структуру DCSS-блоков с помощью ранее созданного класса. Если мы будем двигатсья далее и следовать всё той же логике, мы увидим, что экспорт DCSS-файлов — это совсем иная природа действия и мы просто должны вынести данную функциональность в совершенно другой, логически параллельный, класс, что и сделаем:

module DCSS #Класс экспорта DCSS в CSS class DCSSExport #Инициализатор def initialize document @dcss = document end #Экспорт в CSS def export outfile = "" if outfile != "" file = File.new outfile, "w+" else file = File.new @dcss.filename + ".css", "w+" end self.export_fill file, @dcss.structure file.close end #Функция рекурсивного вызова для экспорта def export_fill file, element #Если это не родительский элемент документа, то выводим его if element.path != "root" and element.has_properties? #Просчитываем путь names = [] temp_element = element until temp_element.parent == false names << temp_element.path temp_element = temp_element.parent end #Готовый путь file.puts names.reverse.join " " #Открывающий тег file.puts "{" #Выводим свойства element.properties.each do |prop_name, prop_value| file.puts "\t" + prop_name + ": " + prop_value + ";" end #Закрывающий тег file.puts "}" file.puts "" end #Возвращаемся назад, если нет дочерних элементов return if not element.has_children? #Обрабатываем дочерние элементы element.children.each do |child| export_fill file, child end end end end

Вот и всё. Представим, что исходный DCSS находится в файле sample.dcss, тогда мы можем сделать файл sample.dcss.css с конвертированными данными с помощью такой конструкции:

#Получаем наш документ document = DCSS::DCSSFile.new "path.dcss" #Передаём его на экспорт exporter = DCSS::DCSSExport.new document #Записываем в экспорт-файл exporter.export

Я думаю, что проще не бывает. Вы со мной согласны?

Заключение

Сегодня в рамках «забав» мы рассмотрели дескриптивный CSS. Такая информация нам была полезна с нескольких сторон: мы знаем, как можем улучшить CSS, чтобы не запутаться в нём при работе в крупных проектах. Помимо этого, мы теперь знаем, как можно обрабатывать чётко-структурированные файлы с особыми обособленными сущностями. Думаю, данные знания никогда не станут лишними, но мы не будем останавливаться на достигнутом и продолжим покорение вершин логичности. Удачи всем.

Мнения (24)

Все эти хорошие люди уже прокомментировали запись. Поделитесь собственным мнением, расскажите, что вы думаете о поставленной проблеме, задаче, озвученных мыслях.

  • Curly Brace

    09 июля 2008 г.17:47

    Дин, навящивой = навязчивой. Очень напрягает что открывающая скобка вынесена на следующую строку, теряется связь.. :(

    Пошел читать дальше.

  • Дин автор

    09 июля 2008 г.17:53

    Спасибо за исправление, считай это авторским знаком. :-)

    Открывающая скобка на новой строке? Ммм, это у меня стиль такой — у всех разный.

  • Sannis

    09 июля 2008 г.19:14

    Мне нравится, буду портировать на пэху :) Идея старая, но реально её никто не использует, пора бы и начать.

  • наташа

    09 июля 2008 г.19:21

    У нас преподаватели по программированию каждые пол-года менялись.

    Первая ставила скобки

    {

    Ляляля;

    Ляляля;

    }

    Вторая

    { ляляля;

    Ляляля;}

    А третья {

    Ляляля;

    Ляляля;

    {

    Потом каждая проводила прямую, соединяющую эти скобки, показывая их содержимое.

    Большинство остались на 1 варианте.

  • Дин автор

    09 июля 2008 г.19:26

    @Sannis, я надеюсь, код достаточно комментирован и Ruby не так сложен, чтобы портирование прошло успешно? :-)

    @наташа, да, стиль — штука тонкая. Один раз привыкнешь, потом будешь всех кусать, когда будут пытаться переучивать. ;-)

  • Curly Brace

    09 июля 2008 г.19:33

    @наташа, а мне кажется что для более логичной визуальной привязки больше всего подходит вид:

    #element {

    Property: value;

    Property: value;

    }

  • Curly Brace

    09 июля 2008 г.19:33

    Парсер лохе, хехе )

  • Дин автор

    09 июля 2008 г.19:39

    Это не «парсер лохе», это код надо по-человечески оформлять в конструкцию вида:

    <code type="тип_кода">

    //Ваш код

    </code>

    Бу! Но, тише, я вам об этом не говорил, вы сами узнали.

  • Дин автор

    09 июля 2008 г.19:44

    У меня глаз после C+ воспринимает конструкции вида:

    .config { font-size: 1.25em; margin: 0.175em; padding: 0 0.1em; }

    Считаю, что это вопрос лишь личной привязанности к определённому стилю. Например, в C было бы неудобно, если имеешь дело с кучей циклов, нагромождать постоянно { сразу после блока кода, а перенос строки помогает найти начало и ассоциировать его с концом (визуально они на одной линии, что помогает и не сбивает, как по мне).

  • Sannis

    09 июля 2008 г.19:51

    Дин, конечно, дошли бы руки :)

    Эх, как хорошо, что у нас был по программе только Delphi, не было проблем со скобками :D

  • Дин автор

    09 июля 2008 г.19:52

    @Sannis, поэтому я люблю Ruby и XML с его замечательными < и >! ;-)

  • Mischka

    10 июля 2008 г.01:09
    a{ link{ ... } hover{ ... } visited{ ... } }

    Такая структура, мне кажется более логичной. Поскольку link — дебильный выродок этой группы (или если хотите, вырожденный элемент), то конечная запись должна принять вид

    a{ ... hover{ ... } visited{ ... } }

    Про visited забывать нельзя.

  • Mischka

    10 июля 2008 г.01:12

    ЗЫ:

    «colleague friend» …Денисом

    «me» … последнем выпуске

  • Alexandr

    10 июля 2008 г.01:18

    @Mischka, в принципе логично, второй ресурс Дина.

  • Sannis

    10 июля 2008 г.03:40

    @Mischka, вы не против варианта

    a{ :hover{ ... } :visited{ ... } }

    ?

    Так по крайней мере не прийдётся в скрипте прописывать, какие могут быть :* у разных элементов :)

  • Miscђka

    10 июля 2008 г.10:08

    @Sannis, чисто визуально мне так даже больше нравицо.

    Да и для семантики разметки, наверное ваш вариант будет более правильным. Ибо

    a { smth }

    Это дочерний элемент, а

    a { :smth }

    Это псевдостиль текущего элемента.

    Но тогда нам нужно будет вернуться к вырожденному элементу :link ((

    Я весь в смятении.

  • Sannis

    10 июля 2008 г.13:17

    @Miscђka , не знаю, что там с a:link, но причин возвращаться к нему не вижу :)

    Набросал то, что уже есть в коде Дина, буду добавлять парсинг переменных и попробую придуматьчто-нибудь насчёт CSSDoc ;) Если кто-то действительно будет этим пользоваться, могу немного принять пожеланий по синтаксису. Вот только уеду отдыхать скоро, так что только в августе дописать смогу :(

  • Дин автор

    10 июля 2008 г.13:28

    @Sannis, клёво. Потом покажешь? :-)

    Эту штуку ведь можно использовать если не в продакшне, то уж при вёрстке точно, я считаю, так как будет удобнее следить за иерархичностью (вложенностью) элементов. А уже перед продакшном запустить утилиту, которая перекодирует DCSS в обычный CSS и, при желании, сожмёт его сразу же, удалив ненужные пробелы, к примеру. Одним словом, возможностей масса.

  • Sannis

    10 июля 2008 г.15:25

    @Дин, конечно.

  • Sannis

    15 июля 2008 г.05:13

    Упорядочил мысли относительно переменных, можно и на результат глянут :) Поеду отдохну, если интерес есть, то в августе продолжу.

  • Дин автор

    15 июля 2008 г.12:01

    @Sannis, красиво, особенно код.

  • Sannis

    15 июля 2008 г.20:37

    @Дин, всёж хорошо, когда среда подсказывает, так что предпочитаю документировать, да и всем советую :) Улучшения вроде корректной обработки экранированных символов и т.e. я пока делать не буду, но точно сделаю обработку глобального комментария для задания переменных, видимых во всём документе.

    P.S. Интересно, кроме нас двоих, у кого-то есть к этому интерес, или все с интересом смотрят только на диковинный билд WebKit с некрасивой, имхо,реализацией переменных.? :)

  • Дин автор

    15 июля 2008 г.21:03

    Кроме нас двоих? Ну не знаю, я уже использую активно собственную систему, и мне нравится.

    Прости, что система съела весь твой комментарий, я всё исправил. :-) Потом надо будет типограф чуть-чуть побить.

  • Евгений

    25 апреля 2009 г.13:59

    Хорошая идея, прочитав статью появляется вопрос — почему же не пользуются такой технологией? Плюсы — на лицо (наглядно, удобно, красиво))) Минусы — стереотипы привычек работы на разных языках программирования. Спасибо за статью.

Я тоже знаю!

Для обращения к человеку используйте символ @, после которого следует имя того, к кому обращаетесь (пробелы заменяются на знак подчёркивания). Если вам интересно, можете подписаться на комментарии по RSS или по эл. почте. Ведите себя достойно, вы же не роботы, правда?

Вы можете использовать следующие XHTML-элементы в разметке комментария: strong, em, span[class=crossline], a[href=uri], code[type=язык], blockquote, ul и ol. В качестве языка кода может быть указан, например, javascript или css.