Pull to refresh

Стандарты синтаксиса шаблонизаторов. Первые шаги

Reading time4 min
Views1.5K
Для затравки и предметного обсуждения нужна точка отсчета. В шаблонизаторе нас интересуют поддерживаемые типы конструкций, способ их обрамления, какие-то возможности (препросмотр без компиляции).
Основные типы конструкций для управления выводом в шаблоне:
  1. Переменные.
  2. Условные операторы.
  3. Операторы циклов.
  4. Инклуды, блоки.
  5. Наследование.

Уже этих конструкций с лихвой достаточно, чтобы реализовать большинство проектов. И само собой мы их видим почти в каждом шаблонизаторе, с теми или иными отличиями и особенностями. Но давайте поразмыслим, какие в теории возможности они должны позволять использовать, и как эти реализации должны выглядеть в идеале.

Для выделения любой конструкции в шаблоне используются различные символы обрамления. В разных шаблонизаторах это выглядит по разному:
<TMPL_VAR JOB>
{{ namevar }}
<% if(question) { %>

Тема выбора таковых заслуживает отдельного внимания, и чтобы не отвлекаться выберем что-то одно
% наша конструкция %


1. Переменные.


Самая часто используемая конструкция. Помимо простых вызовов, гораздо реже, но возникает необходимость обратиться к значению вложенных переменных. Еще реже, когда значение переменной содержится в другой переменной. Вложенными элементами могут быть как массивы так и хэши. Ну а теперь представьте все возможные комбинации из описанных условий.
Javascript:
var data = {
    title: "На дворе трава",
    img : {
        src: "/img/picture.jpg"
    },
    list : [ 'раз', 'два', 'три', 'четыре' ],
    namevar : "theme",
    noname: {
        theme : "На траве дрова"
    }
};

Шаблон:
% title % // data.title
% img.src % // data.img.src
% list.3 % // data.list[3]
% noname[ namevar ] % // data.noname[ data.namevar ]


Проблемы. В прекрасном javascript запись вида
data.list.12 == data['list']['12']; // эквивалентно

Может соответствовать как элементу массива, так и элемента хэша. Большинство серверных языков таким свойством похвастать не может. Исключить такую возможность, или сделать проверки на тип элемента? Как это скажется на производительности?
Вероятно может пригодится определение сигилов для типов переменных (переменная из конфига, служебная переменная, стандартная перемненная).

2. Условные операторы.


Тут вопроса два, давать ли использовать оператор unless? И определить список допустим логических операторов.
  1. С одной стороны оператор unless легко заменить на if с отрицанием. Возразить можно, что это нагромождение, пусть и небольшое. С другой стороны это уже, якобы, урезание(? в javascript например такого оператора нет) функционала.
  2. Большинство логических операторов так или иначе поддерживаются всеми языками программирования. Но, так как конкретна запись операторов отличается от языка к языку, становится ясно, что необходим какой-то промежуточный вариант, который парсером переводится в нужный. Необходимость в промежуточном варианте подкрепляется так же необходимостью в использовании регулярных выражений в условии. А синтаксис регулярных выражений одинаков для всех ЯП? Не уверен, и что-то подсказывает, что чем-то придется поступиться.


3. Операторы циклов.


Не так их и много. Можно порадоваться. for / foreach / while. В качестве данных может быть массив элементов, массив массивов, массив хэшей. Чаще всего встречается именно массив хэшей, и нужно обратить внимание на то, чтобы обеспечить максимальную простоту доступа к элементам итерируемого хэша. Передавать в конструкцию нужно название для цикла (чтобы обращаться к вложенным элементам внутри других циклов), по необходимости источник для итераций, срез массива, название метода/функции обработчика итерируемого элемента.
% nameobj => source.array.data [3..5] :loop_handler %

Где nameobj название для объекта. Дальше мы можем обратиться к элементу:
% nameobj.img.src %

Для источника данных может использоваться путь до любой переменной source.array.data
Порой бывает и такое, что нам нужен срез массива, с третьего по пятый элемент [3..5]
Перед вставкой данных бывает необходимость форматировать их, для этого нм может послужить некий обработчик, выполняющийся при каждой итерации, которому передается ссылка на текущий итерируемый элемент.

4. Инклуды, блоки.


Очень часто нам приходится вставлять в шаблон другие или часть других шаблонов. Синтаксис должен предусмотреть возможность передачи аргумента в инклуд.
Блоки, это такие же инклуды, за тем лишь исключением что они не вынесены в отдельный файл. Соответственно при обращении к блоку, нужно указывать и имя шаблона, в котором он находится.
% nameinclude %
% nameinclude => source.data.inc %
% var.for.inc => source.for.inc %

Нужно дать возможность задавать имя инклуда через перменную, пригодится для альтернативы наследованию.

5. Наследование.


Честно сказать всегда обходился без использования наследования в шаблонах, но это не отменяет необходимость иметь такую возможность, наверное. Но сделать это максимально просто. При этом наверняка хорошо иметь возможность не просто переопределять отдельные блоки из наследуемого шаблона, а так же делать замену блока на существующий инклуд/блок. Это можно решить путем задания имени инклуда через переменную. Да собственно таким путем и решается задача наследования: определить все заменяемые блоки как инклуды, и управлять их названиями…

Некоторые шаблонизаторы позволяют использовать в шаблоне конструкции, для написания кода непосредственно на ЯП. Очевидно что такие решения не то, что нам нужно.

Сложно рассматривать какие-то конструкции более подробно без понимания, какими символами они будут обрамлены, и какие префиксы будут использованы для переменных и операторов внутри контрукции. Составив список конструкий, которые мы собираемся использовать, можно будет перейти к рассмотрению обрамляющих символов, тэгов, сигилов.

UPD: Прошу прощения, если не ясно выразился. То, что в качестве обрамляющих символах в примерах представлен символ % – это просто пример. Это совсем не предложение его использовать, просто, чтобы что-то поставить.
Tags:
Hubs:
Total votes 25: ↑15 and ↓10+5
Comments39

Articles