Template Toolkit: Пособия: Работа с данными

Template Toolkit

(русская редакция)

[ Пособия ] [ Руководство ] [ Модули ] [ Библиотеки ] [ Утилиты ] [ Вопросы ] [ Релиз ] [ Perl-ресурсы ] Форум ]
 
Поиск
Template Toolkit | Пособия | Работа с данными

Работа с данными

[ ◄ Контент сайта ]
Создание файлов с данными с использованием Template Toolkit.

Оглавление

ОПИСАНИЕ

Индекс ] [ Пособия ] [ Наверх ]

Данное пособие является обзором возможностей Template Toolkit по чтению и записи файлов с данными, представленными в различных форматах и стилях. Пособие было написано Дэйвом Кроссом (Dave Cross) и впервые было опубликовано в качестве статьи на http://www.perl.com/ в 2001 году.

Введение в Template Toolkit

Индекс ] [ Пособия ] [ Наверх ]

Существует ряд модулей Perl, которые повсеместно признаны "правильными" в определенных задачах. Если Вы обращаетесь к базе данных без использования DBI, тянете данные из Сети без использования одного из модулей LWP или парсите XML без использования XML::Parser или одного из его подклассов, вы рискуете стать изгоем в сообществе Perl.

Я надеюсь, что 2000 год станет годом рождения еще одного такого ('must have') модуля - Template Toolkit. Я не одинок в этом убеждении, ведь не случайно Template Toolkit был признан 'лучшим новым модулем ('Best New Module') на прошедшей летом конференции по Perl (Perl Conference). Template Toolkit версии 2.0 (известный среди почитателей как TT2) был на днях включен в архив CPAN.

Спроектировал и написал TT2 Энди Уардли (Andy Wardley <abw@wardley.org>). Модуль вырос из предыдущего модуля Энди по работе с шаблонами Text::Metatext, который по совету Фреда Брукса (Fred Brooks) был полностью переписан, и преследует цель стать наиболее полезной (или, по крайней мере, наиболее используемой) системой по работе с шаблонами для Perl.

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

Использование Template Toolkit

Индекс ] [ Пособия ] [ Наверх ]

Давайте посмотрим как мы будем использовать TT2 для обработки простого файла с данными. TT2 - объектно-ориентированный модуль. После загрузки его с CPAN, и стандартной установки, использовать его в вашей программе настолько просто, насколько просто добавить в ваш код строки:

    use Template;
    my $tt = Template->new;

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

Для обработки шаблона, вам нужно вызвать метод 'process':

    $tt->process('my_template', \%data)
        || die $tt->error;

Мы передаем 'process' два параметра, первый - имя файла, содержащего шаблон, который требуется обработать (в примере, my_template) и второй - ссылка на хэш, элементами которого являются данные, которые вы хотите использовать в шаблоне. Если в ходе обработки шаблона возникнет ошибка, программа аварийно завершится с (будем надеятся) полезным сообщением об ошибке.

Что можно включать в '%data'? Ответ - практически все что угодно. Ниже пример данных о командах английской премьер-лиги.

    my @teams = ({ name   => 'Man Utd',
                   played => 16,
                   won    => 12,
                   drawn  => 3,
                   lost   => 1 },
                 { name   => 'Bradford',
                   played => 16,
                   won    => 2,
                   drawn  => 5,
                   lost   => 9 });
    my %data = ( name   => 'English Premier League',
                 season => '2000/01',
                 teams  => \@teams );

Мы создали три элемента с данными, к которым можно получить доступ из шаблона: 'name', 'season' и 'teams'. Обрабтите внимание на то, что 'teams' сложная структура данных.

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

    League Standings
    League Name: [% name %]
    Season: [% season %]
    Teams:
    [% FOREACH team = teams -%]
    [% team.name %] [% team.played -%]
     [% team.won %] [% team.drawn %] [% team.lost %]
    [% END %]

Обработка этого шаблона с этими данными дает следующий результат:

    League Standings
    League Name: English Premier League
    Season: 2000/01
    Teams:
    Man Utd 16 12 3 1
    Bradford 16 2 5 9

Будем надеяться, что синтаксис шаблона прост для понимания. Сделаем несколько пояснений.

  • Директивы процессора шаблонов написаны с использованием простого языка, который не является Perl.

  • Ключи хэша '%data' преобразуются в имена перемнных внутри шаблона.

  • Директивы процессора шаблонов заключены в последовательность '[%' и '%]'.

  • Если эти теги заменены на '[%-' '-%]', то предшествующий и завершающий переводы строки подавляются.

  • В цикле 'FOREACH' переменной 'team' присваивается каждый элемент массива 'teams'.

  • Каждый элемент, присваиваемый переменной 'team' - хэш. Доступ к отдельным элементам хэша осуществляется с использованием точки.

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

