Еще один раз о дереве с xsl

Не так давно я начал изучать xsl (и все с ним сопряженное). Лаконичность конструкций и его принадлежность к xml мне очень нравятся. Я не буду каждый раз писать, для чего я делаю то, или другое. Наверное, главной причиной является академический интерес, а потом уже все остальное.

В этот раз я решил трансформировать xml в дерево, которое можно просматривать с помощью броузера. Формат xml  я решил взять примерно такой, какой выплевывают mysql и mysqldump с ключем —xml при запуске.

Итак, входной документ xml, который нужно трансформировать в дерево:

items.xml
<items>

<item id=«0« caption=«Root«>

</item>

<item id=«1« caption=«level 1:1« parent=«0«>

</item>

<item id=«18« caption=«level 7:1« parent=«17«>

</item>

<item id=«19« caption=«level 6:2« parent=«16«>

</item>

</items>

XML Highlighter:
powered by dedestr

Далее сам шаблон xsl

tree.xsl
<xsl:stylesheet version=«1.0«>

<xsl:template match=«/items«>

<html>

<head>

</head>

<body>

<div>

<ul class=«tree«>

<xsl:apply-templates select=«/items/item[@id=0]«>

</xsl:apply-templates>

</ul>

</div>

</body>

</html>

</xsl:template>

<xsl:template match=«/items/item«>

<xsl:variable name=«parent« select=«@id«>

</xsl:variable>

<li>

<a>

<xsl:choose>

<xsl:when test=«/items/item[@parent=$parent]«>

<xsl:attribute name=«class«>
man

</xsl:attribute>

</xsl:when>

<xsl:otherwise>

<xsl:attribute name=«href«>
#

</xsl:attribute>

</xsl:otherwise>

</xsl:choose>

<xsl:value-of select=«@caption«>

</xsl:value-of>

</a>

<xsl:if test=«/items/item[@parent=$parent]«>

<ul>

<xsl:apply-templates select=«/items/item[@parent=$parent]«>

</xsl:apply-templates>

</ul>

</xsl:if>

</li>

</xsl:template>

</xsl:stylesheet>

XML Highlighter:
powered by dedestr

XPath выражение item[@id=0] в девятой строке предыдущего листинга выбирает элементы item, атрибут id которых равен 0 (что в нашем случае соответствует корню дерева). Такой элемент будет 1. Элемент xsl:apply-templates вызывает шаблон, который может принимать item для обработки.
Шаблон для обработки этого элемента начинается с шестнадцатой строки листинга.

Тут же стоит упомянуть, что дерево я решил делать в этом случае с помощью списков ul. В каждом узле дерева будет ссылка, которая, либо предназначена для свертывания дочерних узлов, либо именно для перехода к нужному контенту.
Для этого в двадцать первой строке реализуется выбор с помощью элемента xsl:choose. Если дочерние элементы у текущего есть, устанавливается атрибут class со значением man. Если же это лист дерева, устанавливается атрибут href, который, естественно, в реальных случаях вряд ли будет ссылаться на этот же документ, как сделано в примере.

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

После того, как определился нужный параметр, выводим (тридцать первая строка листинга) заголовок узла.

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

Этот небольшой шаблон позволяет вывести структурированное дерево.

Если взглянуть на пример, то даже назвать такое деревом вряд ли можно. Но, мы не забываем о том, что у нас есть css и javascript, и в другой записи, я покажу, как можно не используя изображений оживить наше дерево, например, в такое:

Деревце

Рубрика: XSL | Оставить комментарий

15 минут ни о чем

Бред «Чтобы»

Давай жить так, чтобы всем запоминалось.
давай, чтобы нам все в жизни удавалось.

Чтоб не было в жизни печали.
Чтоб все начинанья конец свой встречали.

Чтоб несправедливость вокруг испарялась,
И мысли о ней не оставалось.

Чтоб поколения нами гордились.
Чтобы от скуки никогда не томились.

****

Чтобы прыжок на три метра,
Чтобы взор против ветра.

Чтобы спина не сгибалась,
Чтоб война нАми кончалась.

Чтобы стремление как стрела,
Чтобы мечта впереди была.

Чтоб идеалам не изменяли,
Чтобы желания мы усмиряли.

Ближним всегда чтоб помогали,
Чтобы об этом просто молчали.

С первого слова чтоб понимали,
Зла на других чтоб не держали.

Конца нет

Рубрика: Бред | Оставить комментарий

Списки уникальных строковых значений

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

Сразу мне в голову пришло четыре сходных решения (я не включил сюда альтернативу БД).

  • Первые два способа заключаются в том, чтобы строки хранить в значениях массива. Проверять наличие строки функцией:
    • 1. array_search()
    • 2. in_array()
  • Последние два предусматривают хранение строк в ключах массива. Проверять их наличие:
    • 3. isset()
    • 4. array_key_exists()

Чтобы не тратить много времени, я быстро написал тест для всех четырех способов, который дал следующий результат:

array_search():

6.5036978721619
6.4741249084473
6.5003960132599
6.4547579288483

in_array()

6.4824228286743
6.232085943222
6.5026969909668
6.559093952179

isset()

0.03310489654541
0.037132978439331
0.042035818099976
0.03764009475708

array_key_exists()

0.049704074859619
0.045562028884888
0.045705080032349
0.045676946640015

