egoroff.spb.ru - портфель


egoroff.spb.ru - портфель
http://www.egoroff.spb.ru/portfolio/
Материалы портфеля в RSS формате.
ru
1440
Работаем с .htpasswd
Вашему вниманию предлагается простенький класс, работающий с файлом .htpasswd - это файл механизма стандартной авторизации веб-сервера Apache, содержащий список пользователей имеющих доступ в какой-либо каталог.Здесь рассмотрены 2 реализации данной задачи, - с использованием таблицы и с использованием хэша. Автор первой не я, а Павел Титов, опубликовавший данный код на форуме сайта parser.ru, я лишь поправил его для себя и немного упростил. Вторая реализация сделана мной - и тут опять работает правило - всё что вы делаете (или делали), можно сделать проще, т.к. вторая реализация (с хэшем), гораздо элегантнее. Вообщем как всегда, - век живи, век учись.Краткое описаниеПри создании объекта класса его конструктору - @init передаётся строка содержащая путь к файлу .htpasswd, конструктор пытается найти файл по указанному пути, если ему это не удаётся, он создаёт новый файл, который потом (при вызове методов класса) и записывается по указанному пути.Класс имеет следующие методы, которые можно вызывать извне:
@setpw - добавление нового пользователя, если пользователь с таким именем уже существует, просто меняется его пароль.
@setpwCrypted - то же самое что и выше, но вторым параметром метода передаётся не пароль, а уже его хэш. Мне это нужно для совместного использования с классом авторизации от Михаила Петрушина.
@delete - удаление пользователя.
Код класса в реализации с таблицей
##############################
# $Id: htpasswd.p,v 1.19 2004/01/02 12:28:45 egr Exp $
# Класс для работы с .htpasswd
# Идея Pavel Titov (pavel@titov.pp.ru)
# Я маленько усовершенствовал
@CLASS
htpasswd
##############################
# Конструктор
# _tPasswd - таблица пользователь/пароль
# _sFileName - полный путь к файлу .htpasswd (с путём)
@init[sFileName][lHtpass]
$_sFileName[$sFileName]
^try{
$lHtpass[^file::load[text;$sFileName]]
$lHtpass[^lHtpass.text.match[\n][g]{^#0A}]
$_tPasswd[^table::create{login password^#0A^lHtpass.match[:][g]{ }}]
}{
^if($exception.type eq file.missing
|| $exception.type eq parser.runtime
){
$exception.handled(1)
$_tPasswd[^table::create{login password}]
}
}
##############################
# Добавление пользователя / изменение пароля
@setpw[sLogin;sPassword][lCrypted]
$lCrypted[^math:crypt[$sPassword;^$apr1^$]]
^setpwCrypted[$sLogin;$lCrypted]
##############################
# Добавление пользователя / изменение пароля
# с передачей не самого пароля а уже его хэша
# Работает просто - если пользователь есть, он сначала удаляется,
# а потом записывается новый, с таким же именем, но другим паролем
# Если пользователя нет, понятно что происходит в этом случае :)
@setpwCrypted[sLogin;sPassword]
^if(^_tPasswd.locate[login;$sLogin]){
$_tPasswd[^_remove[$_tPasswd;^_tPasswd.line[];1]]
}
^_tPasswd.append{$sLogin $sPassword}
^_save[]
##############################
# Удаление пользователя
@delete[sLogin]
^if(^_tPasswd.locate[login;$sLogin]){
$_tPasswd[^_remove[$_tPasswd;^_tPasswd.line[];1]]
}
^_save[]
##############################
# Сохранение .htpasswd
@_save[]
$result[^_tPasswd.menu{${_tPasswd.login}:$_tPasswd.password^#0A}]
^result.save[$_sFileName]
$result[]
##############################
# http://parser.ru/forum/?id=3803
## удаление строк из таблицы
# результат - таблица
# t - имя таблицы
# from - начиная с какой строки удалять (!!! нумерация строк с 1)
# count - сколько строк удалять
# пример: $mytable[^remove[$mytable;1;10]] - удалит первые 10 строк
@_remove[t;from;count]
^if(def $t){
$result[^t.select((^t.line[]<$from) || (^t.line[]>=$from+$count))]
}
Код класса в реализации с хэшем
Данная версия работает быстрее при использовании файлов .htpasswd с большим количеством записей (пользователей). Правда при количестве пользователей менее десятка, она вряд ли быстрее первой и кушает немного больше пямяти, но на спичках экономить не следует, и этим можно пренебречь.
##############################
# $Id: htpasswd.p,v 1.21 2004/01/10 11:08:12 egr Exp $
# Класс для работы с .htpasswd
# Идея Pavel Titov (pavel@titov.pp.ru)
# Я сильно усовершенствовал реализацию - вместо таблиц используется хэш
@CLASS
htpasswd
##############################
# Конструктор
# Параметр:
# sFileName - полный путь к файлу .htpasswd (вида /path/.htpasswd)
# Поля класса:
# _sFileName - полный путь к файлу .htpasswd (вида /path/.htpasswd)
# _hPasswd - хэш пользователь/пароль
@init[sFileName][lHtpass]
$_sFileName[$sFileName]
^try{
$lHtpass[^file::load[text;$sFileName]]
$lHtpass[^lHtpass.text.match[\n][g]{^#0A}]
$lHtpass[^table::create{login password^#0A^lHtpass.match[:][g]{ }}]
}{
^if($exception.type eq file.missing
|| $exception.type eq parser.runtime
){
$exception.handled(1)
$lHtpass[^table::create{login password}]
}
}
$_hPasswd[^lHtpass.hash[login]]
##############################
# Добавление пользователя / изменение пароля
@setpw[sLogin;sPassword][lCrypted]
$lCrypted[^math:crypt[$sPassword;^$apr1^$]]
^setpwCrypted[$sLogin;$lCrypted]
##############################
# Добавление пользователя / изменение пароля
# с передачей не самого пароля а уже его хэша
# Работает просто - если пользователь есть, он сначала удаляется,
# а потом записывается новый, с таким же именем, но другим паролем
# Если пользователя нет, понятно что происходит в этом случае :)
@setpwCrypted[sLogin;sPassword][lNewItem]
^if(def $_hPasswd.[$sLogin]){
^_hPasswd.delete[$sLogin]
}
$lNewItem[
$.[$sLogin][
$.login[$sLogin]
$.password[$sPassword]
]
]
$_hPasswd[^_hPasswd.union[$lNewItem]]
^_save[]
##############################
# Удаление пользователя
@delete[sLogin]
^if(def $_hPasswd.[$sLogin]){
^_hPasswd.delete[$sLogin]
}
^_save[]
##############################
# Сохранение .htpasswd
@_save[]
$result[^_hPasswd.foreach[login;password]{${login}:${password.password}}[^#0A]]
^result.save[$_sFileName]
$result[]
Пример вызоваВнимание! - класс не будет работать если вызывать его методы статически. А вообще, использовать его очень просто - сначала создаётся объект класса, а затем вызывается нужный метод:
$oPass[^htpasswd::init[/path/to/.htpasswd]]
# Добавление (изменение пароля) пользователя с передачей
# незашифрованного пароля
^oPass.setpw[$sUserName;$sPasswd]
# Добавление (изменение пароля) пользователя с передачей
# хэша пользовательского пароля, полученного с помощью math:crypt
^oPass.setpwCrypted[$sUserName;$sPasswd]
# Удаление пользователя
^oPass.delete[$sUserName]
Загрузить пример (1-я реализация): htpasswd1.zip (Дата: 10.1.2004 Размер: 1.36 Kб)
Загрузить пример (2-я реализация): htpasswd2.zip (Дата: 10.1.2004 Размер: 1.24 Kб)
Примечание: Загружаемые файлы находятся в кодировке UTF-8.
]]>
2003-11-08T14:40:00+03:00
24
Самодокументирование парсерного кода
Общепризнанно, - что самодокументированная программа, это очень полезная и нужная вещь. Идея эта совсем не нова – ещё в 1975 году Ф. Брукс, в своём, Мифическом человеко-месяце, высказывал эту идею. Для классических языков программирования инструменты для составления документации по программам прямо из исходных текстов существуют уже давно, тот же Doxygen, однако для такого молодого и не очень, надо сказать, популярного языка Парсер, таких вещей пока нет.
Александр Петросян (это автор 3-го парсера) высказал эту идею уже давно, однако ни у кого руки до сих пор не доходили до создания простецкого кода, извлекающего информацию из исходных текстов и создающих простенькую документацию по коду. На самом деле и у меня бы не дошли, однако я стал запутываться в собственном коде без нормальной документации, и значит пришла пора приводить это дело в порядок.
Для тех кто не в танке – мне не досуг разбираться с Doxygen и я лучше сделаю ещё один велосипед, тем более, что для меня пока это проще.
Итак, Вашему вниманию предлагается набор методов:
Извлекающих информацию из файлов с парсерным кодом
Выводящих информацию (если есть) о имени класса, описание класса информацию о родительском классе.
Выводящих информацию о методах определённых в этих файлах.
Выводящих информацию о параметрах (если они есть), передаваемых этим методам.
Выводящих информацию о локальных переменных методов.
Выводящих описания методов
Код работает очень простым способом – он сканирует все каталоги, определённые в переменной $CLASS_PATH и получает информацию обо всех .html и .p файлах находящихся в этих каталогах. Вывод осуществляется в 2 колонки – в левой, в зависимости от установленной куки view:
В виде, - сначала список каталогов из $CLASS_PATH а затем, для выбранного каталога, список его файлов.
В виде иерархии классов.
В правой колонке выводится информация о выбранном файле с классом.
Мне этот способ (сканирование каталогов из $CLASS_PATH) подходит – т.к. у меня весь код сосредоточен в моих классах и операторах (находящихся в каталогах определённых $CLASS_PATH) а класс MAIN на сайте составляют только конфигурационный auto.p, auto.p находящийся в корне веб-пространства и index.html находящийся там же. В случае если это не так (код раскидан по куче html/p файлам в веб-пространстве) этот способ работать не будет, или будет, если все эти каталоги добавить в $CLASS_PATH.
Код методов:
##############################
# Вывод либо списка файлов/каталогов
# либо иерархии классов
# Вызывать где-нибудь внутри <body> ... </body>
@content[]
^if(def $form:view){
$cookie:view[
$.value[^form:view.int(0)]
$.expires(365)
]
}
<table class="contentCells" style="width: 100%">
<tr>
<td style="width: 25%; vertical-align: top;">
^switchView[]
^if(!^cookie:view.int(0)){
^files[]
}{
^tree[]
}
</td>
<td style="width: 75%; vertical-align: top;">
^rem{/**
имя файла может быть пустым поэтому может возникать ошибка
чтения с диска
*/}
^try{
^classInfo[$form:path/$form:fileName]
}{
^if($exception.type eq file.access || $exception.type eq file.missing){
$exception.handled(1)
}
}
</td>
</tr>
</table>
##############################
# Документация по классу
# ВНИМАНИЕ!: В реальном коде, все переменные, используемые в методе
# кроме разумеется параметра метода
# сделать локальными. Я это не сделал по причине читабельности
# при публикации текста в вебе
@classInfo[strClass]
$polyPageClass[^file::load[text;$strClass]]
$polyPageClass[$polyPageClass.text]
$tblPageClass[^classNameAndDesc[$polyPageClass]]
# Имя класса
^if(def $tblPageClass.2){
$strPageClassName[$tblPageClass.2]
}{
$strPageClassName[MAIN]
}
# Описание класса
$strPageClassDescr[$tblPageClass.1]
# Родительский класс (если есть)
$strBaseClass[^baseClass[$polyPageClass]]
# замена шарпов на <br/>
$strPageClassDescr[^strPageClassDescr.match[#][g]{<br/>}]
$strPageClassDescr[^strPageClassDescr.match[<br/>][]{}]
# Вывод
<h1>Класс: $strPageClassName</h1>
^if(def $strBaseClass){
$hashClasses[^classesHash[]]
<h2>Родительский класс:</h2>
<a href="./?path=$hashClasses.[$strBaseClass].path&fileName=$hashClasses.[$strBaseClass].file">
$strBaseClass
</a>
}
^if(def $strPageClassDescr){
<dl>
<dt><h2>Описание:</h2></dt>
<dd>$strPageClassDescr</dd>
</dl>
}
<h2><a name="methods" id="0">Методы (коротко):</a></h2>
$tblClassMethod[^methods[$polyPageClass]]
# Список со ссылками на подробное описание метода
<ol>
$nJx(1)
^tblClassMethod.menu{
<li><a href="$request:uri#$nJx">$tblClassMethod.2</a></li>
^nJx.inc(1)
}
</ol>
<h2>Методы (подробно):</h2>
<dl>
$nJx(1)
^tblClassMethod.menu{
<dt>
<h3>
${nJx}. <a name="$tblClassMethod.2" id="$nJx">$tblClassMethod.2</a>
[<a href="$request:uri#0">К началу</a>]
</h3>
</dt>
^nJx.inc(1)
<dd>
<dl>
$strMethodDescr[$tblClassMethod.1]
$strMethodDescr[^strMethodDescr.match[#][g]{<br/>}]
$strMethodDescr[^strMethodDescr.match[<br/>][]{}]
^rem{/** Вывод описания метода (если есть) */}
^if(^strMethodDescr.length[] > 2){
<dt><h4>Описание:</h4></dt>
<dd>$strMethodDescr</dd>
}
^rem{/** Вывод списка передаваемых параметров (если есть) */}
^if(def $tblClassMethod.3){
$tblParams[^tblClassMethod.3.split[^;]]
<dt><h4>Параметры:</h4></dt>
<dd>
$nIx(1)
^tblParams.menu{
${nIx}. $tblParams.piece<br/>
^nIx.inc(1)
}
</dd>
}
^rem{/** Вывод списка локальных переменных (если есть) */}
^if(def $tblClassMethod.4){
$tblLocalVars[^tblClassMethod.4.split[^;]]
<dt><h4>Локальные переменные:</h4></dt>
<dd>
$hashLocalVars[^tblLocalVars.hash[piece]]
^tblLocalVars.menu{
- $tblLocalVars.piece<br/>
}
</dd>
}
</dl>
</dd>
}
</dl>
##############################
# Файлы и каталоги с классами
@files[][tblFilesList]
<h1>Каталоги</h1>
<ul>
<li>
^if(def $form:path){
<a href="?path=">Корень сайта</a>
}{
<strong>Корень сайта</strong>
}
</li>
^CLASS_PATH.menu{
<li>
^if($CLASS_PATH.path ne $form:path){
<a href="?path=$CLASS_PATH.path">
$CLASS_PATH.path
</a>
}{
<strong>$CLASS_PATH.path</strong>
}
</li>
}
</ul>
<h1>Файлы</h1>
$tblFilesList[^file:list[$form:path/;\.(p|html)^$]]
<ul>
^tblFilesList.menu{
<li>
^if($tblFilesList.name ne $form:fileName){
<a href="?path=$form:path&fileName=$tblFilesList.name">
$tblFilesList.name
</a>
}{
<strong>$tblFilesList.name</strong>
}
</li>
}
</ul>
##############################
# таблица всех пользовательских классов сайта
@classesTable[][tblFilesList;polyPageClass;tblClassInfo]
$result[^table::create{class file path base
MAIN index.html / }]
^CLASS_PATH.menu{
$tblFilesList[^file:list[$CLASS_PATH.path/;\.(p|html)^$]]
^tblFilesList.menu{
$polyPageClass[^file::load[text;$CLASS_PATH.path/$tblFilesList.name]]
$polyPageClass[$polyPageClass.text]
^rem{/** Определение имени класса */}
$tblClassInfo[^classNameAndDesc[$polyPageClass]]
^if(def $tblClassInfo.2){
^result.append{$tblClassInfo.2 $tblFilesList.name $CLASS_PATH.path ^baseClass[$polyPageClass]}
}
}
}
##############################
# Хэш всех пользовательских классов сайта
@classesHash[]
$result[^classesTable[]]
$result[^result.hash[class]]
##############################
# Получает текст файла класса
# Возвращает таблицу с информацией о всех методах
# определённых в файле
#
# Раскладка по столбцам
# 1 - Описание метода
# 2 - Имя метода
# 3 - Строка передаваемых параметров (если есть) разделённых ;
# 4 - Строка локальных переменных (если есть) разделённых ;
@methods[strFileText]
$result[^strFileText.match[
(?:
\#{30} # метка начала описания метода (30 шарпов)
([^^@]*?)? # описание метода
)?
(?:\n|^^) # перед @ обязательно или перевод строки или начало файла
# ставить эту конструкцию именно тут - ВАЖНО!
@([^^^$@^;()#]+?) # имя метода
\[
(.*?) # передаваемые параметры
\]
(?:
\[
(.*?) # локальные переменные
\]
)*
\s
][gx]]
##############################
# Определение имени и описания класса (если есть)
# Получает текст файла класса
# Возвращает таблицу со столбцами:
# 1 - описание класса
# 2 - имя класса
@classNameAndDesc[strFileText]
$result[^strFileText.match[
(?:
\#{30}
(.*?) # описание класса
)?
(?:\n|^^)
@CLASS\n
(.*?) # имя класса
\n
][x]]
##############################
# Определение имени родительского класса (если есть)
# Получает текст файла класса
# Возвращает строку с именем базового класса
@baseClass[strFileText]
$result[^strFileText.match[
\n
@BASE
\n
(.+?) # имя базового класса
\n
][x]]
$result[$result.1]
##############################
# формирование элемента дерева классов
@prnTreeItem[tblItem;strChilds]
$result[
<li>
^if($tblItem.file ne $form:fileName){
<a href="?path=$tblItem.path&fileName=$tblItem.file" style="font-size: 90%">
$tblItem.class
</a>
}{
<strong>$tblItem.class</strong>
}
</li>
<ul>$strChilds</ul>
]
##############################
# Хэш всех пользовательских классов сайта
# с ключом по род. классу
# Параметры:
# $tblItems - таблица с элементами дерева
# $strParent - столбец с идентификатором родителя
@createHashTree[tblItems;strParent][tblEmpty]
$tblEmpty[^table::create[$tblItems][$.limit(0)]]
$result[^hash::create[]]
^tblItems.menu{
^if(!$result.[$tblItems.[$strParent]]){
$result.[$tblItems.[$strParent]][^table::create[$tblEmpty]]
}
^result.[$tblItems.[$strParent]].join[$tblItems][$.limit(1)$.offset[cur]]
}
##############################
# Файлы и каталоги с классами
@tree[][tblClasses;hashTree]
$tblClasses[^classesTable[]]
^tblClasses.sort{class}
$hashTree[^createHashTree[$tblClasses;base]]
$result[
<h1>Иерархия классов</h1>
<ul>^prnTree[$hashTree;]</ul>
]
##############################
# формирование дерева элементов
# метод рекурсивно вызывает сам себя через вызов метода
# prnTreeItem(формирование элемента дерева)
# в $tblBrotherItems формируется список элементов одного уровня(с общим предком)
@prnTree[hashTree;strBase][tblBrotherItems]
^if($hashTree.[$strBase]){
$tblBrotherItems[$hashTree.[$strBase]]
^tblBrotherItems.menu{
^prnTreeItem[$tblBrotherItems.fields;^if($hashTree.[$tblBrotherItems.class]){^prnTree[$hashTree;$tblBrotherItems.class]}]
}
}
##############################
# Форма переключения вида
@switchView[]
<h1>Показывать</h1>
<form action="" method="post">
<input type="radio" name="view" value="0" checked> Список классов<br/>
<input type="radio" name="view" value="1" ^if(^cookie:view.int(0)){checked}> Иерархия классов<p/>
<input type="submit" value="Изменить">
</form>
Поместите этот код в понравившийся вам html файл и вызывайте метод ^content[] в примерно таком контексте:
@main[]
<html>
<head>
<style type="text/css"><!-- @import url(/CSS/standart.css); --></style>
</head>
<body>
^content[]
</body>
</html>
Где standart.css стилевая таблица с требуемым оформлением используемых в документировании тегов.
Примечание: Исторически так сложилось, что у меня метка начала комментариев метода/класса - это 30 (sic!) шарпов (#) если вам это не подходит, поправьте соответствующее регулярное выражение в нужных методах (сами догадаетесь в каких).
В загружаемые исходники этого сайта включён каталог /docs/ с работающим кодом этого примера и, следовательно, исходники теперь имеют документацию.
]]>
2003-09-14T14:14:00+03:00
23
Работаем с RSS
В последнее время, популярность формата RSS - Really Simple Syndication, начала подниматься. Подробнее о том, что это такое читайте у Дмитрия Смирнова, - ссылка есть в конце этой страницы. Пожалуй, внесу и я свою лепту в это дело, - приведу небольшой пример кода работы на парсере с внешним XML в формате RSS.
Этот пример будет основан на предыдущем примере работы с внешним XML
. Разумеется структура RSS
XML несколько другая, чем в этом примере. Я буду работать с версией RSS 2.0 – это конечно не особенно принципиально, потому что код легко адаптируется и для других версий (0.91, 1.0 и пр.). Результирующий HTML будет списком определений dl (definition list). Итак, к делу:
######
@rss[local_file;remote_file][src;xml;list;now;date;title;link;description]
# проверяем локальный файл, - если его дата изменения более чем на
# день старее текущего времени, грузим его с внешнего сервера
$src[^file::stat[$local_file]]
$now[^date::now[]]
^if($src.mdate < $now-1){
# пытаемся загрузить и проверить внешний XML файл и сохранить его
# если не удается загрузка и/или проверка, обрабатываем исключение и ничего не делаем
^try{
# Загрузка внешнего файла
$src[^file::load[text;$remote_file]]
# проверка, - пытаемся создать объект класса xdoc из внешнего XML
$xml[^xdoc::create{^untaint{$src.text}}]
^src.save[text;$local_file]
}{
$exception.handled(1)
}
}
# всегда создаем объект класса xdoc из XML файла на локальном диске
# разумеется он должен всегда существовать. При удачной загрузке с
# внешнего сервера, он ещё и постоянно обновляется
$xml[^xdoc::load[$local_file]]
# хэш элементов item из файла
$list[^xml.select[/rss/channel/item]]
# Название канала
<h1>^xml.selectString[string(/rss/channel/title)]</h1>
# Описание канала
<p>^xml.selectString[string(/rss/channel/description)]</p>
# Список материалов
<dl>
^for[i](1;$list){
$date{^xml.selectString[string(/rss/channel/item[position() = $i]/pubDate)]}
$title{^xml.selectString[string(/rss/channel/item[position() = $i]/title)]}
$link{^xml.selectString[string(/rss/channel/item[position() = $i]/link)]}
$description{^xml.selectString[string(/rss/channel/item[position() = $i]/description)]}
<dt><a href="$link">$title</a> ($date)</dt>
<dd>$description</dd>
}
</dl>
Примечание:
Проверка создания xdoc объекта из внешнего XML имеет цель просто проверить правильность оформления внешнего XML и она конечно не может выявить несоответствия этого XML словарю RSS, поскольку в текущей версии парсера, при работе с XML, отсутствует проверка валидности документов либо с помощью DTD либо с помощью XML Schema.
Понятно что метод вызывается с двумя параметрами:
local_file – локальный XML с путём к нему вида /external_xml/rss2.xml
remote_file – URL удалённого XML
RSS
^rss[rss2.xml;http://www.server.ru/rss2.xml]
Полезно также, почитать предыдущий пример про работу с внешним XML
. А здесь вроде всё, - тренируемся :)
Загрузить пример: rss.zip (Дата: 2.8.2003 Размер: 4.9 Kб)
]]>
2003-02-21T08:36:00+03:00
22
Топологическая сортировка
При работе с деревьями (с древовидной структурой сайта) у меня встал вопрос о том, как управлять положением узлов дерева относительно других узлов. Т.е. как по желанию произвольно менять его, не применяя при этом специальных мер по записи элементов исходной таблицы (из которой формируется дерево) в требуемом порядке – если эта таблица формируется человеком вручную, или соглашений по значениям некоторых полей (ключей), с помощью которых можно сделать требуемую сортировку.
Примером такого соглашения может являться, например то, что одноуровневые элементы дерева (братья) должны выводиться в порядке возрастания/убывания их числовых идентификаторов. Например, в примере формирования списка всех URI сайта, если принять за правило выводить одноуровневые элементы в порядке возрастания их числовых идентификаторов, - раздел hardware будет идти перед разделом software, или компьютеры будут идти перед принтерами. В этом случае, если необходимо изменить порядок следования, необходимо изменить числовые идентификаторы соответствующих разделов.
При большом количестве элементов это может оказаться неудобным, да и при маленьком количестве не очень хочется утруждать себя «правильной» схемой нумерации. Одним из способов решения этой проблемы, может являться сохранение дополнительной информации об элементе, предшествующем данному, или следующим за ним.
Приведенный здесь пример кода, является весьма упрощенным примером топологической сортировки, работающем для узкого класса задач. "Топологическая сортировка – это установление частичного порядка среди объектов, упорядоченных в линейном порядке, т.е. в таком расположении объектов линейной последовательности а1 а2 … аn, чтобы для любых aj предшествующих ak выполнялось условие j < k." Д. Кнут
Хотя проблема возникла при выводе древовидной структуры, а такая сортировка как показано выше осуществляется для линейной последовательности, однако если дерево формируется из таблицы, то данный алгоритм, можно применить и к этой задаче. Подробнее о формировании древовидных структур из линейных, смотрите пример про формирование деревьев средствами парсера из соответствующего раздела.
Теперь об упрощениях и соглашениях принятых при решении задачи:
Каждый элемент может предшествовать только одному элементу, хотя в общем случае при рассмотрении топологической сортировки один и тот же элемент может предшествовать сразу нескольким другим элементам.
Работа ведется с числовыми идентификаторами (с символьными это делается аналогично).
Если элементу никто не предшествует, - идентификатор предшествующего ему элемента принимается равным нулю.
В качестве примера таблицы подвергаемой сортировке, рассмотрим исходную таблицу, применяемую для формирования URI сайта, из примера про построение списка всех URI сайта:
id
parent_id
dir
title
1
0
hardware
Железо
11
1
computers
Компьютеры
12
1
printers
Принтеры
121
12
laser
Лазерные
122
12
ink
Струйные
2
0
software
Программное обеспечение
21
2
os
Операционные системы
22
2
editors
Текстовые редакторы
3
0
news
Новости
Дополним её столбцом prev_id содержащем id строки, предшествующей данной (если ничего не предшествует, то это значение равно нулю) и перемешаем строки, показывая что нам не важен порядок следования строк в исходной таблице. Таблица примет такой вид:
id
parent_id
prev_id
dir
title
1
0
0
hardware
Железо
2
0
122
software
Программное обеспечение
3
0
22
news
Новости
11
1
1
computers
Компьютеры
12
1
11
printers
Принтеры
21
2
2
os
Операционные системы
22
2
21
editors
Текстовые редакторы
121
12
12
laser
Лазерные
122
12
121
ink
Струйные
Сам код метода сортировки имеет вид:
#######
# Топологическая сортировка таблицы
# методу передается исходная таблица - table, имя ключевого столбца - key
# и имя столбца с ключами предшествующих элементов - prev_key
@sort_table[table;key;prev_key][table_hash;table_columns]
# создание хэша из таблицы для ускорения поиска
$table_hash[^table.hash[$prev_key]]
# таблица названий столбцов
$table_columns[^table.columns[]]
# названия столбцов результирующей таблицы
$result[^table::create{^table_columns.menu{$table_columns.column}[ ]}]
# добавление первого элемента
^result.append{^table_columns.menu{$table_hash.0.[$table_columns.column]}[ ]}
$prev_key($table_hash.0.[$key])
^while($prev_key && $table_hash.[$prev_key].[$key]){
^result.append{^table_columns.menu{$table_hash.[$prev_key].[$table_columns.column]}[ ]}
$prev_key($table_hash.[$prev_key].[$key])
}
Ещё немного комментариев, к тем, что уже есть в коде.
Значения столбца prev_id должны быть уникальны, и хотя бы одно из них должно быть равно нулю (первый элемент). Если это не так, метод работать не будет.
Если назвать вышеприведенную исходную таблицу $items, - вызов этого метода, возвращающий ту же самую таблицу, отсортированную по информации из поля prev_id, будет выглядеть так:
$items[^sort_table[$items;id;prev_id]]
Отсортированная таблица будет выглядеть следующим образом:
id
parent_id
prev_id
dir
title
1
0
0
hardware
Железо
11
1
1
computers
Компьютеры
12
1
11
printers
Принтеры
121
12
12
laser
Лазерные
122
12
121
ink
Струйные
2
0
122
software
Программное обеспечение
21
2
2
os
Операционные системы
22
2
21
editors
Текстовые редакторы
3
0
22
news
Новости
Код сортировки достаточно прост и тривиален, а вот код для изменения порядка следования элементов, удаления или вставки нового элемента, будет посложнее, однако проще чем это может показаться с первого взгляда, но это уже совсем другая история.
Для больших структур (сотни элементов) возможно, могут появиться проблемы с производительностью, и придется искать другие способы упорядочивания таких структур.
При частых, одновременных изменениях в структуре дерева (если это используется при построении деревьев), т.е. когда элементы дерева могут добавляться, перемещаться или удаляться одновременно несколькими пользователями в одно и то же время, - будут проблемы, потому что каждое такое изменение может требовать до 6-ти запросов, которые должны производиться при установленной блокировке на таблицу.
Как выяснилось позднее, - в коде есть ошибка, которая проявляется в том, что если в исходной таблице есть NULL(пустые) значения, сортированная таблица получается не совсем такой как хотелось бы. Короче говоря в строке где есть NULL значения столбцов меньше чем в той, в которой их нет. В связи с этим родился ещё один вариант решения задачи:
#######
# Топологическая сортировка таблицы
# методу передается исходная таблица - table, имя ключевого столбца - key
# и имя столбца с ключами предшествующих элементов - prev
@sort_table[table;key;prev][table_columns;insert_current_row]
# таблица названий столбцов
$table_columns[^table.columns[]]
# названия столбцов результирующей таблицы
$result[^table::create{^table_columns.menu{$table_columns.column}[ ]}]
# код вставки текущей строки
$insert_current_row{
^result.join[$table][$.limit(1) $.offset[cur]]
}
# добавление первого элемента
# т.е. у которого prev = 0
^table.locate[$prev;0]
$insert_current_row
$prev_key($table.$key)
^while($prev_key && ^table.locate[$prev;$prev_key]){
$insert_current_row
$prev_key($table.$key)
}
Однако этот способ далеко не оптимален, потому что метод locate работает с помощью последовательного перебора, и при больших таблицах это будет сильно торомозить, однако, поскольку производится копирование строк, а не значений, NULL значения допускаются.
И наконец, последний вариант кода - без поиска последовательным перебором (locate), а с двоичным поиском нужной строки таблицы:
#######
# Топологическая сортировка таблицы
# методу передается исходная таблица - table, имя ключевого столбца - key
# и имя столбца с ключами предшествующих элементов - prev
@sort_table[table;key;prev][table_columns;insert_current_row;i;l;u;break;binary_search;prev_key]
# таблица названий столбцов
$table_columns[^table.columns[]]
# названия столбцов результирующей таблицы
$result[^table::create{^table_columns.menu{$table_columns.column}[ ]}]
# код вставки текущей строки
$insert_current_row{
^result.join[$table][$.limit(1) $.offset[cur]]
}
# добавление первого элемента
# т.е. у которого prev = 0
# или иными словами - задание начальных значений
^table.sort($table.$prev)
$insert_current_row
$prev_key($table.$key)
# Бинарный поиск
$binary_search{
# начальные условия
$l(1)
$u(^table.count[])
$break(1)
^while($u >= $l && $break){
$i(^math:floor(($l + $u)/2))
^table.offset[set]($i)
^if($prev_key < $table.$prev){
$u($i - 1)
}{
^if($prev_key == $table.$prev){
$break(0)
}{
$l($i + 1)
}
}
}
}
^for[k](1;^table.count[] - 1){
$binary_search
$insert_current_row
$prev_key($table.$key)
}
Немного комментариев:
Перед двоичным поиском исходная таблица должна быть отсортирована по значениям столбца с ключами предшествующих элементов и здесь, даже в случае самого неэффективного алгоритма реализации сортировки таблицы в парсере (sort), ускорение работы будет значительное, ибо сортировка производится только один раз, в отличие от locate в предыдущем варианте кода, который выполнялся для каждой строки таблицы.
Значения столбца с ключами предшествующих элементов обязательно должны быть числами.
В принципе всё. Если я найду более оптимальный вариант кода для данной задачи, я в очередной раз изменю эту статью.
]]>
2003-02-16T15:21:00+03:00
21
Установка 3-го парсера на хостинге 350mb.ru
В данной инструкции будет описана установка парсера для основного домена, - для поддоменов она делается аналогично.
Сначала загрузите требуемую версию парсера (CGI скрипт с поддержкой XML или без неё) для Linux Redhat 7.х из скачать сайта parser.ru, или из раздела скачать, на этом сайте.
Оттуда же загрузите, Конфигурационный файл и файлы описания кодировок, а также SQL драйвер для MySQL
Разархивируйте загруженные архивы с CGI скриптом, конфигурационным файлом и файлами описания кодировок, а также драйверами для MySQL
Загружаем по ftp (обязательно! в binary режиме) CGI скрипт parser3.cgi в каталог /public_html/cgi-bin и устанавливаем ему права 700 или -rwx --- --- (это самые жесткие права), т.е. разрешение на чтение, запись и выполнение только для владельца файла. На этом моменте следует остановиться подробнее - очень часто парсер не работает именно из-за того, что он был загружен на сервер не в binary, а в ASCII режиме. Файлы с расширением .cgi многие ftp-клиенты по-умолчанию загружают именно в ASCII режиме, - поэтому перед загрузкой внимательно проверьте настройки своего ftp-клиента.
В /public_html/cgi-bin/lib (если такого каталога нет, создаем его) загружаем драйвер MySQL - libparser3mysql.so
В /public_html/cgi-bin/charsets (если такого каталога нет, создаем его) загружаем файлы таблиц перекодировки: koi8-r.cfg, windows-1250.cfg, windows-1251.cfg, windows-1257.cfg
В /public_html/cgi-bin (т.е. туда, где находится CGI скрипт парсера) загружаем конфигурационный файл - auto.p
В корневом .htaccess сайта назначаем parser3 обработчиком html файлов.
AddHandler parsed-html html
Action parsed-html /cgi-bin/parser3.cgi
<files ~ "\.p$">
Order allow,deny
Deny from all
</files>
Для проверки работоспособности парсера скачайте тестовую страницу http://parser.ru/off-line/download/utils/test.zip и разархивируйте её(_test.html) в /public_html и проверьте работоспособность установленного парсера, зайдя на эту страницу: http://www.your_domain.ru/_test.html.
Послесловие:
Поскольку на данном сервере каталог cgi-bin (т.е. там, где находится парсер) находится в пределах веб-пространства (каталог /public_html), - размещение конфигурационного файла auto.p в этом каталоге не самое безопасное решение, однако самое простое. При размещении его в другом месте (вне веб-пространства) необходимо установить переменную окружения CGI - CGI_PARSER_CONFIG в .htaccess указывающую реальный дисковый путь к этому файлу. Шаблон:
SetEnv CGI_PARSER_CONFIG /home/логин/path_to/auto.p
Например можно создать каталог /parser3 и поместить туда конфигурационный auto.p и тогда этот шаблон будет выглядеть следующим образом:
SetEnv CGI_PARSER_CONFIG /home/логин/parser3/auto.p
А файл .htaccess будет таким:
SetEnv CGI_PARSER_CONFIG /home/логин/parser3/auto.p
AddHandler parsed-html html
Action parsed-html /cgi-bin/parser3.cgi
<files ~ "\.p$">
Order allow,deny
Deny from all
</files>
И далее нужно подправить первую строчку конфигурационного auto.p (определение метода @conf). Вместо:
@conf[filespec]
Нужно написать:
@conf[/home/логин/parser3/auto.p]
Т.е. определять этот метод с параметром показывающим размещение конфигурационного auto.p.
Однако если вы не очень хорошо разбираетесь в тонкостях установки и настройки парсера, - не делайте этого.
Важное замечание:
Позже выяснилось (спасибо Евгению Савину), что не всё так хорошо на этом хостинге в плане установки переменных окружения CGI, - на хостинге не работает модуль mod_env, с помощью которого и устанавливаются переменная окружения CGI_PARSER_CONFIG, необходимая для выноса конфигурационного файла из веб-пространства. Следовательно, в настоящий момент, вы не можете вынести конфигурационный файл из веб-пространства, что не есть хорошо. Остаётся надеятся, что в будущем, эта проблема будет устранена, иначе при работе с парсером на этом хостинге, есть весьма серъёзная дырка.
]]>
2003-02-12T17:07:00+03:00
20
Работаем с внешним XML
В связи с появлением в парсере возможности работать с внешними файлами (не на локальном сервере), появилась возможность получать и выводить информацию из внешних источников у себя на сайте.
В этом примере я рассмотрю использование внешнего XML файла с анонсами материалов, для опубликования ссылок на эти материалы у себя на сайте. Сделаю это на примере сайта php в деталях. Содержимое и структуру XML этого файла смотрите по адресу, который найдете в нижеприведённом коде.
Небольшое предостережение - если XML файл расположен на сервере с русским Apache, нужно обязательно отключить перекодировку с его помощью, иначе может возникнуть ситуация с несовпадением реальной и кодировки в XML декларации. Ну и теперь к делу, - код выглядит следующим образом:
#######
# формирование анонсов последних материалов
# с сайта php в деталях
# Блок <ul>...</ul> написал Константин Томашевич (konst@design.ru)
@phpdetails[][src;local_file;xml;list;now]
# определяем локальный файл где находится XML
$local_file[/phpdetails/last10.xml]
# проверяем локальный файл, - если его дата изменения более чем на
# день старее текущего времени, грузим его с внешнего сервера
$src[^file::stat[$local_file]]
$now[^date::now[]]
^if($src.mdate < $now-1){
# пытаемся загрузить внешний XML файл и сохранить его
# если не удается загрузка, обрабатываем исключение и ничего не делаем
^try{
$src[^file::load[text;http://detail.phpclub.net/last10.xml]]
^src.save[text;$local_file]
}{
$exception.handled(1)
}
}
# всегда создаем объект класса xdoc из XML файла на локальном диске
# разумеется он должен всегда существовать. При удачной загрузке с
# внешнего сервера, он ещё и постоянно обновляется
$xml[^xdoc::load[$local_file]]
# хэш элементов(анонсов) из файла, нужен для определения количества анонсов
$list[^xml.select[/last/item]]
<ul>
#!*!# нумерациЯ с 0 (исправлен range в if)
^for[i](0;$list-1){
#!*!# нет смысла делать еще раз selectXXX, ибо мы уже в цикле типа xsl:for-each
#!*!# более того, если не проверЯешь наличие аттрибутов, можно сократить до
#!*!# одной строки - $list == hash of xnode (/last/item)
<li>
^list.[$i].getAttribute[date]
<a href="http://detail.phpclub.net/^list.[$i].getAttribute[dir]^list.[$i].getAttribute[file].htm">
^list.[$i].getAttribute[title]
</a>
</li>
}
</ul>
Немного комментариев.
Работа по вытягиванию информации из XML всегда ведется с локальным файлом.
Раз в сутки проверяется дата изменения локального файла и если она более чем на сутки старее текущий производится загрузка внешнего файла.
Здесь сделано предположение, что, внешний файл меняется раз в сутки, если это не так и файл меняется гораздо реже, стоит этот период увеличить.
Пусть вас не удивляют странные комментарии сделанные между тегами ul - первоначальный вариант кода был такой:
<ul>
^for[i](1;$list){
$date{^xml.selectString[string(/last/item[position() = $i]/@date)]}
$title{^xml.selectString[string(/last/item[position() = $i]/@title)]}
$path{^xml.selectString[string(/last/item[position() = $i]/@dir)]}
$file{^xml.selectString[string(/last/item[position() = $i]/@file)]}
<li>$date <a href="http://detail.phpclub.net/${path}${file}.htm">$title</a></li>
}
</ul>
Да, - совсем не обязательно проверять дату изменения, можно проверять и дату создания, но это не принципиально.
И в заключении - большое спасибо Константину Томашевичу, за ценные советы по работе с методами класса xnode и написанию части кода примера.
]]>
2003-01-21T08:13:00+03:00
19
Оптимизируем HTML
Если вы просто помешаны на оптимизации и хотите убрать всё лишнее из HTML - ненужные переводы строк, кавычки в некоторых атрибутах тегов, вашему вниманию предлагается следующий метод, делающий это за вас.
Такую оптимизацию очень удобно делать в методе @postrocess класса MAIN.
#######
@postprocess[buffer][attr_string;code;code_num;replace]
# вытаскиваем содержимое тегов pre, script в таблицу $code
$code[^buffer.match[<(pre|script)(.*?)>(.+?)</\1>][ig]]
# меняем теги <pre>...</pre> на <pre>1</pre>, <pre>2</pre> ... и <script> тоже
$code_num(1)
# код замены
$replace{<^match.1.lower[]$match.2>$code_num</^match.1.lower[]>^code_num.inc(1)}
$buffer[
^buffer.match[<(pre|script)(.*?)>(.+?)</\1>][ig]{$replace}
]
$buffer[^trim[$buffer]]
# attr="*" меняем на attr=*
# строка атрибутов у которых надо убрать кавычки
$attr_string[class|align|width|bgcolor|border|cellpadding|cellspacing]
$buffer[^buffer.match[($attr_string)="(.+?)"][ig]{$match.1=$match.2}]
# убираем лишние, но не все!, переводы строк и табуляторы
$buffer[^buffer.match[(\s{2,})][g]{^match.1.left(1)}]
# вывод результата,
# возвращаем теги <pre>, <script> в первоначальное состояние
# код замены
$replace{<${match.1}$match.2>^taint[as-is][$code.3]</$match.1>^code.offset(1)}
$result[^buffer.match[<(pre|script)(.*?)>(\d+)</\1>][g]{$replace}]
#######
# Copyright Michael Petrushin (http://misha.design.ru/)
# обрезает начальные и конечные символы char.
# если char не задан - то обрезает начальные и
# конечные пробельные символы
@trim[str;char][chr;tbl]
^if(def $str){
$chr[^if(def $char){$char}{\s}]
$str[^str.match[^^$chr*][]{}]
# $result[^str.match[$chr+^$][]{}]
# эта замена существенно быстрее на больших текстах
$tbl[^str.match[^^(.*[^^$chr])$chr*^$]]
$result[$tbl.1]
}{
$result[]
}
Определите этот метод в нужной странице или если вы хотите обрабатывать все страницы каталога и подкаталогов ниже или всего сайта то в auto.p каталога или в корне сайта соответственно.
]]>
2003-01-07T14:30:00+03:00
18
Календарь с использованием XML - 2
Совершенству нет предела, поэтому представляю очередную модификацию календаря с использованием XML. Здесь я не буду расписывать подробно о всех аспектах его работы, - об этом вы можете прочитать в предыдущем примере, а расскажу лишь об отличиях.
Предыдущая версия календаря имела один серьёзный изъян, - в конструкторе определялся хэш $days_of_files хранящий информацию о днях месяца, за которые были какие либо публикации (новости например). Определение этого хэша привязано к конкретной структуре БД с которой работает сайт и при её изменении придется переделывать конструктор, также при использовании этого кода в другом проекте придется опять переделывать определение хэша, - это плохо. В связи с этим было решено вынести определение данного хэша из класса и передавать его в качестве параметра конструктору - это позволить использовать код без изменения в различных проектах.
Кроме того, я решил оказаться от использования языка по-умолчанию в коде класса и сделал базовый класс календаря абстрактным, т.е. от которого нельзя создавать экземпляры, а для конкретного языка создается класс, производный от базового, в котором определяется один метод, к котором определены специфичные для каждого языка параметры.
Теперь подробнее о параметрах конструктора. Первый из них это $date - это хэш, в котором передаются параметры дня, месяца и года календаря:
$date[
$.day(2)
$.month(10)
$.year(2002)
]
Следует отметить, что значение месяца и года, всегда должны быть осмысленными, ненулевыми значениями (например по-умолчанию принять их равными текущим значениям месяца и года), а значение дня может быть нулем (если не нужно показывать новости за какой-либо день).
Второй параметр - это вышеупомянутый хэш $days_of_files, имеющий следующий вид:
$days_of_files[
$.1[число > 0]
$.2[число > 0]
$.5[число > 0]
...
]
тут критичны лишь значения ключей хэша, которые должны быть числами, днями месяца, за которые были публикации.
Далее код базового класса:
#######
# абстрактный класс для формирования XML кода календаря (метод ^xml[])
# Описание атрибутов:
# in - показывает что этот день является сегодняшним днём
# f - наличие материалов в этот день
# hit - этот день выбран пользователем -> на него не ставить ссылку и выбирать
# данные только за этот день
@CLASS
calendar
#######
@init[date;days_of_files]
# получение текущей даты
$date_now[^date::now[]]
# Инициал. хэш показывающий какой период времени передан
# при создании экземпляра класса
$self.date[$date]
# декларация специфических переменных языка
# название месяца
$month[]
# хэш названий всех месяцев и дней недели
$calendar_locale[^hash::create[]]
# календарь на месяц
$calendar_month[^table::create{}]
# вызов метода для создания специфических для языка переменных
# в классе производном от этого и определить только один
# метод @i18n[]
^i18n[]
# определение переменных месяца и года для пред. месяца календаря
$calendar_month_prev[^date::create($date.year;$date.month)]
^calendar_month_prev.roll[month](-1)
$prev_m($calendar_month_prev.month)
$prev_y($calendar_month_prev.year)
# определение переменных месяца и года для след. месяца календаря
$calendar_month_next[^date::create($date.year;$date.month)]
^calendar_month_next.roll[month](+1)
$next_m($calendar_month_next.month)
$next_y($calendar_month_next.year)
# the hash of publications(files)
$self.days_of_files[$days_of_files]
#######
@xml[]
$result[
<month>
^month_year[]
^weekdays[]
^week[]
</month>
]
#######
# формирование тега для года и месяца
@month_year[][hit]
^if(
$date.day ||
(
$date_now.month == $date.month &&
$date_now.year == $date.year &&
$date.day
)
){
$hit[]
}{
$hit[hit="1"]
}
<month_year
month="$date.month"
month_title="$month"
next_month="$next_m"
prev_month="$prev_m"
year="$date.year"
next_year="$next_y"
prev_year="$prev_y"
$hit
/>
#######
# тег названий дней недели
@weekdays[]
<weekdays>
^for[i](0;6){
<day title="$calendar_locale.day_names.$i"/>
}
</weekdays>
#######
# формирование тегов для каждой недели месяца
# day - день месяца, может быть нулем, если начало/конец
# первой/последней недели месяца не приходятся на понедельник/воскресенье
@week[][day;in;f;hit]
^calendar_month.menu{
<week>
^for[i](0;6){
$day($calendar_month.$i)
# формирование атрибута текущего дня
^if(
$date_now.day == $day &&
$date_now.month == $date.month &&
$date_now.year == $date.year
){
$in[in="1"]
}{
$in[]
}
# формирование атрибута нахождения в данном дне
^if($day == $date.day){
$hit[hit="1"]
}{
$hit[]
}
# формирование атрибута наличия материалов за день
^if($days_of_files.$day){
$f[f="1"]
}{
$f[]
}
<day d="$day" $in $f $hit/>
}
</week>
}
Код класса для русского языка:
#######
# Вызывать так: формировать объект конструктором init
# $object[^calendar_ru::init[$date;$days_of_files]]
# далее получаем XML календаря ^object.xml[]
@CLASS
calendar_ru
#######
@USE
calendar.p
#######
@BASE
calendar
#######
@init[date;days_of_files]
^BASE:init[$date;$days_of_files]
#######
# Метод, в котором определяются специфические для разных языков
# параметры календаря
@i18n[][m;y]
# the hash of local calendar (month and day names)
$calendar_locale[
$.month_names[
$.1[Январь]
$.2[Февраль]
$.3[Март]
$.4[Апрель]
$.5[Май]
$.6[Июнь]
$.7[Июль]
$.8[Август]
$.9[Сентябрь]
$.10[Октябрь]
$.11[Ноябрь]
$.12[Декабрь]
]
$.day_names[
$.0[Пн]
$.1[Вт]
$.2[Ср]
$.3[Чт]
$.4[Пт]
$.5[Сб]
$.6[Вс]
]
]
$m($date.month)
$y($date.year)
$month[$calendar_locale.month_names.$m]
$calendar_month[^date:calendar[rus]($y;$m)]
Код класса для для английского языка:
#######
# Вызывать так: формировать объект конструктором init
# $object[^calendar_en::init[$date;$days_of_files]]
# далее получаем XML календаря ^object.xml[]
@CLASS
calendar_en
#######
@USE
calendar.p
#######
@BASE
calendar
#######
@init[date;days_of_files]
^BASE:init[$date;$days_of_files]
#######
# Метод, в котором определяются специфические для разных языков
# параметры календаря
@i18n[][m;y]
# the hash of local calendar (month and day names)
$calendar_locale[
$.month_names[
$.1[January]
$.2[February]
$.3[March]
$.4[April]
$.5[May]
$.6[June]
$.7[July]
$.8[August]
$.9[September]
$.10[October]
$.11[November]
$.12[December]
]
$.day_names[
$.0[Su]
$.1[Mo]
$.2[Tu]
$.3[We]
$.4[Th]
$.5[Fr]
$.6[Sa]
]
]
$m($date.month)
$y($date.year)
$month[$calendar_locale.month_names.$m]
$calendar_month[^date:calendar[eng]($y;$m)]
Пример стилевых таблиц для XSLT преобразований смотрите в предыдущем примере или в архиве с этим примером.
Загрузить пример: calendar.zip (Дата: 2.8.2003 Размер: 4.6 Kб)
]]>
2002-12-15T11:43:00+03:00
17
Календарь с использованием XML
UPDATE: В связи с ошибками содержащимися в предыдущем варианте этого примера, пример был серьёзно переделан и вместо двух неправильных вариантов кода, приведён один, - правильный.
Первое, с чего хочу начать, - это собственно XML, формируемый парсером. Он имеет следующий вид (ноябрь 2002):
<month>
<month_year
month="11"
month_title="ноябрь"
next_month="12"
prev_month="10"
year="2002"
next_year="2003"
prev_year="2001"
hit="1"
/>
<weekdays>
<day title="Пн"/>
<day title="Вт"/>
<day title="Ср"/>
<day title="Чт"/>
<day title="Пт"/>
<day title="Сб"/>
<day title="Вс"/>
</weekdays>
<week>
<day d="0"/>
<day d="0"/>
<day d="0"/>
<day d="0"/>
<day d="1"/>
<day d="2"/>
<day d="3" f="1"/>
</week>
<week>
<day d="4"/>
<day d="5"/>
<day d="6" f="1" hit="1"/>
<day d="7"/>
<day d="8"/>
<day d="9"/>
<day d="10" in="1"/>
</week>
...
</month>
Или графически (сгенерировано XML Spy ) это будет выглядеть так:
Думаю особо объяснять тут нечего, расскажу лишь о назначении некоторых атрибутов. in - появляется у дня если этот день является текущим днем, f - появляется у дня, за который были материалы, hit - этот день/месяц выбран пользователем. Да и вы наверно уже обратили внимание что элементов week может быть несколько, сколько? - информация к размышлению.
Информация о том, за какой месяц выводить календарь передаются коду класса через параметр конструктора. Параметр должен быть хэшем следующей структуры:
$date_user[
$.day(2)
$.month(10)
$.year(2002)
]
Т.е. если данные берутся из строки запроса, хэш можно инициализировать следующим образом:
$date_user[
$.day(^form:d.int(0))
$.month(^form:m.int(0))
$.year(^form:y.int(0))
]
Если при создании экземпляра класса ему передаётся хэш с нулевыми ключами, выводится календарь на текущий месяц текущего года.
Далее код класса:
#######
# Calendar class
@CLASS
calendar
#######
@init[date_user]
# получение текущей даты
$date_now[^date::now[]]
^if($date_user.year && $date_user.month){
$d($date_user.day)
$m($date_user.month)
$y($date_user.year)
}{
$d($date_now.day)
$m($date_now.month)
$y($date_now.year)
}
# Инициал. хэш показывающий какой период времени передан
# при создании экземпляра класса
$self.date_user[$date_user]
# вызов метода для создания специфических для языка переменных
# по-умолчанию для русского, если нужен другой язык, сделать
# класс производный от этого и переопределить в нем только один
# метод @i18n[]
^i18n[]
# определение переменных месяца и года для пред. месяца календаря
$calendar_month_prev[^date::create($y;$m)]
^calendar_month_prev.roll[month](-1)
$prev_m($calendar_month_prev.month)
$prev_y($calendar_month_prev.year)
# определение переменных месяца и года для след. месяца календаря
$calendar_month_next[^date::create($y;$m)]
^calendar_month_next.roll[month](+1)
$next_m($calendar_month_next.month)
$next_y($calendar_month_next.year)
# the hash of publications(files)
$days_of_files[
^hash::sql{
SELECT
DAYOFMONTH(date_field),
some_field
FROM
some_table
WHERE
MONTH(date_field) = $m and
YEAR(date_field) = $y
}[$.distinct(1)]
]
#######
# Метод, в котором определяются специфические для разных языков
# параметры календаря
@i18n[]
# the hash of local calendar (month and day names)
$calendar_locale[
$.month_names[
$.1[Январь]
$.2[Февраль]
$.3[Март]
$.4[Апрель]
$.5[Май]
$.6[Июнь]
$.7[Июль]
$.8[Август]
$.9[Сентябрь]
$.10[Октябрь]
$.11[Ноябрь]
$.12[Декабрь]
]
$.day_names[
$.0[Пн]
$.1[Вт]
$.2[Ср]
$.3[Чт]
$.4[Пт]
$.5[Сб]
$.6[Вс]
]
]
$month[$calendar_locale.month_names.$m]
$calendar_month[^date:calendar[rus]($y;$m)]
#######
@xml[]
$result[
<month>
^month_year[]
^weekdays[]
^week[]
</month>
]
#######
# формирование тега для года и месяца
@month_year[][hit]
^if(
$date_user.day ||
(
$date_now.month == $date_user.month &&
$date_now.year == $date_user.year &&
$date_user.day
)
){
$hit[]
}{
$hit[hit="1"]
}
<month_year
month="$m"
month_title="$month"
next_month="$next_m"
prev_month="$prev_m"
year="$y"
next_year="$next_y"
prev_year="$prev_y"
$hit
/>
#######
# тег названий дней недели
@weekdays[]
<weekdays>
^for[i](0;6){
<day title="$calendar_locale.day_names.$i"/>
}
</weekdays>
#######
# формирование тегов для каждой недели месяца
# day - день месяца, может быть нулем, если начало/конец
# первой/последней недели месяца не приходятся на понедельник/воскресенье
@week[][day;in;f;hit]
^calendar_month.menu{
<week>
^for[i](0;6){
$day($calendar_month.$i)
# формирование атрибута текущего дня
^if(
$date_now.day == $day &&
$date_now.month == $date.month &&
$date_now.year == $date.year
){
$in[in="1"]
}{
$in[]
}
# формирование атрибута нахождения в данном дне
^if($day == $date.day){
$hit[hit="1"]
}{
$hit[]
}
# формирование атрибута наличия материалов за день
^if($days_of_files.$day){
$f[f="1"]
}{
$f[]
}
<day d="$day" $in $f $hit/>
}
</week>
}
Для получения XML кода календаря нужно сформировать объект конструктором init $object[^calendar::init[$date_user]] далее вызовом ^object.xml[] получаем XML календаря.
Преобразуем полученный XML в HTML:
$date_user[
$.day(^form:d.int(0))
$.month(^form:m.int(0))
$.year(^form:y.int(0))
]
$object[^calendar::init[$date_user]]
$calendar_xml[^xdoc::create{^object.xml[]}]
$calendar_html[^$calendar_xml.transform[calendar.xsl]]
^calendar_html.string[
$.method[html]
$.indent[no]
]
Пример стилевой таблицы calendar.xsl:
<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<!-- переменные начальных значений месяца и года календаря -->
<xsl:variable name="start_year">2002</xsl:variable>
<xsl:variable name="start_month">2</xsl:variable>
<xsl:template match="month">
<table cellpadding="4" cellspacing="3" width="100%">
<tr>
<td
bgcolor="white"
align="center"
width="16%"
>
<xsl:call-template name="prev_month"/>
</td>
<td
bgcolor="white"
align="center"
colspan="5"
width="68%"
>
<xsl:choose>
<xsl:when test="week/*/@in">
<strong>
<xsl:call-template name="month_year_link"/>
</strong>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="month_year_link"/>
</xsl:otherwise>
</xsl:choose>
</td>
<td bgcolor="" align="center" width="16%">
<xsl:call-template name="next_month"/>
</td>
</tr>
<tr>
<xsl:apply-templates select="weekdays"/>
</tr>
<xsl:for-each select="week">
<tr>
<xsl:call-template name="week"/>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<!-- ссылка на предыдущий месяц -->
<xsl:template name="prev_month">
<xsl:choose>
<xsl:when
test="
month_year/@prev_year < $start_year
"
>
<<
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when
test="
month_year/@prev_month < $start_month and
month_year/@prev_year = $start_year
"
>
<<
</xsl:when>
<xsl:otherwise>
<a
href="{month_year/@prev_year}-{month_year/@prev_month}.html"
style="text-decoration: none;"
>
<<
</a>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- ссылка на следующий месяц -->
<xsl:template name="next_month">
<xsl:choose>
<xsl:when test="week/*/@in">
>>
</xsl:when>
<xsl:otherwise>
<a
href="{month_year/@next_year}-{month_year/@next_month}.html"
style="text-decoration: none;"
>
>>
</a>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- ссылка на текущий месяц -->
<xsl:template name="month_year_link">
<xsl:choose>
<xsl:when test="month_year/@hit">
<xsl:call-template name="month_year"/>
</xsl:when>
<xsl:otherwise>
<a href="{month_year/@year}-{month_year/@month}.html">
<xsl:call-template name="month_year"/>
</a>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- название месяца и года -->
<xsl:template name="month_year">
<xsl:value-of select="month_year/@month_title"/>
<xsl:text> </xsl:text>
<xsl:value-of select="month_year/@year"/>
</xsl:template>
<!-- названия дней недели -->
<xsl:template match="weekdays">
<xsl:for-each select="day">
<td
align="center"
width="16%"
bgcolor="#eeeeee"
>
<xsl:choose>
<xsl:when test="position() != 7">
<xsl:value-of select="@title"/>
</xsl:when>
<xsl:otherwise>
<span style="color: red">
<xsl:value-of select="@title"/>
</span>
</xsl:otherwise>
</xsl:choose>
</td>
</xsl:for-each>
</xsl:template>
<!-- вывод недель месяца -->
<xsl:template name="week">
<xsl:for-each select="day">
<td align="center" width="16%">
<xsl:attribute name="bgcolor">
<xsl:choose>
<xsl:when test="@in">
#ffcc00
</xsl:when>
<xsl:otherwise>
white
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:choose>
<xsl:when test="position() != 7">
<xsl:call-template name="day"/>
</xsl:when>
<xsl:otherwise>
<span style="color: red">
<xsl:call-template name="day"/>
</span>
</xsl:otherwise>
</xsl:choose>
</td>
</xsl:for-each>
</xsl:template>
<!-- вывод дней -->
<xsl:template name="day">
<xsl:choose>
<xsl:when test="@hit">
<strong><xsl:call-template name="day_value"/></strong>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="no_hit"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- вывод невыбранных дней -->
<xsl:template name="no_hit">
<xsl:choose>
<xsl:when test="@f">
<a href="{//*/@year}-{//*/@month}-{@d}.html">
<xsl:call-template name="day_value"/>
</a>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="day_value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="day_value">
<xsl:choose>
<xsl:when test="@d = 0"/>
<xsl:when test="@d > 9">
<xsl:value-of select="@d"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>0</xsl:text><xsl:value-of select="@d"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Этот календарь легко преобразуется в требуемый вид для другого языка, например английского. Для этого необходимо создать класс производный от класса calendar в котором переопределяется всего лишь один метод ^i18n[], например для английского языка:
#######
@CLASS
calendar_eng
#######
@USE
calendar.p
#######
@BASE
calendar
#######
# Метод, в котором определяются специфические для разных языков
# параметры календаря
@i18n[]
# the hash of local calendar (month and day names)
$calendar_locale[
$.month_names[
$.1[January]
$.2[February]
$.3[March]
$.4[April]
$.5[May]
$.6[June]
$.7[July]
$.8[August]
$.9[September]
$.10[October]
$.11[November]
$.12[December]
]
$.day_names[
$.0[Su]
$.1[Mo]
$.2[Tu]
$.3[We]
$.4[Th]
$.5[Fr]
$.6[Sa]
]
]
$month[$calendar_locale.month_names.$m]
$calendar_month[^date:calendar[eng]($y;$m)]
Ну и далее вместо класса calendar подключайте и используйте calendar_eng:
$object[^calendar_eng::init[]]
$calendar_xml[<?xml version="1.0" encoding="$request:charset"?>
^xdoc::create{^object.xml[]}]
$calendar_html[^$calendar_xml.transform[calendar_eng.xsl]]
^calendar_html.string[
$.method[html]
$.indent[no]
]
Да и ещё, - в английском языке первый день недели не понедельник а воскресенье, поэтому стилевую таблицу необходимо подправить(переопределить в ней пару шаблонов). Текст calendar_eng.xsl:
<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:import href="calendar.xsl"/>
<xsl:template match="weekdays">
<xsl:for-each select="day">
<td
bgcolor="#eeeeee"
align="center"
width="16%"
>
<xsl:choose>
<xsl:when test="position() != 1">
<xsl:value-of select="@title"/>
</xsl:when>
<xsl:otherwise>
<span style="color: red">
<xsl:value-of select="@title"/>
</span>
</xsl:otherwise>
</xsl:choose>
</td>
</xsl:for-each>
</xsl:template>
<xsl:template name="week">
<xsl:for-each select="day">
<td align="center" width="16%">
<xsl:attribute name="bgcolor">
<xsl:choose>
<xsl:when test="@in">
#ffcc00
</xsl:when>
<xsl:otherwise>
white
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:choose>
<xsl:when test="position() != 1">
<xsl:call-template name="day"/>
</xsl:when>
<xsl:otherwise>
<span style="color: red">
<xsl:call-template name="day"/>
</span>
</xsl:otherwise>
</xsl:choose>
</td>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Разумеется можно и не получать HTML из XML не отходя от кассы, - в случае если полностью вся страница/сайт формируется сначала в XML'е и затем преобразуется в HTML, это делать совсем не надо. Надо просто вставить вызов ^object.xml[] в требуемое место, ну и затем в нужном xsl файле подключать calendar.xsl и ловить им элемент month в требуемом месте.
Ну вот и всё, ничего сложного я надеюсь. Работающий пример смотрите ... сами знаете где смотреть :).
Загрузить пример: calendar.zip (Дата: 2.8.2003 Размер: 4.18 Kб)
]]>
2002-11-21T08:50:00+03:00
16
Формирование списка всех URI сайта
Иногда может понадобиться список всех URI сайта(например для создания навигационной строки). Можно хранить этот список в текстовом файле, однако это неудобно, например при изменении имени каталога, который имеет детей(подкаталоги), придётся менять все записи являющиеся детями этого раздела и при глубокой иерархии это может привести к большим проблемам.
Я предлагаю хранить информацию о разделах в БД, в таблице следующей структуры(t_sections):
id - числовой(уникальный) идентификатор раздела
parent_id - идентификатор раздела родителя(если нет, то он равен нулю)
dir - имя каталога раздела (без слешей)
title - название раздела
Пример:
id
parent_id
dir
title
1
0
hardware
Железо
11
1
computers
Компьютеры
12
1
printers
Принтеры
121
12
laser
Лазерные
122
12
ink
Струйные
2
0
software
Программное обеспечение
21
2
os
Операционные системы
22
2
editors
Текстовые редакторы
3
0
news
Новости
И далее по информации из этой таблицы формируется список всех URI, - это удобно тем, что при изменении любого имени каталога раздела имеющего детей, вам не нужно будет менять никакие другие записи, кроме самой изменяемой записи раздела. Разумеется идентификатор раздела не должен меняться ни при каких условиях.
Код класса который формирует таблицу всех URI сайта:
#######
# работа с URI - формирование списка всех URI сайта
@CLASS
uri
#######
# конструктор
@init[]
$items[^table::sql{
SELECT
id,
parent_id,
dir,
title
FROM
t_sections
}]
#######
# форм. таблицы соответствий uri для всех разделов сайта
@get_all_site_uri[]
$result[^table::create{uri id title}]
^items.menu{
^result.append{^get_uri[$items.id] $items.id $items.title}
}
#######
# получение uri раздела по заданному id
@get_uri[id][item;uri;define_parent_id;parent_id;method_id]
$method_id($id)
# получение parent_id раздела по заданному id
$define_parent_id{
$item[^items.select($items.id == $method_id)]
$parent_id($item.parent_id)
}
$define_parent_id
^while($parent_id){
$uri[$item.dir/$uri]
$method_id($item.parent_id)
$define_parent_id
}
$uri[$item.dir/$uri]
$result[/$uri]
Код запускается в работу так:
$object[^uri::init[]]
# получаем таблицу содержащую список всех URI сайта
$site_uri[^object.get_all_site_uri[]]
После бурных дебатов с товарищами по несчастью, родился ещё один вариант кода для данной задачи:
#######
# работа с URI - формирование списка всех URI сайта
@CLASS
uri
#######
# конструктор
@init[]
$items[^table::sql{
SELECT
id,
parent_id,
dir,
title
FROM
t_sections
}]
#######
# форм. таблицы соответствий uri для всех разделов сайта
@get_all_site_uri[]
$result[^table::create{id title uri}]
^items.menu{
^result.append{$items.id $items.title ^get_uri[$items.id]}
}
#######
# получение uri раздела по заданному id
@get_uri[id][parent_id;uri]
$parent_id($id)
^while($parent_id && ^items.locate[id;$parent_id]){
$uri[$items.dir/$uri]
$parent_id($items.parent_id)
}
$result[/$uri]
Обсудим достоинства и недостатки методов. Первый способ решения этой задачи достаточно ресурсоёмок и в случае большого количества разделов сайта, могут наблюдатся тормоза, ибо ^table.select(...) последовательно перебирает все строки таблицы, что ужасно и кроме того, этот перебор делается и для разделов которых это можно и не делать (для тех у которых нет родителей).
Во втором случае ресурсов потребляется меньше ибо ^table.locate[...] очевидно быстрее и не выполняется ненужных проходов по таблице. Однако здесь очень важен порядок вызова метода ^get_uri[] в:
^result.append{$items.id $items.title ^get_uri[$items.id]}
этот вызов обязательно должен быть последним, иначе класс будет неправильно работать, т.е. результат зависит от порядка вызова метода. Такой фокус происходит из-за того, что данный метод меняет значение внешней переменной(поля класса) т.е. он меняет указатель на текущую строку и поэтому, стиля программирования с функциями/методами изменяющими значения внешних переменных лучше избегать в любом случае, иначе неявные/нетривиальные ошибки вам обеспечены.
И ещё, у обоих методов есть недостаток - для одного и того же раздела имеющего детей, по нескольку раз вычисляется значение URI, а это лишние и бесполезные итерации.
Идем далее. Есть ещё один метод, - это использование хэша (придумал не я). Поиск с помощью хэша работает побыстрее и у него отсутствет недостаток заключающийся в порядке вызова. Код с использованием хэша:
#######
# работа с URI - формирование списка всех URI сайта
@CLASS
uri
#######
# конструктор
@init[]
$items[^table::sql{
SELECT
id,
parent_id,
dir,
title
FROM
t_sections
}]
# создание хэша из таблицы для ускорения поиска
$items_hash[^items.hash[id]]
#######
# форм. таблицы соответствий uri для всех разделов сайта
@get_all_site_uri[][uri]
$result[^table::create{uri id title}]
$uri{^if(def $items_hash.[$items.id].uri){$items_hash.[$items.id].uri}{^get_uri[$items.id]}}
^items.menu{
^result.append{$uri $items.id $items.title}
}
#######
# получение uri раздела по заданному id
@get_uri[id][parent_id;uri]
$parent_id($id)
^while($parent_id && $items_hash.[$parent_id]){
$uri[$items_hash.[$parent_id].dir/$uri]
$items_hash.[$id].uri[$uri]
$parent_id($items_hash.[$parent_id].parent_id)
}
$result[/$uri]
Да, и тут опять произошли бурные дебаты с товарищами, - выяснилось что база данных вовсе не обязательна, - информацию о разделах можно хранить в простом текстовом, tab-delimited файле с указанной структурой. В этом случае достаточно слегка изменить реализацию конструктора:
#######
# конструктор
@init[]
$items[^table::load[sections.txt]
# создание хэша из таблицы для ускорения поиска
$items_hash[^items.hash[id]]
Где в sections.txt и хранится вся информация о разделах.
И наконец самый универсальный код класса - мы вынесем из конструктора код формирования исходной таблицы разделов и будем передавать эту таблицу параметром конструктора при создании объекта данного класса. Такой подход позволяет нам использовать нам один и тот же код без каких-либо изменений в различных проектах:
#######
# работа с URI - формирование списка всех URI сайта
@CLASS
uri
#######
# конструктор
@init[items]
# входная таблица разделов
$self.items[$items]
# создание хэша из таблицы для ускорения поиска
$items_hash[^items.hash[id]]
#######
# форм. таблицы соответствий uri для всех разделов сайта
@get_all_site_uri[][uri]
$result[^table::create{uri id title}]
$uri{^if(def $items_hash.[$items.id].uri){$items_hash.[$items.id].uri}{^get_uri[$items.id]}}
^items.menu{
^result.append{$uri $items.id $items.title}
}
#######
# получение uri раздела по заданному id
@get_uri[id][parent_id;uri]
$parent_id($id)
^while($parent_id && $items_hash.[$parent_id]){
$uri[$items_hash.[$parent_id].dir/$uri]
$items_hash.[$id].uri[$uri]
$parent_id($items_hash.[$parent_id].parent_id)
}
$result[/$uri]
Здесь перед формированием объекта класса, необходимо сначала сформировать исходную таблицу разделов, которую и передавать в качестве параметра конструктору, например:
# создание исходной таблицы
$items[^table::sql{
SELECT
id,
parent_id,
dir,
title
FROM
t_sections
}]
# создание объекта класса
$object[^uri::init[$items]]
# получаем таблицу содержащую список всех URI сайта
$site_uri[^object.get_all_site_uri[]]
Как выяснилось ещё позднее, - в последних двух примерах (с хэшем) есть неточности:
В методе ^get_all_site_uri[] условие def $items_hash.[$items.id].uri никода не выполняется, если до этого ни разу не вызван метод ^get_uri[] не в контексте этого метода - поэтому, этот наворот оттуда убран. Вообще говоря, это задумывалось для повторного использования уже вычисленных полных URI, которые, если они являются родителями других разделов, могли бы использоваться при вычислении URI детей.
$items_hash.[$id].uri[$uri] - в методе ^get_uri[] формирует полный URI без начального слэша.
Исправляю данные неточности (код класса с комментариями):
#######
@CLASS
uri
#######
# конструктор
@init[items]
# входная таблица разделов
$self.items[$items]
# создание хэша из таблицы для ускорения поиска
$items_hash[^items.hash[id]]
#######
# форм. таблицы соответствий uri для всех разделов сайта
@get_all_site_uri[]
$result[^table::create{uri id title}]
^items.menu{
^result.append{^get_uri[$items.id] $items.id $items.title}
}
#######
# получение uri раздела по заданному id
@get_uri[id][parent_id;uri]
$parent_id($id)
^while($parent_id && $items_hash.[$parent_id]){
# Если для родителя уже вычислен URI, нефиг крутить весь цикл -
# сначала запросить $items_hash на предмет возможно сформированных полных URI родителей
# и затем, просто подставить его перед $uri - промежуточной переменной
# через которую формируется путь, путём добавления в её начало
# частей пути от родителей.
^if(def $items_hash.[$parent_id].uri){
$uri[${items_hash.[$parent_id].uri}$uri]
$items_hash.[$id].uri[$uri]
$parent_id(0)
# внимание! результат должен формироваться именно так (без начального слеша)
# потому что начальный слеш уже есть - он добавлен при формировании
# URI родителя, см. альтернативную ветку
$result[$uri]
}{
$uri[$items_hash.[$parent_id].dir/$uri]
# формируем новый ключ хеша (поле класса) - полный URI некоего раздела
# необходимо для повторного использования уже вычисленных URI разделов
$items_hash.[$id].uri[/$uri]
$parent_id($items_hash.[$parent_id].parent_id)
}
}
# вывод только если результат неопределён т.е.
# условие if внутри цикла ложно - формирование URI для
# разделов для которых ещё не вычислено полное URI родителя
# для проверки повторного использования вычисленных URI
# можно закомментировать if - в случае повторного использования
# URI при повторном использовании будет с двумя начальными слешами
^if(!def $result){
$result[/$uri]
}
Небольшое примечание:
Повторно, уже вычисленные URI родителей, будут использоваться только тогда, когда их вычисление (вызов ^get_uri[]) происходит перед вычислением URI детей, т.е. в случае работы метода ^get_all_site_uri[] хорошо бы, чтобы в исходной таблице разделов, родители шли раньше детей.
В итоге, после всех манипуляций, для приведенного примера таблицы, получается следующий список URI:
uri
id
title
/hardware/
1
Железо
/hardware/computers/
11
Компьютеры
/hardware/printers/
12
Принтеры
/hardware/printers/laser/
121
Лазерные
/hardware/printers/ink/
122
Струйные
/software/
2
Программное обеспечение
/software/os/
21
Операционные системы
/software/editors/
22
Текстовые радакторы
/news/
3
Новости
Загрузить пример: uri.zip (Дата: 2.8.2003 Размер: 1.34 Kб)
]]>
2002-11-18T17:08:00+03:00
15
содержание | 2 | Триумф сетевого маркетинга в России, полезная информация о продукции от Эмвей, amway ru
Используются технологии uCoz