Последнее замечание показывает способ, каким TT2 ограждает дизайнера шаблона от внутренней реализации структуры данных. Данные, передаваемые процессору шаблонов могут быть скалярами, массивами, хэшами, объектами и даже функциями. Процессор правильно проинтерпретирует ваши данные и "сделает правильный выбор", чтобы возвратить корректное значение. В этом примере каждая команда была хэшем, но более крупной системе команда может быть объектом, в котором доступ к соответсвующим свойствам будет осуществляться посредством методов 'name', 'played' и т.д. Изменения в шаблон при этом вносить не надо, поскольку процессор шаблонов поймет, что он должен вызывать методы, а не возвращать значения хэша.

Более сложный пример

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

Следовательно, если у вас есть данные со списками, в которых содержится имя команды вместе с количеством побед, ничьих и поражений, забитых и пропущенных мячей, разбитых на домашние и выездные (всего одинадцать элементов), вы можете все остальные элементы (разница мячей, набранные очки и даже место в лиге). Давайте рассмотрим такой файл, но ограничимся верхними тремя командами. Он будет выглядеть примерно так:

    Man Utd,7,1,0,26,4,5,2,1,15,6
    Arsenal,7,1,0,17,4,2,3,3,7,9
    Leicester,4,3,1,10,8,4,2,2,7,4

Простой скрипт, который разложит эти данные в массив хэшей будет выглядеть примерно так (для краткости я упростил названия колонок данных - w, d и l обозначают победы, ничьи и поражения соответственно, f и a - забитые и пропущенные мячи; префиксы h и a перед названиями колонок обозначают соответственно домашнюю и выездную статистику):

    my @cols = qw(name hw hd hl hf ha aw ad al af aa);
    my @teams;
    while (<>) {
        chomp;
        my %team;
        @team{@cols} = split /,/;
        push @teams, \%team;
    }

Теперь мы можем снова пройти по списку команд и рассчитать все производные элементы:

    foreach (@teams) {
        $_->{w} = $_->{hw} + $_->{aw};
        $_->{d} = $_->{hd} + $_->{ad};
        $_->{l} = $_->{hl} + $_->{al};
        $_->{pl} = $_->{w} + $_->{d} + $_->{l};
        $_->{f} = $_->{hf} + $_->{af};
        $_->{a} = $_->{ha} + $_->{aa};
        $_->{gd} = $_->{f} - $_->{a};
        $_->{pt} = (3 * $_->{w}) + $_->{d};
    }

И затем отсортировать список по убыванию:

    @teams = sort {
	$b->{pt} <=> $b->{pt} || $b->{gd} <=> $a->{gd}
    } @teams;

И, наконец, добавим элемент, обозначающий место в лиге:

    $teams[$_]->{pos} = $_ + 1
        foreach 0 .. $#teams;

Загнав все данные во внутреннюю структуру данных мы можем сгенерировать результат с использованием выходного шаблона. Шаблон для CSV файла, содержащего данные с разделенной домашней и выездной статистикой, будет выглядеть следующим образом:

    [% FOREACH team = teams -%]
    [% team.pos %],[% team.name %],[% team.pl %],[% team.hw %],
    [%- team.hd %],[% team.hl %],[% team.hf %],[% team.ha %],
    [%- team.aw %],[% team.ad %],[% team.al %],[% team.af %],
    [%- team.aa %],[% team.gd %],[% team.pt %]
    [%- END %]

После обработки процессором:

    $tt->process('split.tt', { teams => \@teams }, 'split.csv')
      || die $tt->error;

мы получаем следующий вывод:

    1,Man Utd,16,7,1,0,26,4,5,2,1,15,6,31,39
    2,Arsenal,16,7,1,0,17,4,2,3,3,7,9,11,31
    3,Leicester,16,4,3,1,10,8,4,2,2,7,4,5,29

Обратите внимание, мы использовали третий аргумент в вызове 'process'. Если этот аргумент опущен TT2 выводит результат на 'STDOUT'. Если этот аргумент скаляр, он интерпретируется как файла, в который записывается вывод. Также этот аргумент (помимо прочего) может быть файловым дескриптором или ссылкой на объект, у которого предполагается наличие метода 'print'.

Если бы нас не интересовало разделение на домашние и выездные игры, мы могли бы использовать более простой шаблон:

    [% FOREACH team = teams -%]
    [% team.pos %],[% team.name %],[% team.pl %],[% team.w %],
    [%- team.d %],[% team.l %],[% team.f %],[% team.a %],
    [%- team.aa %],[% team.gd %],[% team.pt %]
    [% END -%]