Для тестов использовалась одна тестирующая функция (принимающая массив и имя функции, проверяющей наличие значения) и отдельные сессии php-интерпритатора.
Тестирующий код:

test.php
<a href=»?t=a»>array_search()</a><br />
<a href=»?t=b»>in_array()</a><br />
<a href=»?t=c»>isset()</a><br />
<a href=»?t=d»>array_key_exists()</a><br />
<?php
function onListA($list$val) {
    return 
false !== array_search(md5($val), $list);
}
function 
onListB($list$val) {
    return 
in_array(md5($val), $list);
}
function 
onListC($list$key) {
    return isset(
$list[md5($key)]);
}
function 
onListD($list$key) {
    return 
array_key_exists(md5($key), $list);
}
define(‘COUNT’30000);
function 
prepareAB() {
    
$result = array();
    for (
$i 0$i COUNT/10;) {
        
$result[] = md5(++$i);
        
$result[] = md5(++$i);
        
$result[] = md5(++$i);
        
$result[] = md5(++$i);
        
$result[] = md5(++$i);
        
$result[] = md5(++$i);
        
$result[] = md5(++$i);
        
$result[] = md5(++$i);
        
$result[] = md5(++$i);
        
$result[] = md5(++$i);
    }
    return 
$result;
}
function 
prepareBC() {
    
$result = array();
    for (
$i 0$i COUNT/10;) {
        
$result[md5(++$i)] = 0;
        
$result[md5(++$i)] = 0;
        
$result[md5(++$i)] = 0;
        
$result[md5(++$i)] = 0;
        
$result[md5(++$i)] = 0;
        
$result[md5(++$i)] = 0;
        
$result[md5(++$i)] = 0;
        
$result[md5(++$i)] = 0;
        
$result[md5(++$i)] = 0;
        
$result[md5(++$i)] = 0;
    }
    return 
$result;
}
function 
microtime_float()
{
    list(
$usec$sec) = explode(» «microtime());
    return ((float)
$usec + (float)$sec);
}
function 
test($func$array) {
    
$time_start microtime_float();
    for (
$i 0$i COUNT; ++$i) {
        
$func($array$i);
    }
    
$time_end microtime_float();
    echo 
$time_end — $time_start;
}
switch (
$_GET[‘t’]?$_GET[‘t’]:‘a’) {
    case 
‘a’test(‘onListA’prepareAB()); break;
    case 
‘b’test(‘onListB’prepareAB()); break;
    case 
‘c’test(‘onListC’prepareBC()); break;
    default : 
test(‘onListD’prepareBC());
}
?>

Думаю, результаты теста говорят сами за себя.

Рубрика: php | Оставить комментарий

Кэширование на клиенте: Expire

Речь пойдет именно о версии 1.1 протокола HTTP и кэшировании на стороне клиента. Под клиентом я имею ввиду не только броузер на рабочей станции но и любой прокси-сервер на пути к нему.

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

var path = http://some.server.org/any/script/path?r=&#8217; + Math.random();

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

То, откуда берет броузер данные, из кеша, или запрашивает у сервера, на самом деле зависит от заголовков в ответе сервера.  Заголовки HTTP описаны в RFC2616. я постараюсь часть, относящуюся к кэшированию на стороне клиента, объяснить исходя из этого комментария человеческим языком, но не уходя далеко от первоначального источника.

Истечение срока документа

Основная задача HTTP кэширования — прозрачное предотвращение клиентским кэшем запроса к серверу и отдача свежего содержимого клиенту. Главный способ такого предотвращения запросов состоит в том, что сервер может явно указать, когда в будущем документ можно будет считать просроченным. И, соответственно, до того момента можно считать, что документ не был изменен и является актуальным.

Здесь стоит подметить, что дату истечения срока документа нужно тщательно определять, потому-что иногда выдача неактуальной информации из кеша в ответ на запрос может нести серьезные последствия.

Таким образом, если серверу требуется, чтобы запрос не кэшировался вовсе, он может явно указать дату истечения документа на прошедшее время. Фактически это означает, что ответ сервера всегда просрочен, и его актуальность требуется проверить. Так же в этом случае сервер должен указать директиву must-revalidate в заголовке Cache-Control.

Протокол HTTP предусматривает два основных заголовка для управления истечения срока документа: Expires и Cache-Control. В этой записи я расскажу о первом заголовке.

Был еще один заголовок: Pragma:no-cache. Он запрещал кэширование документа. Этот заголовок поддерживается в режиме совместимости с протоколом HTTP версии 1.0 некоторыми клиентами и является ненадежным. Это значит, что не все клиенты перестанут кэшировать документы с таким заголовком, и полагаться на него не стоит.

Заголовок Expires

Значение этого заголовка говорит о том, начиная с какой даты и времени документ можно считать просроченным, т.е. когда его нужно снова запрашивать у сервера или удостоверяться в его актуальности. Формат заголовка:

Expires: HTTP-дата

Дата в этом заголовке имеет формат, определенный в RFC1123, например:

Thu, 01 Dec 1994 16:00:00 GMT

Следует не забывать, что дата в заголовке Expires должна быть согласована с серверными настройками даты и времени, потому-что сервер обязан включать свои дату и время в заголовоке Date.

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

Резюмируя

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

Рубрика: http | Оставить комментарий