А вывод выглядел бы следующим образом:

    1,Man Utd,16,12,3,1,41,10,6,31,39
    2,Arsenal,16,9,4,3,24,13,9,11,31
    3,Leicester,16,8,5,3,17,12,4,5,29

Генерация XML

Индекс ] [ Пособия ] [ Наверх ]

Посмотрим теперь насколько мощен и гибок TT2, ведь вы, наверняка, отметили, что весь этот вывод можно легко реализовать парой вызовов 'print' в цикле 'foreach'. Разумеется это так; но это лишь потому, что я сознательно выбрал простой пример для объяснения принципов. Что если мы захотим сгенерировать файл данных в формате XML? И что если (о чем я упоминал) выше, данные о лиге будут храниться в объекте? Код, в этом случае, будет выглядеть даже проще, поскольку большая часть кода написанного выше будет скрыта в модуле 'FootballLeague.pm'.

    use FootballLeague;
    use Template;
    my $league = FootballLeague->new(name => 'English Premier');
    my $tt = Template->new;
    $tt->process('league_xml.tt', { league => $league })
        || die $tt->error;

А шаблон 'league_xml.tt' будет выглядеть следующим образом:

    <?xml version="1.0"?>
    <!DOCTYPE LEAGUE SYSTEM "league.dtd">
    <league name="[% league.name %]" season="[% league.season %]">
    [% FOREACH team = league.teams -%]
      <team name="[% team.name %]"
            pos="[% team.pos %]"
            played="[% team.pl %]"
            goal_diff="[% team.gd %]"
            points="[% team.pt %]">
         <stats type="home"
        	win="[% team.hw %]"
        	draw="[%- team.hd %]"
        	lose="[% team.hl %]"
        	for="[% team.hf %]"
        	against="[% team.ha %]" />
         <stats type="away"
        	win="[% team.aw %]"
        	draw="[%- team.ad %]"
        	lose="[% team.al %]"
        	for="[% team.af %]"
        	against="[% team.aa %]" />
      </team>
    [% END -%]
    </league>

Обратите внимание, что поскольку мы передали методу 'process' объект целиком, нам необходимо добавить дополнительный уровень адресации к нашим переменным в шаблоне - теперь все они являются элементами переменной 'league'. За исключением этого все в шаблоне подобно тому, что мы делали ранее. Вероятно, теперь 'team.name' приводит к вызову функции для доступа к переменной, а не к поиску в элемента в хэше, но все это остается прозрачным для нашего дизайнера шаблона.

Различные форматы

Индекс ] [ Пособия ] [ Наверх ]

В качестве последнего примера предположим, что нам нужно создать таблицы футбольной лиги в различных форматах. Возможно мы передаем эти данные различным людям и не все они могут использовать одинаковый формат. Одним пользователям нужны CSV файлы, а другим XML. Кому-то нужны данные с разбивкой на домашнюю и выездную статистику, а кому-то только итоговые цифры. В общем, нам понадобятся четыре различных шаблона, но хорошее обстоятельство заключается в том, что мы можем использовать один объект данных. Все что будет необходимо сделать скрипту, это установить какой шаблон нужно использовать и обработать его.

    use FootballLeague;
    use Template;
    my ($name, $type, $stats) = @_;
    my $league = FootballLeague->new(name => $name);
    my $tt = Template->new;
    $tt->process("league_${type}_$stats.tt",
                 { league => $league }
                 "league_$stats.$type")
        || die $tt->error;

Например, вы можете вызвать скрипт следующим образом:

    league.pl 'English Premier' xml split

В результате будет обработан шаблон 'league_xml_split.tt', а вывод будет сохранен в файл 'league_split.xml'.

Это уже начинает прояснять истинную мощь Template Toolkit. Если позже мы захотим добавить другой формат - например, мы захотим создать HTML страницу с таблицей лиги и даже документ LaTeX - все что нам будет нужно сделать это создать подходящий шаблон и назвать его в соответствии с существующим соглашением об именах. Нам не нужно будет делать никаких изменений в коде.

Надеюсь, теперь вы понимаете, почему для многих людей Template Toolkit так быстро становится существенной частью установки Perl.

АВТОР

Индекс ] [ Пособия ] [ Наверх ]

Дэйв Кросс (Dave Cross <dave@dave.org.uk>)

ВЕРСИЯ

Индекс ] [ Пособия ] [ Наверх ]

Template Toolkit версия 2.14, дата релиза - 4 октября 2004.

АВТОРСКИЕ ПРАВА

Индекс ] [ Пособия ] [ Наверх ]

Copyright (C) 2001 Dave Cross <dave@dave.org.uk>

Этот модуль является свободно-распространяемым программным обеспечением; вы можете распространять и/или модифицировать его на тех же условиях, что и Perl.