changeset 1:548a93c24e55 release_0_1jk

Tema 0.1jk - Javakonkurs edition (imported from CVS).
author Mikhail Kryshen <mikhail@kryshen.net>
date Thu, 14 Dec 2006 23:22:05 +0300
parents 1d2fe61a3a62
children 6c41a0b43e58
files TODO build.xml dist/biotopes/biotope.template dist/biotopes/classes.template dist/biotopes/doc/article.txt dist/biotopes/doc/readme.html dist/biotopes/doc/readme.txt dist/biotopes/main.template dist/biotopes/photo.template dist/biotopes/plants.template dist/readme.html dist/readme.txt dist/tema dist/tema.bat dist/tema.properties doc/article.txt doc/manual/index.html res/kryshen/tema/demo/demo.template sample/class.xml sample/plants.xml src/kryshen/tema/Function.java src/kryshen/tema/FunctionDataParser.java src/kryshen/tema/Functions.java src/kryshen/tema/ImageConverter.java src/kryshen/tema/ReplaceWriter.java src/kryshen/tema/Tema.java src/kryshen/tema/TemplateException.java src/kryshen/tema/TemplateParser.java src/kryshen/tema/TemplateReader.java src/kryshen/tema/demo/DemoFrame.java src/kryshen/tema/demo/Hello.java src/kryshen/tema/functions/Define.java src/kryshen/tema/functions/IO.java src/kryshen/tema/functions/ImageConverter.java src/kryshen/tema/functions/Logics.java src/kryshen/tema/functions/ReplaceWriter.java src/kryshen/tema/functions/Standard.java test/Hello.class test/Hello.java test/demo test/demo.bat test/include.template test/inctest.template test/macrotest test/main.template test/tema.properties
diffstat 46 files changed, 2612 insertions(+), 1412 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TODO	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,1 @@
+if, <, >, =, +, -, *, /
--- a/build.xml	Tue May 16 18:04:09 2006 +0400
+++ b/build.xml	Thu Dec 14 23:22:05 2006 +0300
@@ -5,6 +5,7 @@
     <property name="build"    value="build"/>
     <property name="dist"     value="dist"/>
     <property name="res"      value="res"/>
+    <property name="doc"      value="doc"/>
     <property name="jar_file" value="tema.jar"/>
 
     <target name="init">
@@ -14,20 +15,30 @@
 
     <target name="compile" depends="init">
         <javac srcdir="${src}" destdir="${build}"
-               deprecation="on" optimize="on" debug="on"/>
+               deprecation="on" optimize="on" debug="on">
+          <!-- <compilerarg value="-Xlint:unchecked"/> -->
+        </javac>
     </target>
 
     <target name="dist" depends="compile">
 	<jar jarfile="${dist}/${jar_file}" manifest="${src}/Manifest.mf">
 	    <fileset dir="${build}" includes="**/*.class"/>
+	    <fileset dir="${res}" includes="**/*"/>
 	</jar>
-    </target>    
+    </target>
 
+    <target name="javadoc" depends="init">
+        <javadoc destdir="${doc}/api" sourcepath="${src}"
+                 packagenames="kryshen.tema.*">
+        </javadoc>
+    </target>
+ 
     <target name="clean">
 	<delete>
 	    <fileset dir="${build}" includes="**/*.class"/>
 	</delete>
 	<delete file="${dist}/${jar_file}"/>
+	<delete dir="${doc}/api"/>
     </target>
     
 </project>
--- a/dist/biotopes/biotope.template	Tue May 16 18:04:09 2006 +0400
+++ b/dist/biotopes/biotope.template	Thu Dec 14 23:22:05 2006 +0300
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="ISO-8859-5"?>
 <!DOCTYPE чъьАпщьу_рьтп SYSTEM "brief.dtd">
-<!-- rangcode: <%xml_escape get\rangcode%> -->
+<!-- rangcode: <%xml_escape db\rangcode%> -->
 <чъьАпщьу_рьтп show="0">
   <ЮьАЦщзь display="0">
-    <%optional:<зпЮБп_ЮпАъЮчАБЮпщущьО щчэуЮ="1" file="<%xml_escape image:maps/<%get\rangcode%>.gif maps/<%get\code%>.png png%>"/>%>
+    <%optional:<зпЮБп_ЮпАъЮчАБЮпщущьО щчэуЮ="1" file="<%xml_escape image:maps/<%db\rangcode%>.gif maps/<%db\code%>.png png%>"/>%>
 <%query:photo_sql [%\<%include\photo.template%>%] <%get\code%>%>
   </ЮьАЦщзь>
-  <щпврпщьу><%xml_escape get\rusname%></щпврпщьу>
-  <зшпАА><%xml_escape get\class0%></зшпАА>
-  <ъчтзшпАА1><%xml_escape get\class1%></ъчтзшпАА1>
-  <ъчтзшпАА2><%xml_escape get\class2%></ъчтзшпАА2>
-  <ъчтзшпАА3><%xml_escape get\class3%></ъчтзшпАА3>
-  <зЮпБзпО_ЕпЮпзБуЮьАБьзп rows="3"><%xml_cdata get\descript%></зЮпБзпО_ЕпЮпзБуЮьАБьзп>
-  <ъЮьэуЮ_чъьАпщьО rows="3"><%xml_cdata get\geobotdescr%></ъЮьэуЮ_чъьАпщьО>
-  <прБчЮК_чъьАпщьО><%xml_escape get\contributors%></прБчЮК_чъьАпщьО>
+  <щпврпщьу><%xml_escape db\rusname%></щпврпщьу>
+  <зшпАА><%xml_escape db\class0%></зшпАА>
+  <ъчтзшпАА1><%xml_escape db\class1%></ъчтзшпАА1>
+  <ъчтзшпАА2><%xml_escape db\class2%></ъчтзшпАА2>
+  <ъчтзшпАА3><%xml_escape db\class3%></ъчтзшпАА3>
+  <зЮпБзпО_ЕпЮпзБуЮьАБьзп rows="3"><%xml_cdata db\descript%></зЮпБзпО_ЕпЮпзБуЮьАБьзп>
+  <ъЮьэуЮ_чъьАпщьО rows="3"><%xml_cdata db\geobotdescr%></ъЮьэуЮ_чъьАпщьО>
+  <прБчЮК_чъьАпщьО><%xml_escape db\contributors%></прБчЮК_чъьАпщьО>
 </чъьАпщьу_рьтп>
--- a/dist/biotopes/classes.template	Tue May 16 18:04:09 2006 +0400
+++ b/dist/biotopes/classes.template	Thu Dec 14 23:22:05 2006 +0300
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="ISO-8859-5"?>
 <зшпААьДьзпФьО><%query:class_sql [%\
   <!-- rangcode: <%xml_escape get\rangcode%> -->
-  <зшпАА id="<%get\NUMBER%>"><%xml_escape get\rusname%></зшпАА>%]%>
+  <зшпАА id="<%get\NUMBER%>"><%xml_escape db\rusname%></зшпАА>%]%>
 </зшпААьДьзпФьО>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dist/biotopes/doc/article.txt	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,391 @@
+* Преобразование базы данных в XML-формат электронной библиотеки.
+
+Для решения задачи преобразования базы данных "Местообитания Восточной
+Фенноскандии" из формата Microsoft Access в XML-формат электронной
+библиотеки КарНЦ РАН была разработана программная система DbReader.
+
+Программа DbReader является универсальным средством для представления
+информации из баз данных в текстовых форматах. Программа обрабатывает
+заданные шаблоны текстовых файлов и заменяет найденные в них
+инструкции на результаты их выполнения. Различные инструкции позволяют
+формировать SQL-запросы, выполнять их и обрабатывать полученные
+данные.
+
+
+* Формат инструкций:
+
+    <%<список_функций>{:|\}<данные>%>
+  где 
+
+<список_функций> - список имен функций, разделенных пробелами.
+    Может быть пустым.
+<данные> - данные, передаваемые функции.
+
+
+* Формат данных:
+
+    [<список_аргументов>][<текст>]
+  где
+
+<список_аргументов> - список аргументов функции, разделенных пробелами.
+    Может быть пустым.
+<текст> - текст, передаваемый функции без разбиения на аргументы.
+    Может быть пустым. Количество аргументов, после которого следует
+    текст, зависит от функции.
+
+Разделитель между списком функций и данными определяет, как должны
+обрабатываться данные функции:
+: - рекурсивная обработка,
+\ - передать без обработки.
+
+Если в списке функций задано две и более функции, они выполняются,
+начиная с последней, так что каждая функция получает в качестве данных
+результат выполнения следующей функции.
+
+Каждая функция имеет код возврата - целое число. Код возврата
+инструкции - код возврата первой в списке функции. Код возврата,
+получаемый при обработке текста - сумма кодов возврата обработанных
+инструкций.
+
+Вместо скобок '<', '>' можно использовать скобки '[', ']'.
+
+
+* Реализованные функции:
+
+Функция:      set
+Аргументы:    <имя>
+Текст:        <значение>
+Действие:     устанавливает значение переменной <имя>
+Результат:    <имя>
+Код возврата: 1
+
+Функция:      get
+Аргументы:    <имя>
+Результат:    значение переменной <имя>. 
+Код возврата: 1, если значение переменной было установлено, 0 - иначе.
+
+Функция:      prepare
+Аргументы:    <имя>
+Текст:        <запрос>
+Действие:     Подготавливает SQL-запрос <запрос> для выполнения,
+              записывает подготовленный запрос в переменную <имя>.
+Результат:    <имя>
+Код возврата: 1
+
+Функция:      query
+Аргументы:    <имя_запроса> <шаблон> <парам1> ... <парамN>
+Действие:     Выполняет запрос с параметрами, подготовленный с помощью
+              функции prepare. Значения параметров подставляются в
+              запрос вместо символа '?'. Значения полей ответа будут
+              записаны, как переменные шаблона <шаблон>. При обработки
+              шаблона <шаблон> также определяется переменная NUMBER,
+              содержащая номер текущей строки ответа. Переменные,
+              определенные в вызывающем шаблоне, доступны из шаблона
+              <шаблон> по имени SUPER.<имя_переменной>.
+
+Результат:    результат обработки шаблона <шаблон> для каждой строки
+              ответа.
+Код возврата: количество полученных строк ответа.
+
+Функция:      optional
+Текст:        <данные>
+Результат:    <данные>, если при обработке данных был получен
+              код возврата отличный от 0, иначе - пустая строка.
+Код возврата: 1, если результат - пустая строка, 0 - иначе.
+
+Функция:      image
+Аргументы:    <исх_файл> <кон_файл> <формат> [<макс_ширина> [<макс_высота>]]
+Действие:     Загружает изображение из файла <исх_файл> (путь определяется
+              относительно конфигурационного параметра "resource_base") и
+              преобразует его в указанный формат, сохраняя результат в
+              <кон_файл>. Если заданы максимальная высота и ширина, большие
+              изображения будут уменьшены. 
+Результат:    <кон_файл> при успешном выполнении, пустая строка - иначе.
+Код возврата: 1 при успешном выполнении, 0 - иначе.
+
+Функция:      copy
+Аргументы:    <исх_файл> <кон_файл>
+Действие:     Копирует файл <исх_файл> в файл <кон_файл>
+	      (путь <исх_файл> определяется относительно
+              конфигурационного параметра "resource_base").
+Результат:    <кон_файл> при успешном выполнении, пустая строка - иначе.
+Код возврата: 1 при успешном выполнении, 0 - иначе.
+
+Функция:      write
+Аргументы:    <имя_файла>
+Текст:        <данные>
+Действие:     Записывает <данные> в файл <исх_файл>.
+Результат:    <кон_файл> при успешном выполнении, пустая строка - иначе.
+Код возврата: 1 при успешном выполнении, 0 - иначе.
+
+Функция:      read
+Текст:        <имя_файла>
+Действие:     Читает файл <имя_файла>.
+Результат:    прочитанные данные при успешном выполнении, пустая
+              строка - иначе.
+Код возврата: 1 при успешном выполнении, 0 - иначе.
+
+Функция:      include
+Текст:        <имя_файла>
+Действие:     включает шаблон из файла <имя_файла>.
+Результат:    результат обработки шаблона.
+Код возврата: код возврата, полученный при обработке шаблона.
+
+Функция:      !
+Текст:        <данные>
+Действие:     нет.
+Результат:    нет.
+Код возврата: код возврата, полученный при обработке текста данных.
+
+Функция:      replace
+Аргументы:    <стр1> <стр2>
+Текст:        <данные>
+Результат:    данные, с замененными вхождениями подстроки <стр1>
+	      на <стр2>.
+Код возврата: код возврата, полученный при обработке текста данных.
+
+Функция:      xml_escape
+Текст:        <данные>
+Результат:    текст <данные>, в котором  символы
+              '&', '<', '>', '`', '\' заменены на соответствующие
+	      сущности XML.
+Код возврата: код возврата, полученный при обработке текста данных.
+
+Функция:      xml_cdata
+Текст:        <данные>
+Результат:    данные в виде блока XML CDATA.
+Код возврата: код возврата, полученный при обработке текста данных.
+
+Система DbReader расширяема: возможно добавление в систему новых
+функций, реализованных в виде классов на языке Java.
+
+
+* Применение системы DbReader для преобразования базы данных
+  "Местообитания Восточной Фенноскандии" из формата Microsoft Access в
+  XML-формат электронной библиотеки КарНЦ РАН
+
+Для преобразования используются две таблицы базы данных "Местообитания
+Восточной Фенноскандии" (перечислены только используемые поля):
+
+biotopelist (code, rangcode, rusname, engname, descript, geobotdescr,
+             contributors, descrdate)
+
+  code - счетчик, ключевое поле;
+  rangcode - поле в формате ABBCCDDEE, где А, BB, CC, DD, ЕЕ - коды
+             класса, подклассов и растения;
+  rusname, engname, descript, geobotdescr - текстовые поля;
+  descrdate - дата.
+
+photos (biotope, photo, authphoto)
+
+  biotope - число, указывает на code в biotopelist;
+  photo - имя файла фотографии;
+  authphoto - текст.
+
+Требуется сгенерировать xml-файл для каждой записи в biotopelist, файл
+со списком классов, файл со списком растений, полноразмерные и
+уменьшенные версии каждой фотографии.
+
+Для генерации xml-файлов с описаниями биотопов использовался следующий
+шаблон DbReader (файл biotope.template):
+
+----------------------------------------------------------------------
+<?xml version="1.0" encoding="ISO-8859-5"?>
+<!DOCTYPE описание_вида SYSTEM "brief.dtd">
+<!-- rangcode: <%xml_escape get\rangcode%> -->
+<описание_вида show="0">
+  <рисунки display="0">
+    <%optional:<карта_распространения номер="1"
+    file="<%xml_escape image:maps/<%get\rangcode%>.gif maps/<%get\code%>.png png%>"/>%>
+<%query:photo_sql [%\<%include\photo.template%>%] <%get\code%>%>
+  </рисунки>
+  <название><%xml_escape get\rusname%></название>
+  <класс><%xml_escape get\class0%></класс>
+  <подкласс1><%xml_escape get\class1%></подкласс1>
+  <подкласс2><%xml_escape get\class2%></подкласс2>
+  <подкласс3><%xml_escape get\class3%></подкласс3>
+  <краткая_характеристика rows="3"><%xml_cdata get\descript%></краткая_характеристика>
+  <пример_описания rows="3"><%xml_cdata get\geobotdescr%></пример_описания>
+  <авторы_описания><%xml_escape get\contributors%></авторы_описания>
+</описание_вида>
+----------------------------------------------------------------------
+
+Соответствующий SQL-запрос, при выполнении которого устанавливаются
+значения переменных шаблона rangcode, rusname, class0, class1, class2,
+class3, descript, geobotdescr и contributors (файл biotope.sql):
+
+----------------------------------------------------------------------
+SELECT t1.*,
+
+(SELECT t2.rusname FROM biotopelist AS t2
+WHERE Left(t2.rangcode, 1) = Left(t1.rangcode, 1)
+AND Right(t2.rangcode, 8)  = '00000000'
+AND Right(t1.rangcode, 8) <> '00000000') AS class0,
+
+(SELECT t2.rusname FROM biotopelist AS t2
+WHERE Left(t2.rangcode, 3) = Left(t1.rangcode, 3)
+AND Right(t2.rangcode, 6)  = '000000'
+AND Right(t1.rangcode, 6) <> '000000') AS class1,
+
+(SELECT t2.rusname FROM biotopelist AS t2
+WHERE Left(t2.rangcode, 5) = Left(t1.rangcode, 5)
+AND Right(t2.rangcode, 4)  = '0000'
+AND Right(t1.rangcode, 4) <> '0000') AS class2,
+
+(SELECT t2.rusname FROM biotopelist AS t2
+WHERE Left(t2.rangcode, 7) = Left(t1.rangcode, 7)
+AND Right(t2.rangcode, 2)  = '00'
+AND Right(t1.rangcode, 2) <> '00') AS class3
+
+FROM biotopelist AS t1
+WHERE rusname
+----------------------------------------------------------------------
+
+Шаблон biotope.template содержит инструкцию <%include\photo.template%>,
+включающую шаблон описания фотографии (файл photo.template):
+
+----------------------------------------------------------------------
+    <фотография num="<%xml_escape get\NUMBER%>"
+       <%optional:автор="<%xml_escape get\authphoto%>" %>text="" 
+       file="<%xml_escape image:biotopephotos/<%get\photo%> images/<%get\SUPER.code%>_<%get\NUMBER%>.jpg jpg 300 300%>" 
+        big="<%xml_escape copy:biotopephotos/<%get\photo%> images/big/<%get\SUPER.code%>_<%get\NUMBER%>.jpg%>"/>
+----------------------------------------------------------------------
+
+Соответствующий SQL-запрос для получения описания фотографий
+(photo.sql):
+
+----------------------------------------------------------------------
+SELECT * from photos WHERE biotope = ?
+----------------------------------------------------------------------
+
+Шаблон, генерирующий список классов (файл classes.template):
+
+----------------------------------------------------------------------
+<?xml version="1.0" encoding="ISO-8859-5"?>
+<классификация><%query:class_sql [%\
+  <!-- rangcode: <%xml_escape get\rangcode%> -->
+  <класс id="<%get\NUMBER%>"><%xml_escape get\rusname%></класс>%]%>
+</классификация>
+----------------------------------------------------------------------
+
+Соответствующий SQL-запрос (class.sql):
+
+----------------------------------------------------------------------
+SELECT t1.*
+FROM biotopelist AS t1
+WHERE rusname AND Right(t1.rangcode, 8)  = '00000000'
+  AND EXISTS
+    (SELECT NULL FROM biotopelist AS t2
+     WHERE Left(t2.rangcode, 1) = Left(t1.rangcode, 1)
+     AND Right(t2.rangcode, 8) <> '00000000')
+----------------------------------------------------------------------
+
+Шаблон, генерирующий список растений (файл plants.template):
+
+----------------------------------------------------------------------
+<?xml version="1.0" encoding="ISO-8859-5"?>
+<растения><%query:plant_sql [%\
+  <!-- rangcode: <%xml_escape get\rangcode%> -->
+  <растение><%xml_escape get\rusname%></растение>%]%>
+</растения>
+----------------------------------------------------------------------
+
+Соответствующий SQL-запрос (plant.sql):
+
+----------------------------------------------------------------------
+SELECT t1.*
+FROM biotopelist AS t1
+WHERE rusname AND Right(t1.rangcode, 2) <> '00'
+----------------------------------------------------------------------
+
+Система DbReader начинает работу с обработки шаблона index.template,
+который содержит вызовы перечисленных выше SQL-запросов и включения
+шаблонов:
+
+----------------------------------------------------------------------
+<%!:
+  <%prepare:biotope_sql <%read\biotope.sql%>%>
+  <%prepare:photo_sql <%read\photo.sql%>%>
+  <%prepare:class_sql <%read\class.sql%>%>
+  <%prepare:plant_sql <%read\plant.sql%>%>
+
+  <%query:biotope_sql [%\<%write:<%get\code%>.xml <%include\biotope.template%>%> %]%>
+
+  <%write:classes.xml <%include\classes.template%>%>
+  <%write:plants.xml <%include\plants.template%>%>
+%>
+----------------------------------------------------------------------
+
+Пример сгенерированного XML-файла:
+
+----------------------------------------------------------------------
+<?xml version="1.0" encoding="ISO-8859-5"?>
+<!DOCTYPE описание_вида SYSTEM "brief.dtd">
+<!-- rangcode: E04020302 -->
+<описание_вида show="0">
+  <рисунки display="0">    
+    <фотография num="1" автор="Крышень А.М." text="" 
+                file="images/3_1.jpg" 
+                big="images/big/3_1.jpg"/>
+    <фотография num="2" автор="Крышень А.М." text="" 
+                file="images/3_2.jpg" 
+                big="images/big/3_2.jpg"/>
+  </рисунки>
+  <название>Betula sp. - Avenella flexuosa - Polytrichum commune</название>
+  <класс>E. Суходольные местообитания</класс>
+  <подкласс1>Автоморфные влажные</подкласс1>
+  <подкласс2></подкласс2>
+  <подкласс3></подкласс3>
+  <краткая_характеристика rows="3"><![CDATA[Развиваются  на луговиковых вырубках лесов черничного типа.  Через 5 - 7 лет  после рубки порослевая береза выходит из  яруса травянистых растений, ее влияние сказывается, в первую очередь, на влажности почвы.]]></краткая_характеристика>
+  <пример_описания rows="3"></пример_описания>
+  <авторы_описания></авторы_описания>
+</описание_вида>
+----------------------------------------------------------------------
+
+
+* Структура программы DbReader
+
+Программа DbReader написана на языке программирования Java и состоит
+из следующих основных классов:
+
+ru.karrc.dbreader.DbReader: cодержит точку входа программы. Загружает
+конфигурацию, устанавливает соединение с источником данных с помощью
+библиотеки JDBC, запускает обработку основного шаблона.
+
+ru.karrc.dbreader.TemplateParser: осуществляет разбор шаблона,
+обработку инструкций и вызов функций.
+
+ru.karrc.dbreader.Function: абстрактный класс. Реализации всех функций
+DbReader наследуют этот класс.
+
+ru.karrc.dbreader.FunctionDataParser: обработчик данных
+функции. Посволяет получить список аргументов и текст, передаваемый
+функции. При необходимости вызывает рекурсивную обработку данных с
+помощью TemplateParser.
+
+ru.karrc.dbreader.Functions: содержит реализации функций в виде
+анонимных классов, наследующих Function.
+
+ru.karrc.dbreader.TemplateParser: исключение, выбрасываемое в случае
+возникновения ошибки при обработке шаблона.
+
+Кроме того, реализовано несколько впомогательных классов, которые
+используются в реализациях функций системы.
+
+
+* Метрики реализованной программной системы.
+
+Количество файлов исходного кода: 8,
+Количество Java-классов (включая анонимные): 27,
+Общее количество строк исходного текста: 1311,
+Количество строк кода (исключая пустые строки и комментарии): 812,
+Количество строк комментариев: 180.
+
+
+* Заключение
+
+Автором была разработана программная система DbReader, которая
+является расширяемым и универсальным средством для представления
+информации из баз данных в текстовых форматах. С помощью системы
+DbReader база данных "Местообитания Восточной Фенноскандии" была
+переведена в XML-формат электронной библиотеки КарНЦ РАН.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dist/biotopes/doc/readme.html	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,86 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+          "DTD/xhtml1-transitional.dtd">
+
+<html>
+
+<head>
+  <title>DbReader</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=koi8-r" />
+</head>
+
+<body>
+<h1>DbReader</h1>
+
+<h2>Установка программы</h2>
+<ol>
+<li>Распаковать архив.</li>
+
+<li>Установить источник данных:<br />
+в Windows 2000: Панель управления/Администрирование/Источники данных (ODBC), добавить biotopes-data.mdb.</li>
+
+<li>Настроить программу:<br />
+В файле dbreader.properties указать имя, присвоенное источнику данных:<br />
+    <code>resource: jdbc:odbc:<em>имя</em></code><br />
+и путь к каталогу, относительно которого программа будет искать изображения (содержащий biotopephotos и maps):<br />
+    <code>resource_base: C:\\biotopes</code><br />
+(символ '\' необходимо дублировать)</li>
+
+<li>Запуск программы:<br />
+<code>dbreader.bat</code> или<br />
+<code>java -jar dbreader.jar</code>.<br />
+Для запуска необходима Java Runtime Environment (JRE) 5.0, можно скачать с <a href="http://java.sun.com/j2se/1.5.0/download.jsp">http://java.sun.com/j2se/1.5.0/download.jsp</a>. Возможно, будет работать и со старыми версиями JRE - не проверял.
+
+Все значения файла конфигурации можно переопределять при запуске программы:<br />
+команда <code>java -Dru.karrc.dbreader.<em>ключ</em>=<em>значение</em> -jar dbreader.jar</code><br />
+(например: <code>java -Dru.karrc.dbreader.log=dbreader.log -jar dbreader.jar</code>)</li>
+</ol>
+
+<h2>Работа программы</h2>
+
+<p>Программа выполняет запрос biotope.sql (или другой, указанный в dbreader.properties), после чего обрабатывает файл biotope.template, заменяя найденные в нем инструкции на результаты их выполнения.</p>
+<p>Формат инструкций: <code>&lt;%<em>список_функций</em>[:|\]<em>данные</em>%&gt;</code>, <em>список функций</em> - имена функций разделенные пробелом, список может быть пустым. Если указано несколько функций, они выполняются начиная с последней.</p>
+
+<h3>Разделители между списком функций и данными</h3>
+
+<ul>
+  <li><code>:</code> - данные будут обрабатываться рекурсивно.</li>
+  <li><code>\</code> - данные будут переданы функции без обработки. Можно использовать для экранирования комбинаций символов <code>&lt;%</code> и <code>%&gt;</code> (<code>&lt;%\&lt;%%&gt;</code> и <code>&lt;%\%%&gt;&gt;</code>).</li>
+</ul>
+
+<h3>Функции</h3>
+
+<ul>
+  <li><code>&lt;%get:<em>имя</em>%&gt;</code><br />
+  Заменяется на значение встроенной переменной или колонки запроса.</li>
+
+  <li><code>&lt;%optional:<em>текст</em>%&gt;</code><br />
+  Заменяется на <em>текст</em>, если в нем найдены инструкции, заменившиеся на непустые строки, иначе заменяется на пустую строку.</li>
+
+  <li><code>&lt;%escape:<em>текст</em>%&gt;</code><br />
+  Заменяет в тексте "опасные" символы (таблица замен задается в файле конфигурации).</li>
+
+  <li><code>&lt;%invoke:<em>имя</em>; <em>парам1</em> <em>парам2</em> ... <em>парамN</em>%&gt;</code><br />
+  Выполняет запрос с параметрами <em>имя</em>.sql и заменяется на результаты обработки шаблона <em>имя</em>.template. Значения параметров подставляются в запрос вместо символа '?'.</li>
+
+  <li><code>&lt;%image:<em>исх_файл</em> <em>кон_файл</em> <em>формат</em> [<em>макс_ширина</em>] [<em>макс_высота</em>]%&gt;</code><br />
+  Загружает изображение из файла <em>исх_файл</em> (путь определяется относительно конфигурационного параметра "resource_base") и преобразует его в указанный формат, сохраняя результат в <em>кон_файл</em>. Если заданы максимальная высота и ширина, большие изображения будут уменьшены.
+При успешном выполнении, инструкция <code>&lt;%image:...%&gt;</code> будет заменена на значение <em>кон_файл</em>, иначе - на пустую строку.</li>
+
+  <li><code>&lt;%copy:<em>исх_файл</em> <em>кон_файл</em>%&gt;</code><br />
+  Копирует файл <em>исх_файл</em> (путь определяется относительно конфигурационного параметра "resource_base") в <em>кон_файл</em>. При успешном выполнении, инструкция <code>&lt;%copy:...%&gt;</code> будет заменена на значение <em>кон_файл</em>, иначе - на пустую строку.</li>
+</ul>
+
+<h3>Встроенные переменные</h3>
+
+<p><code>NUMBER</code> - порядковый номер строки результата выполнения запроса.
+Чтобы обратиться к переменной запроса, из которого обрабатываемый шаблон был вызван с помощью функции invoke, перед именем переменной нужно добавить <code>SUPER.</code> (например: <code>SUPER.NUMBER</code>).</p>
+
+<h3>Пример</h3>
+
+<p>Следующая инструкция получит значение из столбца name и заменит в нем специальные символы:</p>
+<p><code>&lt;%escape get:name%&gt;</code></p>
+
+<p align="right"><i><a href="mailto:kryshen@cs.karelia.ru">Михаил Крышень</a></i></p>
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dist/biotopes/doc/readme.txt	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,63 @@
+text/plain; charset=UTF-8
+
+пёя│я┌п╟п╫п╬п╡п╨п╟ п©я─п╬пЁя─п╟п╪п╪я▀
+-------------------
+
+1. п═п╟я│п©п╟п╨п╬п╡п╟я┌я▄ п╟я─я┘п╦п╡.
+
+2. пёя│я┌п╟п╫п╬п╡п╦я┌я▄ п╦я│я┌п╬я┤п╫п╦п╨ п╢п╟п╫п╫я▀я┘:
+п╡ Windows 2000: п÷п╟п╫п╣п╩я▄ я┐п©я─п╟п╡п╩п╣п╫п╦я▐/п░п╢п╪п╦п╫п╦я│я┌я─п╦я─п╬п╡п╟п╫п╦п╣/п≤я│я┌п╬я┤п╫п╦п╨п╦ п╢п╟п╫п╫я▀я┘ (ODBC), п╢п╬п╠п╟п╡п╦я┌я▄ biotopes.mdb.
+
+3. п²п╟я│я┌я─п╬п╦я┌я▄ п©я─п╬пЁя─п╟п╪п╪я┐:
+п▓ я└п╟п╧п╩п╣ dbreader.properties я┐п╨п╟п╥п╟я┌я▄ п╦п╪я▐, п©я─п╦я│п╡п╬п╣п╫п╫п╬п╣ п╦я│я┌п╬я┤п╫п╦п╨я┐ п╢п╟п╫п╫я▀я┘:
+    "resource: jdbc:odbc:<п╦п╪я▐>"
+п╦ п©я┐я┌я▄ п╨ п╨п╟я┌п╟п╩п╬пЁя┐, п╬я┌п╫п╬я│п╦я┌п╣п╩я▄п╫п╬ п╨п╬я┌п╬я─п╬пЁп╬ п©я─п╬пЁя─п╟п╪п╪п╟ п╠я┐п╢п╣я┌ п╦я│п╨п╟я┌я▄ п╦п╥п╬п╠я─п╟п╤п╣п╫п╦я▐ (я│п╬п╢п╣я─п╤п╟я┴п╦п╧ biotopephotos п╦ maps):
+    "resource_base: C:\\biotopes"
+(я│п╦п╪п╡п╬п╩ '\' п╫п╣п╬п╠я┘п╬п╢п╦п╪п╬ п╢я┐п╠п╩п╦я─п╬п╡п╟я┌я▄)
+
+4. п≈п╟п©я┐я│п╨ п©я─п╬пЁя─п╟п╪п╪я▀: dbreader.bat п╦п╩п╦ "java -jar dbreader.jar"
+п■п╩я▐ п╥п╟п©я┐я│п╨п╟ п╫п╣п╬п╠я┘п╬п╢п╦п╪п╟ Java Runtime Environment (JRE) 5.0, п╪п╬п╤п╫п╬ я│п╨п╟я┤п╟я┌я▄ я│ http://java.sun.com/j2se/1.5.0/download.jsp. п▓п╬п╥п╪п╬п╤п╫п╬, п╠я┐п╢п╣я┌ я─п╟п╠п╬я┌п╟я┌я▄ п╦ я│п╬ я│я┌п╟я─я▀п╪п╦ п╡п╣я─я│п╦я▐п╪п╦ JRE - п╫п╣ п©я─п╬п╡п╣я─я▐п╩.
+
+п▓я│п╣ п╥п╫п╟я┤п╣п╫п╦я▐ я└п╟п╧п╩п╟ п╨п╬п╫я└п╦пЁя┐я─п╟я├п╦п╦ п╪п╬п╤п╫п╬ п©п╣я─п╣п╬п©я─п╣п╢п╣п╩я▐я┌я▄ п©я─п╦ п╥п╟п©я┐я│п╨п╣ п©я─п╬пЁя─п╟п╪п╪я▀:
+    п╨п╬п╪п╟п╫п╢п╟ "java -Dru.karrc.dbreader.<п╨п╩я▌я┤>=<п╥п╫п╟я┤п╣п╫п╦п╣> -jar dbreader.jar"
+(п╫п╟п©я─п╦п╪п╣я─: "java -Dru.karrc.dbreader.log=dbreader.log -jar dbreader.jar")
+
+
+п═п╟п╠п╬я┌п╟ п©я─п╬пЁя─п╟п╪п╪я▀
+----------------
+
+п÷я─п╬пЁя─п╟п╪п╪п╟ п╡я▀п©п╬п╩п╫я▐п╣я┌ п╥п╟п©я─п╬я│ biotope.sql (п╦п╩п╦ п╢я─я┐пЁп╬п╧, я┐п╨п╟п╥п╟п╫п╫я▀п╧ п╡ dbreader.properties), п©п╬я│п╩п╣ я┤п╣пЁп╬ п╬п╠я─п╟п╠п╟я┌я▀п╡п╟п╣я┌ я└п╟п╧п╩ biotope.template, п╥п╟п╪п╣п╫я▐я▐ п╫п╟п╧п╢п╣п╫п╫я▀п╣ п╡ п╫п╣п╪ п╦п╫я│я┌я─я┐п╨я├п╦п╦ п╫п╟ я─п╣п╥я┐п╩я▄я┌п╟я┌я▀ п╦я┘ п╡я▀п©п╬п╩п╫п╣п╫п╦я▐.
+
+п╓п╬я─п╪п╟я┌ п╦п╫я│я┌я─я┐п╨я├п╦п╧: "<%я│п©п╦я│п╬п╨_я└я┐п╫п╨я├п╦п╧[:|\]п╢п╟п╫п╫я▀п╣%>", я│п©п╦я│п╬п╨ я└я┐п╫п╨я├п╦п╧ - п╦п╪п╣п╫п╟ я└я┐п╫п╨я├п╦п╧ я─п╟п╥п╢п╣п╩п╣п╫п╫я▀п╣ п©я─п╬п╠п╣п╩п╬п╪, я│п©п╦я│п╬п╨ п╪п╬п╤п╣я┌ п╠я▀я┌я▄ п©я┐я│я┌я▀п╪. п∙я│п╩п╦ я┐п╨п╟п╥п╟п╫п╬ п╫п╣я│п╨п╬п╩я▄п╨п╬ я└я┐п╫п╨я├п╦п╧, п╬п╫п╦ п╡я▀п©п╬п╩п╫я▐я▌я┌я│я▐ п╫п╟я┤п╦п╫п╟я▐ я│ п©п╬я│п╩п╣п╢п╫п╣п╧.
+
+п═п╟п╥п╢п╣п╩п╦я┌п╣п╩п╦ п╪п╣п╤п╢я┐ я│п©п╦я│п╨п╬п╪ я└я┐п╫п╨я├п╦п╧ п╦ п╢п╟п╫п╫я▀п╪п╦:
+':' - п╢п╟п╫п╫я▀п╣ п╠я┐п╢я┐я┌ п╬п╠я─п╟п╠п╟я┌я▀п╡п╟я┌я▄я│я▐ я─п╣п╨я┐я─я│п╦п╡п╫п╬.
+'\' - п╢п╟п╫п╫я▀п╣ п╠я┐п╢я┐я┌ п©п╣я─п╣п╢п╟п╫я▀ я└я┐п╫п╨я├п╦п╦ п╠п╣п╥ п╬п╠я─п╟п╠п╬я┌п╨п╦. п°п╬п╤п╫п╬ п╦я│п©п╬п╩я▄п╥п╬п╡п╟я┌я▄ п╢п╩я▐ я█п╨я─п╟п╫п╦я─п╬п╡п╟п╫п╦я▐ п╨п╬п╪п╠п╦п╫п╟я├п╦п╧ я│п╦п╪п╡п╬п╩п╬п╡ "<%" п╦ "%>" ("<%\<%%>" п╦ "<%\%%>>").
+
+п╓я┐п╫п╨я├п╦п╦:
+<%get:<п╦п╪я▐>%> - п╥п╟п╪п╣п╫я▐п╣я┌я│я▐ п╫п╟ п╥п╫п╟я┤п╣п╫п╦п╣ п╡я│я┌я─п╬п╣п╫п╫п╬п╧ п©п╣я─п╣п╪п╣п╫п╫п╬п╧ п╦п╩п╦ п╨п╬п╩п╬п╫п╨п╦ п╥п╟п©я─п╬я│п╟.
+
+<%optional:<я┌п╣п╨я│я┌>%>
+п≈п╟п╪п╣п╫я▐п╣я┌я│я▐ п╫п╟ <я┌п╣п╨я│я┌>, п╣я│п╩п╦ п╡ п╫п╣п╪ п╫п╟п╧п╢п╣п╫я▀ п╦п╫я│я┌я─я┐п╨я├п╦п╦, п╥п╟п╪п╣п╫п╦п╡я┬п╦п╣я│я▐ п╫п╟ п╫п╣п©я┐я│я┌я▀п╣ я│я┌я─п╬п╨п╦, п╦п╫п╟я┤п╣ п╥п╟п╪п╣п╫я▐п╣я┌я│я▐ п╫п╟ п©я┐я│я┌я┐я▌ я│я┌я─п╬п╨я┐.
+
+<%escape:<я┌п╣п╨я│я┌>%>
+п≈п╟п╪п╣п╫я▐п╣я┌ п╡ я┌п╣п╨я│я┌п╣ "п╬п©п╟я│п╫я▀п╣" я│п╦п╪п╡п╬п╩я▀ (я┌п╟п╠п╩п╦я├п╟ п╥п╟п╪п╣п╫ п╥п╟п╢п╟п╣я┌я│я▐ п╡ я└п╟п╧п╩п╣ п╨п╬п╫я└п╦пЁя┐я─п╟я├п╦п╦).
+
+<%invoke:<п╦п╪я▐> <п©п╟я─п╟п╪1> <п©п╟я─п╟п╪2> ... <п©п╟я─п╟п╪N>%>
+п▓я▀п©п╬п╩п╫я▐п╣я┌ п╥п╟п©я─п╬я│ я│ п©п╟я─п╟п╪п╣я┌я─п╟п╪п╦ <п╦п╪я▐>.sql п╦ п╥п╟п╪п╣п╫я▐п╣я┌я│я▐ п╫п╟ я─п╣п╥я┐п╩я▄я┌п╟я┌я▀ п╬п╠я─п╟п╠п╬я┌п╨п╦ я┬п╟п╠п╩п╬п╫п╟ <п╦п╪я▐>.template. п≈п╫п╟я┤п╣п╫п╦я▐ п©п╟я─п╟п╪п╣я┌я─п╬п╡ п©п╬п╢я│я┌п╟п╡п╩я▐я▌я┌я│я▐ п╡ п╥п╟п©я─п╬я│ п╡п╪п╣я│я┌п╬ я│п╦п╪п╡п╬п╩п╟ '?'.
+
+<%image:<п╦я│я┘_я└п╟п╧п╩> <п╨п╬п╫_я└п╟п╧п╩> <я└п╬я─п╪п╟я┌> [<п╪п╟п╨я│_я┬п╦я─п╦п╫п╟>] [<п╪п╟п╨я│_п╡я▀я│п╬я┌п╟>]%>
+п≈п╟пЁя─я┐п╤п╟п╣я┌ п╦п╥п╬п╠я─п╟п╤п╣п╫п╦п╣ п╦п╥ я└п╟п╧п╩п╟ <п╦я│я┘_я└п╟п╧п╩> (п©я┐я┌я▄ п╬п©я─п╣п╢п╣п╩я▐п╣я┌я│я▐ п╬я┌п╫п╬я│п╦я┌п╣п╩я▄п╫п╬ п╨п╬п╫я└п╦пЁя┐я─п╟я├п╦п╬п╫п╫п╬пЁп╬ п©п╟я─п╟п╪п╣я┌я─п╟ "resource_base") п╦ п©я─п╣п╬п╠я─п╟п╥я┐п╣я┌ п╣пЁп╬ п╡ я┐п╨п╟п╥п╟п╫п╫я▀п╧ я└п╬я─п╪п╟я┌, я│п╬я┘я─п╟п╫я▐я▐ я─п╣п╥я┐п╩я▄я┌п╟я┌ п╡ <п╨п╬п╫_я└п╟п╧п╩>. п∙я│п╩п╦ п╥п╟п╢п╟п╫я▀ п╪п╟п╨я│п╦п╪п╟п╩я▄п╫п╟я▐ п╡я▀я│п╬я┌п╟ п╦ я┬п╦я─п╦п╫п╟, п╠п╬п╩я▄я┬п╦п╣ п╦п╥п╬п╠я─п╟п╤п╣п╫п╦я▐ п╠я┐п╢я┐я┌ я┐п╪п╣п╫я▄я┬п╣п╫я▀.
+п÷я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦, п╦п╫я│я┌я─я┐п╨я├п╦я▐ "<%!image ...%>" п╠я┐п╢п╣я┌ п╥п╟п╪п╣п╫п╣п╫п╟ п╫п╟ п╥п╫п╟я┤п╣п╫п╦п╣ <п╨п╬п╫_я└п╟п╧п╩>, п╦п╫п╟я┤п╣ - п╫п╟ п©я┐я│я┌я┐я▌ я│я┌я─п╬п╨я┐.
+
+<%copy:<п╦я│я┘_я└п╟п╧п╩> <п╨п╬п╫_я└п╟п╧п╩>%>
+п п╬п©п╦я─я┐п╣я┌ я└п╟п╧п╩ <п╦я│я┘_я└п╟п╧п╩> (п©я┐я┌я▄ п╬п©я─п╣п╢п╣п╩я▐п╣я┌я│я▐ п╬я┌п╫п╬я│п╦я┌п╣п╩я▄п╫п╬ п╨п╬п╫я└п╦пЁя┐я─п╟я├п╦п╬п╫п╫п╬пЁп╬ п©п╟я─п╟п╪п╣я┌я─п╟ "resource_base").
+п÷я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦, п╦п╫я│я┌я─я┐п╨я├п╦я▐ "<%!copy ...%>" п╠я┐п╢п╣я┌ п╥п╟п╪п╣п╫п╣п╫п╟ п╫п╟ п╥п╫п╟я┤п╣п╫п╦п╣ <п╨п╬п╫_я└п╟п╧п╩>, п╦п╫п╟я┤п╣ - п╫п╟ п©я┐я│я┌я┐я▌ я│я┌я─п╬п╨я┐.
+
+п▓я│я┌я─п╬п╣п╫п╫я▀п╣ п©п╣я─п╣п╪п╣п╫п╫я▀п╣:
+NUMBER - п©п╬я─я▐п╢п╨п╬п╡я▀п╧ п╫п╬п╪п╣я─ я│я┌я─п╬п╨п╦ я─п╣п╥я┐п╩я▄я┌п╟я┌п╟ п╡я▀п©п╬п╩п╫п╣п╫п╦я▐ п╥п╟п©я─п╬я│п╟.
+п╖я┌п╬п╠я▀ п╬п╠я─п╟я┌п╦я┌я▄я│я▐ п╨ п©п╣я─п╣п╪п╣п╫п╫п╬п╧ п╥п╟п©я─п╬я│п╟, п╦п╥ п╨п╬я┌п╬я─п╬пЁп╬ п╬п╠я─п╟п╠п╟я┌я▀п╡п╟п╣п╪я▀п╧ я┬п╟п╠п╩п╬п╫ п╠я▀п╩ п╡я▀п╥п╡п╟п╫ я│ п©п╬п╪п╬я┴я▄я▌ я└я┐п╫п╨я├п╦п╦ invoke, п©п╣я─п╣п╢ п╦п╪п╣п╫п╣п╪ п©п╣я─п╣п╪п╣п╫п╫п╬п╧ п╫я┐п╤п╫п╬ п╢п╬п╠п╟п╡п╦я┌я▄ "SUPER." (п╫п╟п©я─п╦п╪п╣я─: "SUPER.NUMBER").
+
+п÷я─п╦п╪п╣я─:
+п║п╩п╣п╢я┐я▌я┴п╟я▐ п╦п╫я│я┌я─я┐п╨я├п╦я▐ п©п╬п╩я┐я┤п╦я┌ п╥п╫п╟я┤п╣п╫п╦п╣ п╦п╥ я│я┌п╬п╩п╠я├п╟ name п╦ п╥п╟п╪п╣п╫п╦я┌ п╡ п╫п╣п╪ я│п©п╣я├п╦п╟п╩я▄п╫я▀п╣ я│п╦п╪п╡п╬п╩я▀:
+<%escape get:name%>
--- a/dist/biotopes/main.template	Tue May 16 18:04:09 2006 +0400
+++ b/dist/biotopes/main.template	Thu Dec 14 23:22:05 2006 +0300
@@ -4,7 +4,7 @@
   <%prepare:class_sql <%read\class.sql%>%>
   <%prepare:plant_sql <%read\plant.sql%>%>
 
-  <%query:biotope_sql [%\<%write:<%get\code%>.xml <%include\biotope.template%>%> %]%>
+  <%query:biotope_sql [%\<%write:<%db\code%>.xml <%include\biotope.template%>%> %]%>
 
   <%write:classes.xml <%include\classes.template%>%>
   <%write:plants.xml <%include\plants.template%>%>
--- a/dist/biotopes/photo.template	Tue May 16 18:04:09 2006 +0400
+++ b/dist/biotopes/photo.template	Thu Dec 14 23:22:05 2006 +0300
@@ -1,3 +1,3 @@
-    <ДчБчсЮпДьО num="<%xml_escape get\NUMBER%>" <%optional:прБчЮ="<%xml_escape get\authphoto%>" %>text="" 
-                file="<%xml_escape image:biotopephotos/<%get\photo%> images/<%get\SUPER.code%>_<%get\NUMBER%>.jpg jpg 300 300%>" 
-                big="<%xml_escape copy:biotopephotos/<%get\photo%> images/big/<%get\SUPER.code%>_<%get\NUMBER%>.jpg%>"/>
+    <ДчБчсЮпДьО num="<%number\%>" <%optional:прБчЮ="<%xml_escape db\authphoto%>" %>text="" 
+                file="<%xml_escape image:biotopephotos/<%db\photo%> images/<%super.db\code%>_<%number\%>.jpg jpg 300 300%>" 
+                big="<%xml_escape copy:biotopephotos/<%db\photo%> images/big/<%super.db\code%>_<%number\%>.jpg%>"/>
--- a/dist/biotopes/plants.template	Tue May 16 18:04:09 2006 +0400
+++ b/dist/biotopes/plants.template	Thu Dec 14 23:22:05 2006 +0300
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="ISO-8859-5"?>
 <ЮпАБущьО><%query:plant_sql [%\
-  <!-- rangcode: <%xml_escape get\rangcode%> -->
-  <ЮпАБущьу><%xml_escape get\rusname%></ЮпАБущьу>%]%>
+  <!-- rangcode: <%xml_escape db\rangcode%> -->
+  <ЮпАБущьу><%xml_escape db\rusname%></ЮпАБущьу>%]%>
 </ЮпАБущьО>
--- a/dist/readme.html	Tue May 16 18:04:09 2006 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
-          "DTD/xhtml1-transitional.dtd">
-
-<html>
-
-<head>
-  <title>DbReader</title>
-  <meta http-equiv="Content-Type" content="text/html; charset=koi8-r" />
-</head>
-
-<body>
-<h1>DbReader</h1>
-
-<h2>Установка программы</h2>
-<ol>
-<li>Распаковать архив.</li>
-
-<li>Установить источник данных:<br />
-в Windows 2000: Панель управления/Администрирование/Источники данных (ODBC), добавить biotopes-data.mdb.</li>
-
-<li>Настроить программу:<br />
-В файле dbreader.properties указать имя, присвоенное источнику данных:<br />
-    <code>resource: jdbc:odbc:<em>имя</em></code><br />
-и путь к каталогу, относительно которого программа будет искать изображения (содержащий biotopephotos и maps):<br />
-    <code>resource_base: C:\\biotopes</code><br />
-(символ '\' необходимо дублировать)</li>
-
-<li>Запуск программы:<br />
-<code>dbreader.bat</code> или<br />
-<code>java -jar dbreader.jar</code>.<br />
-Для запуска необходима Java Runtime Environment (JRE) 5.0, можно скачать с <a href="http://java.sun.com/j2se/1.5.0/download.jsp">http://java.sun.com/j2se/1.5.0/download.jsp</a>. Возможно, будет работать и со старыми версиями JRE - не проверял.
-
-Все значения файла конфигурации можно переопределять при запуске программы:<br />
-команда <code>java -Dru.karrc.dbreader.<em>ключ</em>=<em>значение</em> -jar dbreader.jar</code><br />
-(например: <code>java -Dru.karrc.dbreader.log=dbreader.log -jar dbreader.jar</code>)</li>
-</ol>
-
-<h2>Работа программы</h2>
-
-<p>Программа выполняет запрос biotope.sql (или другой, указанный в dbreader.properties), после чего обрабатывает файл biotope.template, заменяя найденные в нем инструкции на результаты их выполнения.</p>
-<p>Формат инструкций: <code>&lt;%<em>список_функций</em>[:|\]<em>данные</em>%&gt;</code>, <em>список функций</em> - имена функций разделенные пробелом, список может быть пустым. Если указано несколько функций, они выполняются начиная с последней.</p>
-
-<h3>Разделители между списком функций и данными</h3>
-
-<ul>
-  <li><code>:</code> - данные будут обрабатываться рекурсивно.</li>
-  <li><code>\</code> - данные будут переданы функции без обработки. Можно использовать для экранирования комбинаций символов <code>&lt;%</code> и <code>%&gt;</code> (<code>&lt;%\&lt;%%&gt;</code> и <code>&lt;%\%%&gt;&gt;</code>).</li>
-</ul>
-
-<h3>Функции</h3>
-
-<ul>
-  <li><code>&lt;%get:<em>имя</em>%&gt;</code><br />
-  Заменяется на значение встроенной переменной или колонки запроса.</li>
-
-  <li><code>&lt;%optional:<em>текст</em>%&gt;</code><br />
-  Заменяется на <em>текст</em>, если в нем найдены инструкции, заменившиеся на непустые строки, иначе заменяется на пустую строку.</li>
-
-  <li><code>&lt;%escape:<em>текст</em>%&gt;</code><br />
-  Заменяет в тексте "опасные" символы (таблица замен задается в файле конфигурации).</li>
-
-  <li><code>&lt;%invoke:<em>имя</em>; <em>парам1</em> <em>парам2</em> ... <em>парамN</em>%&gt;</code><br />
-  Выполняет запрос с параметрами <em>имя</em>.sql и заменяется на результаты обработки шаблона <em>имя</em>.template. Значения параметров подставляются в запрос вместо символа '?'.</li>
-
-  <li><code>&lt;%image:<em>исх_файл</em> <em>кон_файл</em> <em>формат</em> [<em>макс_ширина</em>] [<em>макс_высота</em>]%&gt;</code><br />
-  Загружает изображение из файла <em>исх_файл</em> (путь определяется относительно конфигурационного параметра "resource_base") и преобразует его в указанный формат, сохраняя результат в <em>кон_файл</em>. Если заданы максимальная высота и ширина, большие изображения будут уменьшены.
-При успешном выполнении, инструкция <code>&lt;%image:...%&gt;</code> будет заменена на значение <em>кон_файл</em>, иначе - на пустую строку.</li>
-
-  <li><code>&lt;%copy:<em>исх_файл</em> <em>кон_файл</em>%&gt;</code><br />
-  Копирует файл <em>исх_файл</em> (путь определяется относительно конфигурационного параметра "resource_base") в <em>кон_файл</em>. При успешном выполнении, инструкция <code>&lt;%copy:...%&gt;</code> будет заменена на значение <em>кон_файл</em>, иначе - на пустую строку.</li>
-</ul>
-
-<h3>Встроенные переменные</h3>
-
-<p><code>NUMBER</code> - порядковый номер строки результата выполнения запроса.
-Чтобы обратиться к переменной запроса, из которого обрабатываемый шаблон был вызван с помощью функции invoke, перед именем переменной нужно добавить <code>SUPER.</code> (например: <code>SUPER.NUMBER</code>).</p>
-
-<h3>Пример</h3>
-
-<p>Следующая инструкция получит значение из столбца name и заменит в нем специальные символы:</p>
-<p><code>&lt;%escape get:name%&gt;</code></p>
-
-<p align="right"><i><a href="mailto:kryshen@cs.karelia.ru">Михаил Крышень</a></i></p>
-
-</body>
-</html>
--- a/dist/readme.txt	Tue May 16 18:04:09 2006 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-text/plain; charset=UTF-8
-
-пёя│я┌п╟п╫п╬п╡п╨п╟ п©я─п╬пЁя─п╟п╪п╪я▀
--------------------
-
-1. п═п╟я│п©п╟п╨п╬п╡п╟я┌я▄ п╟я─я┘п╦п╡.
-
-2. пёя│я┌п╟п╫п╬п╡п╦я┌я▄ п╦я│я┌п╬я┤п╫п╦п╨ п╢п╟п╫п╫я▀я┘:
-п╡ Windows 2000: п÷п╟п╫п╣п╩я▄ я┐п©я─п╟п╡п╩п╣п╫п╦я▐/п░п╢п╪п╦п╫п╦я│я┌я─п╦я─п╬п╡п╟п╫п╦п╣/п≤я│я┌п╬я┤п╫п╦п╨п╦ п╢п╟п╫п╫я▀я┘ (ODBC), п╢п╬п╠п╟п╡п╦я┌я▄ biotopes.mdb.
-
-3. п²п╟я│я┌я─п╬п╦я┌я▄ п©я─п╬пЁя─п╟п╪п╪я┐:
-п▓ я└п╟п╧п╩п╣ dbreader.properties я┐п╨п╟п╥п╟я┌я▄ п╦п╪я▐, п©я─п╦я│п╡п╬п╣п╫п╫п╬п╣ п╦я│я┌п╬я┤п╫п╦п╨я┐ п╢п╟п╫п╫я▀я┘:
-    "resource: jdbc:odbc:<п╦п╪я▐>"
-п╦ п©я┐я┌я▄ п╨ п╨п╟я┌п╟п╩п╬пЁя┐, п╬я┌п╫п╬я│п╦я┌п╣п╩я▄п╫п╬ п╨п╬я┌п╬я─п╬пЁп╬ п©я─п╬пЁя─п╟п╪п╪п╟ п╠я┐п╢п╣я┌ п╦я│п╨п╟я┌я▄ п╦п╥п╬п╠я─п╟п╤п╣п╫п╦я▐ (я│п╬п╢п╣я─п╤п╟я┴п╦п╧ biotopephotos п╦ maps):
-    "resource_base: C:\\biotopes"
-(я│п╦п╪п╡п╬п╩ '\' п╫п╣п╬п╠я┘п╬п╢п╦п╪п╬ п╢я┐п╠п╩п╦я─п╬п╡п╟я┌я▄)
-
-4. п≈п╟п©я┐я│п╨ п©я─п╬пЁя─п╟п╪п╪я▀: dbreader.bat п╦п╩п╦ "java -jar dbreader.jar"
-п■п╩я▐ п╥п╟п©я┐я│п╨п╟ п╫п╣п╬п╠я┘п╬п╢п╦п╪п╟ Java Runtime Environment (JRE) 5.0, п╪п╬п╤п╫п╬ я│п╨п╟я┤п╟я┌я▄ я│ http://java.sun.com/j2se/1.5.0/download.jsp. п▓п╬п╥п╪п╬п╤п╫п╬, п╠я┐п╢п╣я┌ я─п╟п╠п╬я┌п╟я┌я▄ п╦ я│п╬ я│я┌п╟я─я▀п╪п╦ п╡п╣я─я│п╦я▐п╪п╦ JRE - п╫п╣ п©я─п╬п╡п╣я─я▐п╩.
-
-п▓я│п╣ п╥п╫п╟я┤п╣п╫п╦я▐ я└п╟п╧п╩п╟ п╨п╬п╫я└п╦пЁя┐я─п╟я├п╦п╦ п╪п╬п╤п╫п╬ п©п╣я─п╣п╬п©я─п╣п╢п╣п╩я▐я┌я▄ п©я─п╦ п╥п╟п©я┐я│п╨п╣ п©я─п╬пЁя─п╟п╪п╪я▀:
-    п╨п╬п╪п╟п╫п╢п╟ "java -Dru.karrc.dbreader.<п╨п╩я▌я┤>=<п╥п╫п╟я┤п╣п╫п╦п╣> -jar dbreader.jar"
-(п╫п╟п©я─п╦п╪п╣я─: "java -Dru.karrc.dbreader.log=dbreader.log -jar dbreader.jar")
-
-
-п═п╟п╠п╬я┌п╟ п©я─п╬пЁя─п╟п╪п╪я▀
-----------------
-
-п÷я─п╬пЁя─п╟п╪п╪п╟ п╡я▀п©п╬п╩п╫я▐п╣я┌ п╥п╟п©я─п╬я│ biotope.sql (п╦п╩п╦ п╢я─я┐пЁп╬п╧, я┐п╨п╟п╥п╟п╫п╫я▀п╧ п╡ dbreader.properties), п©п╬я│п╩п╣ я┤п╣пЁп╬ п╬п╠я─п╟п╠п╟я┌я▀п╡п╟п╣я┌ я└п╟п╧п╩ biotope.template, п╥п╟п╪п╣п╫я▐я▐ п╫п╟п╧п╢п╣п╫п╫я▀п╣ п╡ п╫п╣п╪ п╦п╫я│я┌я─я┐п╨я├п╦п╦ п╫п╟ я─п╣п╥я┐п╩я▄я┌п╟я┌я▀ п╦я┘ п╡я▀п©п╬п╩п╫п╣п╫п╦я▐.
-
-п╓п╬я─п╪п╟я┌ п╦п╫я│я┌я─я┐п╨я├п╦п╧: "<%я│п©п╦я│п╬п╨_я└я┐п╫п╨я├п╦п╧[:|\]п╢п╟п╫п╫я▀п╣%>", я│п©п╦я│п╬п╨ я└я┐п╫п╨я├п╦п╧ - п╦п╪п╣п╫п╟ я└я┐п╫п╨я├п╦п╧ я─п╟п╥п╢п╣п╩п╣п╫п╫я▀п╣ п©я─п╬п╠п╣п╩п╬п╪, я│п©п╦я│п╬п╨ п╪п╬п╤п╣я┌ п╠я▀я┌я▄ п©я┐я│я┌я▀п╪. п∙я│п╩п╦ я┐п╨п╟п╥п╟п╫п╬ п╫п╣я│п╨п╬п╩я▄п╨п╬ я└я┐п╫п╨я├п╦п╧, п╬п╫п╦ п╡я▀п©п╬п╩п╫я▐я▌я┌я│я▐ п╫п╟я┤п╦п╫п╟я▐ я│ п©п╬я│п╩п╣п╢п╫п╣п╧.
-
-п═п╟п╥п╢п╣п╩п╦я┌п╣п╩п╦ п╪п╣п╤п╢я┐ я│п©п╦я│п╨п╬п╪ я└я┐п╫п╨я├п╦п╧ п╦ п╢п╟п╫п╫я▀п╪п╦:
-':' - п╢п╟п╫п╫я▀п╣ п╠я┐п╢я┐я┌ п╬п╠я─п╟п╠п╟я┌я▀п╡п╟я┌я▄я│я▐ я─п╣п╨я┐я─я│п╦п╡п╫п╬.
-'\' - п╢п╟п╫п╫я▀п╣ п╠я┐п╢я┐я┌ п©п╣я─п╣п╢п╟п╫я▀ я└я┐п╫п╨я├п╦п╦ п╠п╣п╥ п╬п╠я─п╟п╠п╬я┌п╨п╦. п°п╬п╤п╫п╬ п╦я│п©п╬п╩я▄п╥п╬п╡п╟я┌я▄ п╢п╩я▐ я█п╨я─п╟п╫п╦я─п╬п╡п╟п╫п╦я▐ п╨п╬п╪п╠п╦п╫п╟я├п╦п╧ я│п╦п╪п╡п╬п╩п╬п╡ "<%" п╦ "%>" ("<%\<%%>" п╦ "<%\%%>>").
-
-п╓я┐п╫п╨я├п╦п╦:
-<%get:<п╦п╪я▐>%> - п╥п╟п╪п╣п╫я▐п╣я┌я│я▐ п╫п╟ п╥п╫п╟я┤п╣п╫п╦п╣ п╡я│я┌я─п╬п╣п╫п╫п╬п╧ п©п╣я─п╣п╪п╣п╫п╫п╬п╧ п╦п╩п╦ п╨п╬п╩п╬п╫п╨п╦ п╥п╟п©я─п╬я│п╟.
-
-<%optional:<я┌п╣п╨я│я┌>%>
-п≈п╟п╪п╣п╫я▐п╣я┌я│я▐ п╫п╟ <я┌п╣п╨я│я┌>, п╣я│п╩п╦ п╡ п╫п╣п╪ п╫п╟п╧п╢п╣п╫я▀ п╦п╫я│я┌я─я┐п╨я├п╦п╦, п╥п╟п╪п╣п╫п╦п╡я┬п╦п╣я│я▐ п╫п╟ п╫п╣п©я┐я│я┌я▀п╣ я│я┌я─п╬п╨п╦, п╦п╫п╟я┤п╣ п╥п╟п╪п╣п╫я▐п╣я┌я│я▐ п╫п╟ п©я┐я│я┌я┐я▌ я│я┌я─п╬п╨я┐.
-
-<%escape:<я┌п╣п╨я│я┌>%>
-п≈п╟п╪п╣п╫я▐п╣я┌ п╡ я┌п╣п╨я│я┌п╣ "п╬п©п╟я│п╫я▀п╣" я│п╦п╪п╡п╬п╩я▀ (я┌п╟п╠п╩п╦я├п╟ п╥п╟п╪п╣п╫ п╥п╟п╢п╟п╣я┌я│я▐ п╡ я└п╟п╧п╩п╣ п╨п╬п╫я└п╦пЁя┐я─п╟я├п╦п╦).
-
-<%invoke:<п╦п╪я▐> <п©п╟я─п╟п╪1> <п©п╟я─п╟п╪2> ... <п©п╟я─п╟п╪N>%>
-п▓я▀п©п╬п╩п╫я▐п╣я┌ п╥п╟п©я─п╬я│ я│ п©п╟я─п╟п╪п╣я┌я─п╟п╪п╦ <п╦п╪я▐>.sql п╦ п╥п╟п╪п╣п╫я▐п╣я┌я│я▐ п╫п╟ я─п╣п╥я┐п╩я▄я┌п╟я┌я▀ п╬п╠я─п╟п╠п╬я┌п╨п╦ я┬п╟п╠п╩п╬п╫п╟ <п╦п╪я▐>.template. п≈п╫п╟я┤п╣п╫п╦я▐ п©п╟я─п╟п╪п╣я┌я─п╬п╡ п©п╬п╢я│я┌п╟п╡п╩я▐я▌я┌я│я▐ п╡ п╥п╟п©я─п╬я│ п╡п╪п╣я│я┌п╬ я│п╦п╪п╡п╬п╩п╟ '?'.
-
-<%image:<п╦я│я┘_я└п╟п╧п╩> <п╨п╬п╫_я└п╟п╧п╩> <я└п╬я─п╪п╟я┌> [<п╪п╟п╨я│_я┬п╦я─п╦п╫п╟>] [<п╪п╟п╨я│_п╡я▀я│п╬я┌п╟>]%>
-п≈п╟пЁя─я┐п╤п╟п╣я┌ п╦п╥п╬п╠я─п╟п╤п╣п╫п╦п╣ п╦п╥ я└п╟п╧п╩п╟ <п╦я│я┘_я└п╟п╧п╩> (п©я┐я┌я▄ п╬п©я─п╣п╢п╣п╩я▐п╣я┌я│я▐ п╬я┌п╫п╬я│п╦я┌п╣п╩я▄п╫п╬ п╨п╬п╫я└п╦пЁя┐я─п╟я├п╦п╬п╫п╫п╬пЁп╬ п©п╟я─п╟п╪п╣я┌я─п╟ "resource_base") п╦ п©я─п╣п╬п╠я─п╟п╥я┐п╣я┌ п╣пЁп╬ п╡ я┐п╨п╟п╥п╟п╫п╫я▀п╧ я└п╬я─п╪п╟я┌, я│п╬я┘я─п╟п╫я▐я▐ я─п╣п╥я┐п╩я▄я┌п╟я┌ п╡ <п╨п╬п╫_я└п╟п╧п╩>. п∙я│п╩п╦ п╥п╟п╢п╟п╫я▀ п╪п╟п╨я│п╦п╪п╟п╩я▄п╫п╟я▐ п╡я▀я│п╬я┌п╟ п╦ я┬п╦я─п╦п╫п╟, п╠п╬п╩я▄я┬п╦п╣ п╦п╥п╬п╠я─п╟п╤п╣п╫п╦я▐ п╠я┐п╢я┐я┌ я┐п╪п╣п╫я▄я┬п╣п╫я▀.
-п÷я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦, п╦п╫я│я┌я─я┐п╨я├п╦я▐ "<%!image ...%>" п╠я┐п╢п╣я┌ п╥п╟п╪п╣п╫п╣п╫п╟ п╫п╟ п╥п╫п╟я┤п╣п╫п╦п╣ <п╨п╬п╫_я└п╟п╧п╩>, п╦п╫п╟я┤п╣ - п╫п╟ п©я┐я│я┌я┐я▌ я│я┌я─п╬п╨я┐.
-
-<%copy:<п╦я│я┘_я└п╟п╧п╩> <п╨п╬п╫_я└п╟п╧п╩>%>
-п п╬п©п╦я─я┐п╣я┌ я└п╟п╧п╩ <п╦я│я┘_я└п╟п╧п╩> (п©я┐я┌я▄ п╬п©я─п╣п╢п╣п╩я▐п╣я┌я│я▐ п╬я┌п╫п╬я│п╦я┌п╣п╩я▄п╫п╬ п╨п╬п╫я└п╦пЁя┐я─п╟я├п╦п╬п╫п╫п╬пЁп╬ п©п╟я─п╟п╪п╣я┌я─п╟ "resource_base").
-п÷я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦, п╦п╫я│я┌я─я┐п╨я├п╦я▐ "<%!copy ...%>" п╠я┐п╢п╣я┌ п╥п╟п╪п╣п╫п╣п╫п╟ п╫п╟ п╥п╫п╟я┤п╣п╫п╦п╣ <п╨п╬п╫_я└п╟п╧п╩>, п╦п╫п╟я┤п╣ - п╫п╟ п©я┐я│я┌я┐я▌ я│я┌я─п╬п╨я┐.
-
-п▓я│я┌я─п╬п╣п╫п╫я▀п╣ п©п╣я─п╣п╪п╣п╫п╫я▀п╣:
-NUMBER - п©п╬я─я▐п╢п╨п╬п╡я▀п╧ п╫п╬п╪п╣я─ я│я┌я─п╬п╨п╦ я─п╣п╥я┐п╩я▄я┌п╟я┌п╟ п╡я▀п©п╬п╩п╫п╣п╫п╦я▐ п╥п╟п©я─п╬я│п╟.
-п╖я┌п╬п╠я▀ п╬п╠я─п╟я┌п╦я┌я▄я│я▐ п╨ п©п╣я─п╣п╪п╣п╫п╫п╬п╧ п╥п╟п©я─п╬я│п╟, п╦п╥ п╨п╬я┌п╬я─п╬пЁп╬ п╬п╠я─п╟п╠п╟я┌я▀п╡п╟п╣п╪я▀п╧ я┬п╟п╠п╩п╬п╫ п╠я▀п╩ п╡я▀п╥п╡п╟п╫ я│ п©п╬п╪п╬я┴я▄я▌ я└я┐п╫п╨я├п╦п╦ invoke, п©п╣я─п╣п╢ п╦п╪п╣п╫п╣п╪ п©п╣я─п╣п╪п╣п╫п╫п╬п╧ п╫я┐п╤п╫п╬ п╢п╬п╠п╟п╡п╦я┌я▄ "SUPER." (п╫п╟п©я─п╦п╪п╣я─: "SUPER.NUMBER").
-
-п÷я─п╦п╪п╣я─:
-п║п╩п╣п╢я┐я▌я┴п╟я▐ п╦п╫я│я┌я─я┐п╨я├п╦я▐ п©п╬п╩я┐я┤п╦я┌ п╥п╫п╟я┤п╣п╫п╦п╣ п╦п╥ я│я┌п╬п╩п╠я├п╟ name п╦ п╥п╟п╪п╣п╫п╦я┌ п╡ п╫п╣п╪ я│п©п╣я├п╦п╟п╩я▄п╫я▀п╣ я│п╦п╪п╡п╬п╩я▀:
-<%escape get:name%>
--- a/dist/tema	Tue May 16 18:04:09 2006 +0400
+++ b/dist/tema	Thu Dec 14 23:22:05 2006 +0300
@@ -1,3 +1,3 @@
 #!/bin/sh
 
-java -jar tema.jar
+java -jar tema.jar "$@"
--- a/dist/tema.bat	Tue May 16 18:04:09 2006 +0400
+++ b/dist/tema.bat	Thu Dec 14 23:22:05 2006 +0300
@@ -1,1 +1,1 @@
-java -jar tema.jar
+java -jar tema.jar %1
--- a/dist/tema.properties	Tue May 16 18:04:09 2006 +0400
+++ b/dist/tema.properties	Thu Dec 14 23:22:05 2006 +0300
@@ -9,14 +9,14 @@
 main_template     : main.template
 
 # File encodings
-# input_encoding    : ISO-8859-5
-# output_encoding   : ISO-8859-5
+# input_encoding    : UTF-8
+# output_encoding   : UTF-8
 
 # Cache templates
-cache_read        : true
+# cache_read        : true
 
 # Output main_template parsing result to stderr
-#output            : stderr
+# output            : stderr
 
 # File to output error messages (redirect stderr)
-#log               : tema.log
+# log               : tema.log
--- a/doc/article.txt	Tue May 16 18:04:09 2006 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,391 +0,0 @@
-* Преобразование базы данных в XML-формат электронной библиотеки.
-
-Для решения задачи преобразования базы данных "Местообитания Восточной
-Фенноскандии" из формата Microsoft Access в XML-формат электронной
-библиотеки КарНЦ РАН была разработана программная система DbReader.
-
-Программа DbReader является универсальным средством для представления
-информации из баз данных в текстовых форматах. Программа обрабатывает
-заданные шаблоны текстовых файлов и заменяет найденные в них
-инструкции на результаты их выполнения. Различные инструкции позволяют
-формировать SQL-запросы, выполнять их и обрабатывать полученные
-данные.
-
-
-* Формат инструкций:
-
-    <%<список_функций>{:|\}<данные>%>
-  где 
-
-<список_функций> - список имен функций, разделенных пробелами.
-    Может быть пустым.
-<данные> - данные, передаваемые функции.
-
-
-* Формат данных:
-
-    [<список_аргументов>][<текст>]
-  где
-
-<список_аргументов> - список аргументов функции, разделенных пробелами.
-    Может быть пустым.
-<текст> - текст, передаваемый функции без разбиения на аргументы.
-    Может быть пустым. Количество аргументов, после которого следует
-    текст, зависит от функции.
-
-Разделитель между списком функций и данными определяет, как должны
-обрабатываться данные функции:
-: - рекурсивная обработка,
-\ - передать без обработки.
-
-Если в списке функций задано две и более функции, они выполняются,
-начиная с последней, так что каждая функция получает в качестве данных
-результат выполнения следующей функции.
-
-Каждая функция имеет код возврата - целое число. Код возврата
-инструкции - код возврата первой в списке функции. Код возврата,
-получаемый при обработке текста - сумма кодов возврата обработанных
-инструкций.
-
-Вместо скобок '<', '>' можно использовать скобки '[', ']'.
-
-
-* Реализованные функции:
-
-Функция:      set
-Аргументы:    <имя>
-Текст:        <значение>
-Действие:     устанавливает значение переменной <имя>
-Результат:    <имя>
-Код возврата: 1
-
-Функция:      get
-Аргументы:    <имя>
-Результат:    значение переменной <имя>. 
-Код возврата: 1, если значение переменной было установлено, 0 - иначе.
-
-Функция:      prepare
-Аргументы:    <имя>
-Текст:        <запрос>
-Действие:     Подготавливает SQL-запрос <запрос> для выполнения,
-              записывает подготовленный запрос в переменную <имя>.
-Результат:    <имя>
-Код возврата: 1
-
-Функция:      query
-Аргументы:    <имя_запроса> <шаблон> <парам1> ... <парамN>
-Действие:     Выполняет запрос с параметрами, подготовленный с помощью
-              функции prepare. Значения параметров подставляются в
-              запрос вместо символа '?'. Значения полей ответа будут
-              записаны, как переменные шаблона <шаблон>. При обработки
-              шаблона <шаблон> также определяется переменная NUMBER,
-              содержащая номер текущей строки ответа. Переменные,
-              определенные в вызывающем шаблоне, доступны из шаблона
-              <шаблон> по имени SUPER.<имя_переменной>.
-
-Результат:    результат обработки шаблона <шаблон> для каждой строки
-              ответа.
-Код возврата: количество полученных строк ответа.
-
-Функция:      optional
-Текст:        <данные>
-Результат:    <данные>, если при обработке данных был получен
-              код возврата отличный от 0, иначе - пустая строка.
-Код возврата: 1, если результат - пустая строка, 0 - иначе.
-
-Функция:      image
-Аргументы:    <исх_файл> <кон_файл> <формат> [<макс_ширина> [<макс_высота>]]
-Действие:     Загружает изображение из файла <исх_файл> (путь определяется
-              относительно конфигурационного параметра "resource_base") и
-              преобразует его в указанный формат, сохраняя результат в
-              <кон_файл>. Если заданы максимальная высота и ширина, большие
-              изображения будут уменьшены. 
-Результат:    <кон_файл> при успешном выполнении, пустая строка - иначе.
-Код возврата: 1 при успешном выполнении, 0 - иначе.
-
-Функция:      copy
-Аргументы:    <исх_файл> <кон_файл>
-Действие:     Копирует файл <исх_файл> в файл <кон_файл>
-	      (путь <исх_файл> определяется относительно
-              конфигурационного параметра "resource_base").
-Результат:    <кон_файл> при успешном выполнении, пустая строка - иначе.
-Код возврата: 1 при успешном выполнении, 0 - иначе.
-
-Функция:      write
-Аргументы:    <имя_файла>
-Текст:        <данные>
-Действие:     Записывает <данные> в файл <исх_файл>.
-Результат:    <кон_файл> при успешном выполнении, пустая строка - иначе.
-Код возврата: 1 при успешном выполнении, 0 - иначе.
-
-Функция:      read
-Текст:        <имя_файла>
-Действие:     Читает файл <имя_файла>.
-Результат:    прочитанные данные при успешном выполнении, пустая
-              строка - иначе.
-Код возврата: 1 при успешном выполнении, 0 - иначе.
-
-Функция:      include
-Текст:        <имя_файла>
-Действие:     включает шаблон из файла <имя_файла>.
-Результат:    результат обработки шаблона.
-Код возврата: код возврата, полученный при обработке шаблона.
-
-Функция:      !
-Текст:        <данные>
-Действие:     нет.
-Результат:    нет.
-Код возврата: код возврата, полученный при обработке текста данных.
-
-Функция:      replace
-Аргументы:    <стр1> <стр2>
-Текст:        <данные>
-Результат:    данные, с замененными вхождениями подстроки <стр1>
-	      на <стр2>.
-Код возврата: код возврата, полученный при обработке текста данных.
-
-Функция:      xml_escape
-Текст:        <данные>
-Результат:    текст <данные>, в котором  символы
-              '&', '<', '>', '`', '\' заменены на соответствующие
-	      сущности XML.
-Код возврата: код возврата, полученный при обработке текста данных.
-
-Функция:      xml_cdata
-Текст:        <данные>
-Результат:    данные в виде блока XML CDATA.
-Код возврата: код возврата, полученный при обработке текста данных.
-
-Система DbReader расширяема: возможно добавление в систему новых
-функций, реализованных в виде классов на языке Java.
-
-
-* Применение системы DbReader для преобразования базы данных
-  "Местообитания Восточной Фенноскандии" из формата Microsoft Access в
-  XML-формат электронной библиотеки КарНЦ РАН
-
-Для преобразования используются две таблицы базы данных "Местообитания
-Восточной Фенноскандии" (перечислены только используемые поля):
-
-biotopelist (code, rangcode, rusname, engname, descript, geobotdescr,
-             contributors, descrdate)
-
-  code - счетчик, ключевое поле;
-  rangcode - поле в формате ABBCCDDEE, где А, BB, CC, DD, ЕЕ - коды
-             класса, подклассов и растения;
-  rusname, engname, descript, geobotdescr - текстовые поля;
-  descrdate - дата.
-
-photos (biotope, photo, authphoto)
-
-  biotope - число, указывает на code в biotopelist;
-  photo - имя файла фотографии;
-  authphoto - текст.
-
-Требуется сгенерировать xml-файл для каждой записи в biotopelist, файл
-со списком классов, файл со списком растений, полноразмерные и
-уменьшенные версии каждой фотографии.
-
-Для генерации xml-файлов с описаниями биотопов использовался следующий
-шаблон DbReader (файл biotope.template):
-
-----------------------------------------------------------------------
-<?xml version="1.0" encoding="ISO-8859-5"?>
-<!DOCTYPE описание_вида SYSTEM "brief.dtd">
-<!-- rangcode: <%xml_escape get\rangcode%> -->
-<описание_вида show="0">
-  <рисунки display="0">
-    <%optional:<карта_распространения номер="1"
-    file="<%xml_escape image:maps/<%get\rangcode%>.gif maps/<%get\code%>.png png%>"/>%>
-<%query:photo_sql [%\<%include\photo.template%>%] <%get\code%>%>
-  </рисунки>
-  <название><%xml_escape get\rusname%></название>
-  <класс><%xml_escape get\class0%></класс>
-  <подкласс1><%xml_escape get\class1%></подкласс1>
-  <подкласс2><%xml_escape get\class2%></подкласс2>
-  <подкласс3><%xml_escape get\class3%></подкласс3>
-  <краткая_характеристика rows="3"><%xml_cdata get\descript%></краткая_характеристика>
-  <пример_описания rows="3"><%xml_cdata get\geobotdescr%></пример_описания>
-  <авторы_описания><%xml_escape get\contributors%></авторы_описания>
-</описание_вида>
-----------------------------------------------------------------------
-
-Соответствующий SQL-запрос, при выполнении которого устанавливаются
-значения переменных шаблона rangcode, rusname, class0, class1, class2,
-class3, descript, geobotdescr и contributors (файл biotope.sql):
-
-----------------------------------------------------------------------
-SELECT t1.*,
-
-(SELECT t2.rusname FROM biotopelist AS t2
-WHERE Left(t2.rangcode, 1) = Left(t1.rangcode, 1)
-AND Right(t2.rangcode, 8)  = '00000000'
-AND Right(t1.rangcode, 8) <> '00000000') AS class0,
-
-(SELECT t2.rusname FROM biotopelist AS t2
-WHERE Left(t2.rangcode, 3) = Left(t1.rangcode, 3)
-AND Right(t2.rangcode, 6)  = '000000'
-AND Right(t1.rangcode, 6) <> '000000') AS class1,
-
-(SELECT t2.rusname FROM biotopelist AS t2
-WHERE Left(t2.rangcode, 5) = Left(t1.rangcode, 5)
-AND Right(t2.rangcode, 4)  = '0000'
-AND Right(t1.rangcode, 4) <> '0000') AS class2,
-
-(SELECT t2.rusname FROM biotopelist AS t2
-WHERE Left(t2.rangcode, 7) = Left(t1.rangcode, 7)
-AND Right(t2.rangcode, 2)  = '00'
-AND Right(t1.rangcode, 2) <> '00') AS class3
-
-FROM biotopelist AS t1
-WHERE rusname
-----------------------------------------------------------------------
-
-Шаблон biotope.template содержит инструкцию <%include\photo.template%>,
-включающую шаблон описания фотографии (файл photo.template):
-
-----------------------------------------------------------------------
-    <фотография num="<%xml_escape get\NUMBER%>"
-       <%optional:автор="<%xml_escape get\authphoto%>" %>text="" 
-       file="<%xml_escape image:biotopephotos/<%get\photo%> images/<%get\SUPER.code%>_<%get\NUMBER%>.jpg jpg 300 300%>" 
-        big="<%xml_escape copy:biotopephotos/<%get\photo%> images/big/<%get\SUPER.code%>_<%get\NUMBER%>.jpg%>"/>
-----------------------------------------------------------------------
-
-Соответствующий SQL-запрос для получения описания фотографий
-(photo.sql):
-
-----------------------------------------------------------------------
-SELECT * from photos WHERE biotope = ?
-----------------------------------------------------------------------
-
-Шаблон, генерирующий список классов (файл classes.template):
-
-----------------------------------------------------------------------
-<?xml version="1.0" encoding="ISO-8859-5"?>
-<классификация><%query:class_sql [%\
-  <!-- rangcode: <%xml_escape get\rangcode%> -->
-  <класс id="<%get\NUMBER%>"><%xml_escape get\rusname%></класс>%]%>
-</классификация>
-----------------------------------------------------------------------
-
-Соответствующий SQL-запрос (class.sql):
-
-----------------------------------------------------------------------
-SELECT t1.*
-FROM biotopelist AS t1
-WHERE rusname AND Right(t1.rangcode, 8)  = '00000000'
-  AND EXISTS
-    (SELECT NULL FROM biotopelist AS t2
-     WHERE Left(t2.rangcode, 1) = Left(t1.rangcode, 1)
-     AND Right(t2.rangcode, 8) <> '00000000')
-----------------------------------------------------------------------
-
-Шаблон, генерирующий список растений (файл plants.template):
-
-----------------------------------------------------------------------
-<?xml version="1.0" encoding="ISO-8859-5"?>
-<растения><%query:plant_sql [%\
-  <!-- rangcode: <%xml_escape get\rangcode%> -->
-  <растение><%xml_escape get\rusname%></растение>%]%>
-</растения>
-----------------------------------------------------------------------
-
-Соответствующий SQL-запрос (plant.sql):
-
-----------------------------------------------------------------------
-SELECT t1.*
-FROM biotopelist AS t1
-WHERE rusname AND Right(t1.rangcode, 2) <> '00'
-----------------------------------------------------------------------
-
-Система DbReader начинает работу с обработки шаблона index.template,
-который содержит вызовы перечисленных выше SQL-запросов и включения
-шаблонов:
-
-----------------------------------------------------------------------
-<%!:
-  <%prepare:biotope_sql <%read\biotope.sql%>%>
-  <%prepare:photo_sql <%read\photo.sql%>%>
-  <%prepare:class_sql <%read\class.sql%>%>
-  <%prepare:plant_sql <%read\plant.sql%>%>
-
-  <%query:biotope_sql [%\<%write:<%get\code%>.xml <%include\biotope.template%>%> %]%>
-
-  <%write:classes.xml <%include\classes.template%>%>
-  <%write:plants.xml <%include\plants.template%>%>
-%>
-----------------------------------------------------------------------
-
-Пример сгенерированного XML-файла:
-
-----------------------------------------------------------------------
-<?xml version="1.0" encoding="ISO-8859-5"?>
-<!DOCTYPE описание_вида SYSTEM "brief.dtd">
-<!-- rangcode: E04020302 -->
-<описание_вида show="0">
-  <рисунки display="0">    
-    <фотография num="1" автор="Крышень А.М." text="" 
-                file="images/3_1.jpg" 
-                big="images/big/3_1.jpg"/>
-    <фотография num="2" автор="Крышень А.М." text="" 
-                file="images/3_2.jpg" 
-                big="images/big/3_2.jpg"/>
-  </рисунки>
-  <название>Betula sp. - Avenella flexuosa - Polytrichum commune</название>
-  <класс>E. Суходольные местообитания</класс>
-  <подкласс1>Автоморфные влажные</подкласс1>
-  <подкласс2></подкласс2>
-  <подкласс3></подкласс3>
-  <краткая_характеристика rows="3"><![CDATA[Развиваются  на луговиковых вырубках лесов черничного типа.  Через 5 - 7 лет  после рубки порослевая береза выходит из  яруса травянистых растений, ее влияние сказывается, в первую очередь, на влажности почвы.]]></краткая_характеристика>
-  <пример_описания rows="3"></пример_описания>
-  <авторы_описания></авторы_описания>
-</описание_вида>
-----------------------------------------------------------------------
-
-
-* Структура программы DbReader
-
-Программа DbReader написана на языке программирования Java и состоит
-из следующих основных классов:
-
-ru.karrc.dbreader.DbReader: cодержит точку входа программы. Загружает
-конфигурацию, устанавливает соединение с источником данных с помощью
-библиотеки JDBC, запускает обработку основного шаблона.
-
-ru.karrc.dbreader.TemplateParser: осуществляет разбор шаблона,
-обработку инструкций и вызов функций.
-
-ru.karrc.dbreader.Function: абстрактный класс. Реализации всех функций
-DbReader наследуют этот класс.
-
-ru.karrc.dbreader.FunctionDataParser: обработчик данных
-функции. Посволяет получить список аргументов и текст, передаваемый
-функции. При необходимости вызывает рекурсивную обработку данных с
-помощью TemplateParser.
-
-ru.karrc.dbreader.Functions: содержит реализации функций в виде
-анонимных классов, наследующих Function.
-
-ru.karrc.dbreader.TemplateParser: исключение, выбрасываемое в случае
-возникновения ошибки при обработке шаблона.
-
-Кроме того, реализовано несколько впомогательных классов, которые
-используются в реализациях функций системы.
-
-
-* Метрики реализованной программной системы.
-
-Количество файлов исходного кода: 8,
-Количество Java-классов (включая анонимные): 27,
-Общее количество строк исходного текста: 1311,
-Количество строк кода (исключая пустые строки и комментарии): 812,
-Количество строк комментариев: 180.
-
-
-* Заключение
-
-Автором была разработана программная система DbReader, которая
-является расширяемым и универсальным средством для представления
-информации из баз данных в текстовых форматах. С помощью системы
-DbReader база данных "Местообитания Восточной Фенноскандии" была
-переведена в XML-формат электронной библиотеки КарНЦ РАН.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/manual/index.html	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,358 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+          "DTD/xhtml1-transitional.dtd">
+
+<html>
+
+<head>
+  <title>п°п╟п╨я─п╬п©я─п╬я├п╣я│я│п╬я─ TEMA</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+</head>
+
+<body>
+<h1>п°п╟п╨я─п╬п©я─п╬я├п╣я│я│п╬я─ TEMA</h1>
+
+п°п╟п╨я─п╬п©я─п╬я├п╣я│я│п╬я─ TEMA п╬п╠я─п╟п╠п╟я┌я▀п╡п╟п╣я┌ п╥п╟п╢п╟п╫п╫я▀п╣ я┬п╟п╠п╩п╬п╫я▀ я┌п╣п╨я│я┌п╬п╡я▀я┘ я└п╟п╧п╩п╬п╡ п╦
+п╥п╟п╪п╣п╫я▐п╣я┌ п╫п╟п╧п╢п╣п╫п╫я▀п╣ п╡ п╫п╦я┘ п╦п╫я│я┌я─я┐п╨я├п╦п╦ п╫п╟ я─п╣п╥я┐п╩я▄я┌п╟я┌я▀ п╦я┘ п╡я▀п©п╬п╩п╫п╣п╫п╦я▐.
+
+<p><b>п╓п╬я─п╪п╟я┌ п╦п╫я│я┌я─я┐п╨я├п╦п╧</b></p>
+
+<blockquote>
+  <code>&lt;%<i>я│п©п╦я│п╬п╨_я└я┐п╫п╨я├п╦п╧</i>{:|\|`}<i>п╢п╟п╫п╫я▀п╣</i>%&gt;</code>  
+</blockquote>
+<p>
+пЁп╢п╣<br />
+
+<code><i>я│п©п╦я│п╬п╨_я└я┐п╫п╨я├п╦п╧</i></code> - я│п©п╦я│п╬п╨ п╦п╪п╣п╫ я└я┐п╫п╨я├п╦п╧, я─п╟п╥п╢п╣п╩п╣п╫п╫я▀я┘
+    п©я─п╬п╠п╣п╩п╟п╪п╦. п°п╬п╤п╣я┌ п╠я▀я┌я▄ п©я┐я│я┌я▀п╪.<br />
+<code><i>п╢п╟п╫п╫я▀п╣</i></code> - п╢п╟п╫п╫я▀п╣, п©п╣я─п╣п╢п╟п╡п╟п╣п╪я▀п╣ я└я┐п╫п╨я├п╦п╦.
+</p>
+
+<p><b>п╓п╬я─п╪п╟я┌ п╢п╟п╫п╫я▀я┘</b></p>
+
+<blockquote>
+  <code>
+    [<i>я│п©п╦я│п╬п╨_п╟я─пЁя┐п╪п╣п╫я┌п╬п╡</i>][<i>я┌п╣п╨я│я┌</i>]
+  </code>
+</blockquote>
+<p>
+пЁп╢п╣<br />
+
+<code><i>я│п©п╦я│п╬п╨_п╟я─пЁя┐п╪п╣п╫я┌п╬п╡</i></code> - я│п©п╦я│п╬п╨ п╟я─пЁя┐п╪п╣п╫я┌п╬п╡ я└я┐п╫п╨я├п╦п╦, я─п╟п╥п╢п╣п╩п╣п╫п╫я▀я┘ п©я─п╬п╠п╣п╩п╟п╪п╦.
+    п°п╬п╤п╣я┌ п╠я▀я┌я▄ п©я┐я│я┌я▀п╪.<br />
+<code><i>я┌п╣п╨я│я┌</i></code> - я┌п╣п╨я│я┌, п©п╣я─п╣п╢п╟п╡п╟п╣п╪я▀п╧ я└я┐п╫п╨я├п╦п╦ п╠п╣п╥ я─п╟п╥п╠п╦п╣п╫п╦я▐ п╫п╟ п╟я─пЁя┐п╪п╣п╫я┌я▀.
+    п°п╬п╤п╣я┌ п╠я▀я┌я▄ п©я┐я│я┌я▀п╪. п п╬п╩п╦я┤п╣я│я┌п╡п╬ п╟я─пЁя┐п╪п╣п╫я┌п╬п╡, п©п╬я│п╩п╣ п╨п╬я┌п╬я─п╬пЁп╬ я│п╩п╣п╢я┐п╣я┌
+    я┌п╣п╨я│я┌, п╥п╟п╡п╦я│п╦я┌ п╬я┌ я└я┐п╫п╨я├п╦п╦.
+</p><p>
+п═п╟п╥п╢п╣п╩п╦я┌п╣п╩я▄ п╪п╣п╤п╢я┐ я│п©п╦я│п╨п╬п╪ я└я┐п╫п╨я├п╦п╧ п╦ п╢п╟п╫п╫я▀п╪п╦ п╬п©я─п╣п╢п╣п╩я▐п╣я┌, п╨п╟п╨ п╢п╬п╩п╤п╫я▀
+п╬п╠я─п╟п╠п╟я┌я▀п╡п╟я┌я▄я│я▐ п╢п╟п╫п╫я▀п╣ я└я┐п╫п╨я├п╦п╦:
+</p><p>
+<code>:</code> - я─п╣п╨я┐я─я│п╦п╡п╫п╟я▐ п╬п╠я─п╟п╠п╬я┌п╨п╟,<br />
+<code>\</code> п╦п╩п╦ <code>`</code> - п©п╣я─п╣п╢п╟я┌я▄ п╠п╣п╥ п╬п╠я─п╟п╠п╬я┌п╨п╦.
+</p><p>
+п∙я│п╩п╦ п╡ я│п©п╦я│п╨п╣ я└я┐п╫п╨я├п╦п╧ п╥п╟п╢п╟п╫п╬ п╢п╡п╣ п╦ п╠п╬п╩п╣п╣ я└я┐п╫п╨я├п╦п╦, п╬п╫п╦ п╡я▀п©п╬п╩п╫я▐я▌я┌я│я▐,
+п╫п╟я┤п╦п╫п╟я▐ я│ п©п╬я│п╩п╣п╢п╫п╣п╧, я┌п╟п╨ я┤я┌п╬ п╨п╟п╤п╢п╟я▐ я└я┐п╫п╨я├п╦я▐ п©п╬п╩я┐я┤п╟п╣я┌ п╡ п╨п╟я┤п╣я│я┌п╡п╣ п╢п╟п╫п╫я▀я┘
+я─п╣п╥я┐п╩я▄я┌п╟я┌ п╡я▀п©п╬п╩п╫п╣п╫п╦я▐ я│п╩п╣п╢я┐я▌я┴п╣п╧ я└я┐п╫п╨я├п╦п╦.
+</p><p>
+п п╟п╤п╢п╟я▐ я└я┐п╫п╨я├п╦я▐ п╦п╪п╣п╣я┌ п╨п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟ - я├п╣п╩п╬п╣ я┤п╦я│п╩п╬. п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟
+п╦п╫я│я┌я─я┐п╨я├п╦п╦ - п╨п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟ п©п╣я─п╡п╬п╧ п╡ я│п©п╦я│п╨п╣ я└я┐п╫п╨я├п╦п╦. п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟,
+п©п╬п╩я┐я┤п╟п╣п╪я▀п╧ п©я─п╦ п╬п╠я─п╟п╠п╬я┌п╨п╣ я┌п╣п╨я│я┌п╟ - я│я┐п╪п╪п╟ п╨п╬п╢п╬п╡ п╡п╬п╥п╡я─п╟я┌п╟ п╬п╠я─п╟п╠п╬я┌п╟п╫п╫я▀я┘
+п╦п╫я│я┌я─я┐п╨я├п╦п╧ (п╨п╟п╨ п©я─п╟п╡п╦п╩п╬, я│п╪я▀я│п╩ я█я┌п╬пЁп╬ п╥п╫п╟я┤п╣п╫п╦я▐ - п╨п╬п╩п╦я┤п╣я│я┌п╡п╬ п╦п╫я│я┌я─я┐п╨я├п╦п╧,
+п╥п╟п╪п╣п╫п╣п╫п╫я▀я┘ п╫п╟ п╫п╣п©я┐я│я┌п╬п╧ я┌п╣п╨я│я┌).
+</p><p>
+п я─п╬п╪п╣ я│п╨п╬п╠п╬п╨ '&lt;', '&gt;', п╪п╬п╤п╫п╬ п╦я│п©п╬п╩я▄п╥п╬п╡п╟я┌я▄ я│п╨п╬п╠п╨п╦ '[', ']'.
+</p>
+
+
+<h2>п╓я┐п╫п╨я├п╦п╦</h2>
+
+<p><code><b>set</b></code></p>
+
+<table>
+<tr><td>п░я─пЁя┐п╪п╣п╫я┌я▀:</td>
+<td><i>п╦п╪я▐</i></td></tr>
+
+<tr><td>п╒п╣п╨я│я┌:</td>
+<td><i>п╥п╫п╟я┤п╣п╫п╦п╣</i></td></tr>
+
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td>
+<td>пёя│я┌п╟п╫п╟п╡п╩п╦п╡п╟п╣я┌ п╥п╫п╟я┤п╣п╫п╦п╣ п©п╣я─п╣п╪п╣п╫п╫п╬п╧ <i>п╦п╪я▐</i>.</td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td>
+<td><i>п╦п╪я▐</i></td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td>
+<td>1</td></tr>
+</table>
+
+<p><code><b>define</b></code></p>
+
+<table>
+<tr><td>п░я─пЁя┐п╪п╣п╫я┌я▀:</td>
+<td><i>п╦п╪я▐</i></td></tr>
+
+<tr><td>п╒п╣п╨я│я┌:</td>
+<td><i>я┬п╟п╠п╩п╬п╫</i></td></tr>
+
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td><td>п·п©я─п╣п╢п╣п╩я▐п╣я┌ п╫п╬п╡я┐я▌ я└я┐п╫п╨я├п╦я▌ <i>п╦п╪я▐</i>, п©я─п╦
+п╡я▀п╥п╬п╡п╣ п╨п╬я┌п╬я─п╬п╧ п╬п╠я─п╟п╠п╟я┌я▀п╡п╟п╣я┌я│я▐ <i>я┬п╟п╠п╩п╬п╫</i>. п÷я─п╦ п╬п╠я─п╟п╠п╬я┌п╨п╣ п╢п╬я│я┌я┐п©п╫я▀
+я└я┐п╫п╨я├п╦п╦ <code>nextarg</code> п╢п╩я▐ п©п╬п╩я┐я┤п╣п╫п╦я▐ п╬я┤п╣я─п╣п╢п╫п╬пЁп╬ п╟я─пЁя┐п╪п╣п╫я┌п╟
+п╡я▀п╥я▀п╡п╟п╣п╪п╬п╧ я└я┐п╫п╨я├п╦п╦ п╦ <code>data</code> п╢п╩я▐ п©п╬п╩я┐я┤п╣п╫п╦я▐ я┌п╣п╨я│я┌п╟.</td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td>
+<td><i>п╦п╪я▐</i></td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td>
+<td>1</td></tr>
+</table>
+
+<p><code><b>load</b></code></p>
+
+<table>
+<tr><td>п░я─пЁя┐п╪п╣п╫я┌я▀:</td>
+<td><i>п╦п╪я▐</i> <i>п╦п╪я▐_п╨п╩п╟я│я│п╟</i></td></tr>
+
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td>
+
+<td>п·п©я─п╣п╢п╣п╩я▐п╣я┌ п╫п╬п╡я┐я▌ я└я┐п╫п╨я├п╦я▌ <i>п╦п╪я▐</i>. п═п╣п╟п╩п╦п╥п╟я├п╦я▐ я└я┐п╫п╨я├п╦п╦ п╬п©я─п╣п╢п╣п╩п╣п╫п╟
+Java-п╨п╩п╟я│я│п╬п╪ <i>п╦п╪я▐_п╨п╩п╟я│я│п╟</i>, п╫п╟я│п╩п╣п╢я┐я▌я┴п╦п╪ п╨п╩п╟я│я│
+<code>kryshen.tema.Function</code>.</td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td>
+<td><i>п╦п╪я▐</i></td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td>
+<td>1</td></tr>
+</table>
+
+<p><code><b>prepare</b></code></p>
+
+<table>
+<tr><td>п░я─пЁя┐п╪п╣п╫я┌я▀:</td>
+<td><i>п╦п╪я▐</i></td></tr>
+
+<tr><td>п╒п╣п╨я│я┌:</td>
+<td><i>п╥п╟п©я─п╬я│</i></td></tr>
+
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td>
+<td>п÷п╬п╢пЁп╬я┌п╟п╡п╩п╦п╡п╟п╣я┌ SQL-п╥п╟п©я─п╬я│ <i>п╥п╟п©я─п╬я│</i> п╢п╩я▐ п╡я▀п©п╬п╩п╫п╣п╫п╦я▐, п╥п╟п©п╦я│я▀п╡п╟п╣я┌
+п©п╬п╢пЁп╬я┌п╬п╡п╩п╣п╫п╫я▀п╧ п╥п╟п©я─п╬я│ п╡ п©п╣я─п╣п╪п╣п╫п╫я┐я▌ <i>п╦п╪я▐</i>.</td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td>
+<td><i>п╦п╪я▐</i></td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td>
+<td>1</td></tr>
+</table>
+
+<p><code><b>query</b></code></p>
+
+<table>
+<tr><td>п░я─пЁя┐п╪п╣п╫я┌я▀:</td>
+<td><i>п╦п╪я▐_п╥п╟п©я─п╬я│п╟</i> <i>я┬п╟п╠п╩п╬п╫</i> <i>п©п╟я─п╟п╪1</i> ... <i>п©п╟я─п╟п╪N</i></td></tr>
+
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td>
+<td>п▓я▀п©п╬п╩п╫я▐п╣я┌ п╥п╟п©я─п╬я│ я│ п©п╟я─п╟п╪п╣я┌я─п╟п╪п╦, п©п╬п╢пЁп╬я┌п╬п╡п╩п╣п╫п╫я▀п╧ я│ п©п╬п╪п╬я┴я▄я▌ я└я┐п╫п╨я├п╦п╦
+prepare. п≈п╫п╟я┤п╣п╫п╦я▐ п©п╟я─п╟п╪п╣я┌я─п╬п╡ п©п╬п╢я│я┌п╟п╡п╩я▐я▌я┌я│я▐ п╡ п╥п╟п©я─п╬я│ п╡п╪п╣я│я┌п╬ я│п╦п╪п╡п╬п╩п╟
+'?'. п≈п╫п╟я┤п╣п╫п╦я▐ п©п╬п╩п╣п╧ п╬я┌п╡п╣я┌п╟ п╢п╬я│я┌я┐п©п╫я▀ я│ п©п╬п╪п╬я┴я▄я▌ я└я┐п╫п╨я├п╦п╦ <code>db</code>,
+п╨п╟п╨ п©п╣я─п╣п╪п╣п╫п╫я▀п╣ я┬п╟п╠п╩п╬п╫п╟ <i>я┬п╟п╠п╩п╬п╫</i>. п÷я─п╦ п╬п╠я─п╟п╠п╬я┌п╨п╦ я┬п╟п╠п╩п╬п╫п╟ я┌п╟п╨п╤п╣
+п╬п©я─п╣п╢п╣п╩я▐п╣я┌я│я▐ п©п╣я─п╣п╪п╣п╫п╫п╟я▐ <code>number</code>, я│п╬п╢п╣я─п╤п╟я┴п╟я▐ п╫п╬п╪п╣я─ я┌п╣п╨я┐я┴п╣п╧
+я│я┌я─п╬п╨п╦ п╬я┌п╡п╣я┌п╟.</td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td>
+<td>я─п╣п╥я┐п╩я▄я┌п╟я┌ п╬п╠я─п╟п╠п╬я┌п╨п╦ я┬п╟п╠п╩п╬п╫п╟ <i>я┬п╟п╠п╩п╬п╫</i> п╢п╩я▐ п╨п╟п╤п╢п╬п╧ я│я┌я─п╬п╨п╦
+п╬я┌п╡п╣я┌п╟.</td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td>
+<td>п п╬п╩п╦я┤п╣я│я┌п╡п╬ п©п╬п╩я┐я┤п╣п╫п╫я▀я┘ я│я┌я─п╬п╨ п╬я┌п╡п╣я┌п╟.</td></tr>
+</table>
+
+<p><code><b>optional</b></code></p>
+
+<table>
+<tr><td>п╒п╣п╨я│я┌:</td>
+<td><i>п╢п╟п╫п╫я▀п╣</i></td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td><td><i>п╢п╟п╫п╫я▀п╣</i>, п╣я│п╩п╦ п©я─п╦ п╬п╠я─п╟п╠п╬я┌п╨п╣ п╢п╟п╫п╫я▀я┘
+п╠я▀п╩ п©п╬п╩я┐я┤п╣п╫ п╨п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟ п╬я┌п╩п╦я┤п╫я▀п╧ п╬я┌ 0, п╦п╫п╟я┤п╣ - п©я┐я│я┌п╟я▐
+я│я┌я─п╬п╨п╟.</td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td>
+<td>1, п╣я│п╩п╦ я─п╣п╥я┐п╩я▄я┌п╟я┌ - п©я┐я│я┌п╟я▐ я│я┌я─п╬п╨п╟, 0 - п╦п╫п╟я┤п╣.</td></tr>
+</table>
+
+<p><code><b>image</b></code></p>
+
+<table>
+<tr><td>п░я─пЁя┐п╪п╣п╫я┌я▀:</td>
+<td><i>п╦я│я┘_я└п╟п╧п╩</i> <i>п╨п╬п╫_я└п╟п╧п╩</i> <i>я└п╬я─п╪п╟я┌</i> [<i>п╪п╟п╨я│_я┬п╦я─п╦п╫п╟</i>
+[<i>п╪п╟п╨я│_п╡я▀я│п╬я┌п╟</i>]]</td></tr>
+
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td>
+<td>п≈п╟пЁя─я┐п╤п╟п╣я┌ п╦п╥п╬п╠я─п╟п╤п╣п╫п╦п╣ п╦п╥ я└п╟п╧п╩п╟ <i>п╦я│я┘_я└п╟п╧п╩</i> (п©я┐я┌я▄ п╬п©я─п╣п╢п╣п╩я▐п╣я┌я│я▐
+п╬я┌п╫п╬я│п╦я┌п╣п╩я▄п╫п╬ п╨п╬п╫я└п╦пЁя┐я─п╟я├п╦п╬п╫п╫п╬пЁп╬ п©п╟я─п╟п╪п╣я┌я─п╟ "resource_base") п╦
+п©я─п╣п╬п╠я─п╟п╥я┐п╣я┌ п╣пЁп╬ п╡ я┐п╨п╟п╥п╟п╫п╫я▀п╧ я└п╬я─п╪п╟я┌, я│п╬я┘я─п╟п╫я▐я▐ я─п╣п╥я┐п╩я▄я┌п╟я┌ п╡
+<i>п╨п╬п╫_я└п╟п╧п╩</i>. п∙я│п╩п╦ п╥п╟п╢п╟п╫я▀ п╪п╟п╨я│п╦п╪п╟п╩я▄п╫п╟я▐ п╡я▀я│п╬я┌п╟ п╦ я┬п╦я─п╦п╫п╟, п╠п╬п╩я▄я┬п╦п╣
+п╦п╥п╬п╠я─п╟п╤п╣п╫п╦я▐ п╠я┐п╢я┐я┌ я┐п╪п╣п╫я▄я┬п╣п╫я▀.</td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td>
+<td><i>п╨п╬п╫_я└п╟п╧п╩</i> п©я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦, п©я┐я│я┌п╟я▐ я│я┌я─п╬п╨п╟ - п╦п╫п╟я┤п╣.</td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td>
+<td>1 п©я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦, 0 - п╦п╫п╟я┤п╣.</td></tr>
+</table>
+
+
+<p><code><b>copy</b></code></p>
+
+<table>
+<tr><td>п░я─пЁя┐п╪п╣п╫я┌я▀:</td><td><i>п╦я│я┘_я└п╟п╧п╩</i> <i>п╨п╬п╫_я└п╟п╧п╩</i></td></tr>
+
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td><td>п п╬п©п╦я─я┐п╣я┌ я└п╟п╧п╩ <i>п╦я│я┘_я└п╟п╧п╩</i> п╡ я└п╟п╧п╩
+<i>п╨п╬п╫_я└п╟п╧п╩</i> (п©я┐я┌я▄ <i>п╦я│я┘_я└п╟п╧п╩</i> п╬п©я─п╣п╢п╣п╩я▐п╣я┌я│я▐ п╬я┌п╫п╬я│п╦я┌п╣п╩я▄п╫п╬
+п╨п╬п╫я└п╦пЁя┐я─п╟я├п╦п╬п╫п╫п╬пЁп╬ п©п╟я─п╟п╪п╣я┌я─п╟ "resource_base").</td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td><td><i>п╨п╬п╫_я└п╟п╧п╩</i> п©я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦,
+п©я┐я│я┌п╟я▐ я│я┌я─п╬п╨п╟ - п╦п╫п╟я┤п╣.</td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td><td>1 п©я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦, 0 -
+п╦п╫п╟я┤п╣.</td></tr>
+</table>
+
+<p><code><b>write</b></code></p>
+
+<table>
+<tr><td>п░я─пЁя┐п╪п╣п╫я┌я▀:</td><td><i>п╦п╪я▐_я└п╟п╧п╩п╟</i></td></tr>
+
+<tr><td>п╒п╣п╨я│я┌:</td><td><i>п╢п╟п╫п╫я▀п╣</i></td></tr>
+
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td><td>п≈п╟п©п╦я│я▀п╡п╟п╣я┌ <i>п╢п╟п╫п╫я▀п╣</i> п╡ я└п╟п╧п╩
+<i>п╦я│я┘_я└п╟п╧п╩</i>.</td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td><td><i>п╨п╬п╫_я└п╟п╧п╩</i> п©я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦,
+п©я┐я│я┌п╟я▐ я│я┌я─п╬п╨п╟ - п╦п╫п╟я┤п╣.</td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td><td>1 п©я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦, 0 -
+п╦п╫п╟я┤п╣.</td></tr>
+</table>
+
+<p><code><b>read</b></code></p>
+
+<table>
+<tr><td>п╒п╣п╨я│я┌:</td><td><i>п╦п╪я▐_я└п╟п╧п╩п╟</i></td></tr>
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td><td>п╖п╦я┌п╟п╣я┌ я└п╟п╧п╩ <i>п╦п╪я▐_я└п╟п╧п╩п╟</i>.</td></tr>
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td><td>п©я─п╬я┤п╦я┌п╟п╫п╫я▀п╣ п╢п╟п╫п╫я▀п╣ п©я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦,
+  п©я┐я│я┌п╟я▐ я│я┌я─п╬п╨п╟ - п╦п╫п╟я┤п╣.</td></tr>
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td><td>1 п©я─п╦ я┐я│п©п╣я┬п╫п╬п╪ п╡я▀п©п╬п╩п╫п╣п╫п╦п╦, 0 -
+п╦п╫п╟я┤п╣.</td></tr>
+</table>
+
+<p><code><b>include</b></code></p>
+
+<table>
+<tr><td>п╒п╣п╨я│я┌:</td><td><i>п╦п╪я▐_я└п╟п╧п╩п╟</i></td></tr>
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td><td>п╡п╨п╩я▌я┤п╟п╣я┌ я┬п╟п╠п╩п╬п╫ п╦п╥ я└п╟п╧п╩п╟
+<i>п╦п╪я▐_я└п╟п╧п╩п╟</i>.</td></tr>
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td><td>я─п╣п╥я┐п╩я▄я┌п╟я┌ п╬п╠я─п╟п╠п╬я┌п╨п╦ я┬п╟п╠п╩п╬п╫п╟.</td></tr>
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td><td>п╨п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟, п©п╬п╩я┐я┤п╣п╫п╫я▀п╧ п©я─п╦ п╬п╠я─п╟п╠п╬я┌п╨п╣
+я┬п╟п╠п╩п╬п╫п╟.</td></tr>
+</table>
+
+<p><code><b>!</b></code></p>
+
+<table>
+<tr><td>п╒п╣п╨я│я┌:</td><td><i>п╢п╟п╫п╫я▀п╣</i></td></tr>
+
+<tr><td>п■п╣п╧я│я┌п╡п╦п╣:</td><td>п╫п╣я┌.</td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td><td>п╫п╣я┌.</td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td><td>п╨п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟, п©п╬п╩я┐я┤п╣п╫п╫я▀п╧ п©я─п╦ п╬п╠я─п╟п╠п╬я┌п╨п╣
+я┌п╣п╨я│я┌п╟ п╢п╟п╫п╫я▀я┘.</td></tr>
+</table>
+
+<p><code><b>replace</b></code></p>
+
+<table>
+<tr><td>п░я─пЁя┐п╪п╣п╫я┌я▀:</td><td><i>я│я┌я─1</i> <i>я│я┌я─2</i></td></tr>
+
+<tr><td>п╒п╣п╨я│я┌:</td><td><i>п╢п╟п╫п╫я▀п╣</i></td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td><td>п╢п╟п╫п╫я▀п╣, я│ п╥п╟п╪п╣п╫п╣п╫п╫я▀п╪п╦ п╡я┘п╬п╤п╢п╣п╫п╦я▐п╪п╦ п©п╬п╢я│я┌я─п╬п╨п╦
+<i>я│я┌я─1</i> п╫п╟ <i>я│я┌я─2</i>.</td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td><td> п╨п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟, п©п╬п╩я┐я┤п╣п╫п╫я▀п╧ п©я─п╦ п╬п╠я─п╟п╠п╬я┌п╨п╣
+я┌п╣п╨я│я┌п╟ п╢п╟п╫п╫я▀я┘.</td></tr>
+</table>
+
+<p><code><b>xml_escape</b></code></p>
+
+<table>
+<tr><td>п╒п╣п╨я│я┌:</td><td><i>п╢п╟п╫п╫я▀п╣</i></td></tr>
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td><td>я┌п╣п╨я│я┌ <i>п╢п╟п╫п╫я▀п╣</i>, п╡ п╨п╬я┌п╬я─п╬п╪ я│п╦п╪п╡п╬п╩я▀
+'&amp;', '&lt;', '&gt;', '`', '\' п╥п╟п╪п╣п╫п╣п╫я▀ п╫п╟ я│п╬п╬я┌п╡п╣я┌я│я┌п╡я┐я▌я┴п╦п╣ я│я┐я┴п╫п╬я│я┌п╦
+XML.</td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td><td> п╨п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟, п©п╬п╩я┐я┤п╣п╫п╫я▀п╧ п©я─п╦ п╬п╠я─п╟п╠п╬я┌п╨п╣
+я┌п╣п╨я│я┌п╟ п╢п╟п╫п╫я▀я┘.</td></tr>
+</table>
+
+<p><code><b>xml_cdata</b></code></p>
+
+<table>
+<tr><td>п╒п╣п╨я│я┌:</td><td><i>п╢п╟п╫п╫я▀п╣</i></td></tr>
+
+<tr><td>п═п╣п╥я┐п╩я▄я┌п╟я┌:</td><td>п╢п╟п╫п╫я▀п╣ п╡ п╡п╦п╢п╣ п╠п╩п╬п╨п╟ XML CDATA.</td></tr>
+
+<tr><td>п п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟:</td><td> п╨п╬п╢ п╡п╬п╥п╡я─п╟я┌п╟, п©п╬п╩я┐я┤п╣п╫п╫я▀п╧ п©я─п╦ п╬п╠я─п╟п╠п╬я┌п╨п╣
+я┌п╣п╨я│я┌п╟ п╢п╟п╫п╫я▀я┘.  </td></tr>
+</table>
+
+<p>
+п°п╟п╨я─п╬п©я─п╬я├п╣я│я│п╬я─ TEMA я─п╟я│я┬п╦я─я▐п╣п╪: п╡п╬п╥п╪п╬п╤п╫п╬ п╢п╬п╠п╟п╡п╩п╣п╫п╦п╣ п╡ я│п╦я│я┌п╣п╪я┐ п╫п╬п╡я▀я┘
+я└я┐п╫п╨я├п╦п╧, я─п╣п╟п╩п╦п╥п╬п╡п╟п╫п╫я▀я┘ п╡ п╡п╦п╢п╣ п╨п╩п╟я│я│п╬п╡ п╫п╟ я▐п╥я▀п╨п╣ Java.
+</p>
+
+<h2>п≈п╟п©я┐я│п╨</h2>
+
+<p>java -jar tema.jar [<i>п╬п©я├п╦п╦</i>]
+</p><p>
+п·п©я├п╦п╦:
+</p>
+<table>
+<tr><td>-d[emo]</td><td>п■п╣п╪п╬п╫я│я┌я─п╟я├п╦п╬п╫п╫я▀п╧ я─п╣п╤п╦п╪</td></tr>
+<tr><td>-v[ersion]</td><td>п▓я▀п╡п╬п╢ п╡п╣я─я│п╦п╦</td></tr>
+<tr><td>-h[help] -u[sage]</td><td>п▓я▀п╡п╬п╢ я│п©я─п╟п╡п╨п╦</td></tr>
+</table>
+
+<p>
+п÷я─п╦ п╥п╟п©я┐я│п╨п╣ я┤п╦я┌п╟п╣я┌я│я▐ я└п╟п╧п╩ "tema.properties" п╦п╥ я┌п╣п╨я┐я┴п╣пЁп╬ п╨п╟я┌п╟п╩п╬пЁп╟.<br />
+п÷я─п╦п╪п╣я─ я└п╟п╧п╩п╟ "tema.properties":
+</p>
+
+<pre>
+# п²п╟я│я┌я─п╬п╧п╨п╟ п╦я│я┌п╬я┤п╫п╦п╨п╟ п╢п╟п╫п╫я▀я┘
+# resource          : jdbc:odbc:database
+# driver            : sun.jdbc.odbc.JdbcOdbcDriver
+
+# п▒п╟п╥п╬п╡я▀п╧ п╨п╟я┌п╟п╩п╬пЁ я─п╣я│я┐я─я│п╬п╡
+# resource_base     : .
+
+# п╗п╟п╠п╩п╬п╫, я│ п╨п╬я┌п╬я─п╬пЁп╬ п╫п╟я┤п╦п╫п╟п╣я┌я│я▐ п╬п╠я─п╟п╠п╬я┌п╨п╟
+main_template     : main.template
+
+# п п╬п╢п╦я─п╬п╡п╨п╦ я└п╟п╧п╩п╬п╡
+# input_encoding    : UTF-8
+# output_encoding   : UTF-8
+
+# п я█я┬п╦я─п╬п╡п╟п╫п╦п╣ я┬п╟п╠п╩п╬п╫п╬п╡
+# cache_read        : true
+
+# п▓я▀п╡п╬п╢ я─п╣п╥я┐п╩я▄я┌п╟я┌п╟ я─п╟п╥п╠п╬я─п╟ я┬п╟п╠п╩п╬п╫п╟ main.template п╡ stderr
+# output            : stderr
+
+# п▓я▀п╡п╬п╢ я│п╬п╬п╠я┴п╣п╫п╦п╧ п╬п╠ п╬я┬п╦п╠п╨п╟я┘ п╡ я└п╟п╧п╩
+# log               : dbreader.log
+</pre>
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/res/kryshen/tema/demo/demo.template	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,25 @@
+[%!\ TEMA demo template %]
+
+п▓я▀п╡п╬п╢ я┌п╣п╨я│я┌п╟:
+test text
+
+п▓я▀п╡п╬п╢ я│п©п╣я├п╦п╟п╩я▄п╫я▀я┘ я│п╦п╪п╡п╬п╩п╬п╡:
+[%\<%test%>%]
+
+п·п╠я┼я▐п╡п╩п╣п╫п╦п╣ я└я┐п╫п╨я├п╦п╦:
+<%define\test test arg1:[%nextarg:%], test arg2:[%nextarg:%], test data:[%data:%].%>   
+
+п▓я▀п╥п╬п╡ п╫п╬п╡п╬п╧ я└я┐п╫п╨я├п╦п╦:
+<%test:1 2 3%>
+
+п≈п╟пЁя─я┐п╥п╨п╟ я└я┐п╫п╨я├п╦п╦ п╦п╥ п╨п╩п╟я│я│п╟:
+<%load\hello kryshen.tema.demo.Hello%>
+
+п▓я▀п©п╬п╩п╣п╫п╣п╫п╦п╣ п╥п╟пЁя─я┐п╤п╣п╫п╫п╬п╧ я└я┐п╫п╨я├п╦п╦:
+<%hello\TEMA%>
+
+пёя│п╩п╬п╡п╫п╬п╣ п╡я▀п©п╬п╩п╫п╣п╫п╦п╣:
+<%optional:<%true:%>True%> <%optional:<%false:%>False%>
+
+п╒п╣я│я┌п╦я─п╬п╡п╟п╫п╦п╣ xml_escape:
+<%xml_escape\x < a & b%>
--- a/sample/class.xml	Tue May 16 18:04:09 2006 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-5"?>
-<зшпААьДьзпФьО>
-  <зшпАА id="1">╟щБЮчъчсущщКу эуАБччяьБпщьО</зшпАА>
-  <зшпАА id="2">╪чЮО ь эчЮАзьу ъчяуЮужЛО</зшпАА>
-  <зшпАА id="3">╬вуЮп ь ъЮьяЮужщКу эуАБччяьБпщьО</зшпАА>
-  <зшпАА id="4">юузь, ЮЦГЛь ь ъЮьяЮужщКу эуАБччяьБпщьО</зшпАА>
-  <зшпАА id="5">╠чшчБп ь впячшчГущщКу вуэшь</зшпАА>
-  <зшпАА id="6">аЦЕчтчшЛщКу эуАБччяьБпщьО</зшпАА>
-</зшпААьДьзпФьО>
--- a/sample/plants.xml	Tue May 16 18:04:09 2006 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-5"?>
-<ЮпАБущьО>
-  <ЮпАБущьу>Betula sp.</ЮпАБущьу>
-  <ЮпАБущьу>Calamagrostis arundinacea</ЮпАБущьу>
-  <ЮпАБущьу>Geranium sylvaticum</ЮпАБущьу>
-</ЮпАБущьО>
--- a/src/kryshen/tema/Function.java	Tue May 16 18:04:09 2006 +0400
+++ b/src/kryshen/tema/Function.java	Thu Dec 14 23:22:05 2006 +0300
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
  *
- * $Id: Function.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: Function.java,v 1.5 2006/12/14 14:39:22 mikhail Exp $
  */
 
 package kryshen.tema;
@@ -10,13 +24,19 @@
 import java.util.*;
 import java.sql.ResultSet;
 
+/**
+ * Abstact class for TEMA functions.
+ *
+ * @author Mikhail A. Kryshen
+ */
 public abstract class Function {
-    public final String name;
 
-    protected Function(String name) {
-	this.name = name;
-    }
-
+    /**
+     * Invoke the function.
+     *
+     * @param fdp FunctionDataParser to access function arguments.
+     * @param out Writer for the function output.
+     */
     public abstract int invoke(FunctionDataParser fdp, Writer out)
         throws IOException, TemplateException;
 }
--- a/src/kryshen/tema/FunctionDataParser.java	Tue May 16 18:04:09 2006 +0400
+++ b/src/kryshen/tema/FunctionDataParser.java	Thu Dec 14 23:22:05 2006 +0300
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
  *
- * $Id: FunctionDataParser.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: FunctionDataParser.java,v 1.5 2006/12/14 14:39:22 mikhail Exp $
  */
 
 package kryshen.tema;
@@ -23,13 +37,13 @@
 
     private TemplateParser tp;
     private FunctionData fd;
-    private Reader in;
+    private TemplateReader in;
 
     private boolean available = true;
     private boolean first = true;
 
     FunctionDataParser(TemplateParser tp, FunctionData fd,
-                       Reader in) {
+                       TemplateReader in) {
         this.tp = tp;
         this.fd = fd;
         this.in = in;
@@ -40,7 +54,7 @@
         
         if (!available)
             throw new TemplateException
-		("Unexpected end of instruction data.");
+		("Unexpected end of instruction data.", in);
 
         TemplateParser.Result r;
 
@@ -53,11 +67,11 @@
                          argument ? ARG_SEPARATORS : null,
                          (argument || !first) ? ARG_SEPARATORS : null);
         } else if (fd == FunctionData.SUBFUNCTION && argument) {
-            /* Subfunction can pass a list of arguments */
+            // Subfunction can pass a list of arguments
             StringWriter sw = new StringWriter();
             tp.parseFunction(in, sw);
             sw.close();
-            in = new StringReader(sw.toString());
+            in = new TemplateReader(new StringReader(sw.toString()), in);
             fd = FunctionData.NONRECURSIVE;
             r = parseData(out, true);
         } else if (fd == FunctionData.SUBFUNCTION) {
@@ -74,7 +88,7 @@
     }
 
     public int parseData(Writer out) throws IOException, TemplateException {
-        return parseData(out, false).substitutions; 
+        return parseData(out, false).retCode; 
     }
 
     public String getData() throws IOException, TemplateException {        
@@ -92,7 +106,7 @@
             r = parseData(out, true);
         }
 
-        return r.substitutions;
+        return r.retCode;
     }
 
     public String getNextArg() throws IOException, TemplateException {        
@@ -119,4 +133,8 @@
     public TemplateParser getTemplateParser() {
         return tp;
     }
+    
+    public TemplateReader getTemplateReader() {
+        return in;
+    }
 }
--- a/src/kryshen/tema/Functions.java	Tue May 16 18:04:09 2006 +0400
+++ b/src/kryshen/tema/Functions.java	Thu Dec 14 23:22:05 2006 +0300
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
  *
- * $Id: Functions.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: Functions.java,v 1.11 2006/12/14 19:44:31 mikhail Exp $
  */
 
 package kryshen.tema;
@@ -9,328 +23,48 @@
 import java.io.*;
 import java.util.*;
 
-import java.sql.SQLException;
-import java.sql.PreparedStatement;
-
 import kryshen.tema.functions.*;
 
+/**
+ * Standard TEMA functions.
+ *
+ * @author Mikhail A. Kryshen
+ */
 public class Functions {
-    public static final Function GET =
-        new Function("get") {
-            public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
-
-		String name = fdp.getData();
-                return fdp.getTemplateParser().parseVariable(name, out);
-            }
-        };
-
-    public static final Function SET =
-        new Function("set") {
-            public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
-
-                String arg0 = fdp.getNextArg();
-                String arg1 = fdp.getData();
-                
-                fdp.getTemplateParser().setValue(arg0, arg1);
-                
-		out.write(arg0);
-                return 1;
-            }
-        };
-
-    /* prepare:qury_name sql_statement */
-    public static final Function PREPARE =
-        new Function("prepare") {
-            public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
-
-                String arg0 = fdp.getNextArg();
-                String data = fdp.getData();
-
-		PreparedStatement statement;
-
-		try {
-		    statement = Tema.connection.prepareStatement(data);
-		} catch (SQLException e) {
-		    throw new TemplateException(e.getMessage(), e);
-		}
-                
-                fdp.getTemplateParser().setValue(arg0, statement);   
-             
-		out.write(arg0);
-                return 1;
-            }
-        };
-
-    public static final Function OPTIONAL =
-        new Function("optional") {
-            public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
-
-                StringWriter sw = new StringWriter();
-                int s = fdp.parseData(sw);
-                sw.close();
-                String data = sw.toString();
-
-                if (s > 0) {
-                    out.write(data);
-                    return 1;
-                } else {
-                    return 0;
-                }
-            }
-        };
-
-    public static final Function IMAGE =
-        new Function("image") {
-            public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
-
-                String arg0 = fdp.getNextArg();
-                String arg1 = fdp.getNextArg();
-                String arg2 = fdp.getNextArg();
-                int arg3 = fdp.hasMoreData() ? 
-		    Integer.parseInt(fdp.getNextArg()) : -1;
-                int arg4 = fdp.hasMoreData() ? 
-		    Integer.parseInt(fdp.getNextArg()) : -1;
-
-                File source = new File
-                    (Tema.getProperty("resource_base"), arg0);
-
-                try {
-                    ImageConverter.convert
-                        (source,                  /* source file */
-                         new File(arg1),          /* dest file */
-                         arg2,                    /* format */
-                         arg3,                    /* max width */
-                         arg4);                   /* max height */
-                } catch (Exception e) {
-                    System.err.println(e);
-                    return 0;
-                }
-
-                out.write(arg1);
-                return 1;
-            }
-        };
-
-    public static final Function COPY =
-        new Function("copy") {
-           public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
-
-                String arg0 = fdp.getNextArg();
-                String arg1 = fdp.getNextArg();
-
-                File source = new File
-                    (Tema.getProperty("resource_base"), arg0);
-                File dest = new File(arg1);
-                
-                try {
-                    copyFile(source, dest);
-                } catch (IOException e) {
-                    System.err.println(e);
-                    return 0;
-                }
-                out.write(arg1);
-                return 1;
-            }
-        };
-
-    /* query:sql_query template arg1 arg2 ... argN */
-    public static final Function QUERY =
-        new Function("query") {
-           public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
-                
-                String arg0 = fdp.getNextArg();
-                String arg1 = fdp.getNextArg();
-		List<String> args = fdp.getArgs();				
-
-		TemplateParser tp = fdp.getTemplateParser();
-
-		try {
-		    return Tema.query
-			(arg1, (PreparedStatement)tp.getValue(arg0),
-			 args, tp, out);
-		} catch (SQLException e) {
-		    throw new TemplateException(e.getMessage(), e);
-		}
-            }
-        };
-
-    public static final Function WRITE =
-        new Function("write") {
-           public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
-
-                String arg0 = fdp.getNextArg();
-
-		System.err.println("Writing " + arg0 + "...");
-
-                Writer fw;
+    // TODO: arithmetics, conditions
 
-                try {
-		    fw = Tema.createFileWriter(arg0);
-                } catch (IOException e) {
-                    System.err.println(e);
-                    return 0;
-                }
-                
-                try {
-		    fdp.parseData(fw);
-                } finally {
-                    fw.close();
-                }
-
-                out.write(arg0);
-		
-		System.err.println("Saved " + arg0 + ".");
-                return 1;
-            }
-        };
-
-    public static final Function READ =
-        new Function("read") {
-           public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
+    /**
+     * Register all standard functions.
+     *
+     * @param p TemplateParser to register functions in.
+     */
+    static void registerAllFunctions(TemplateParser p) {
+        p.registerFunction("!",          Standard.NULL_OUTPUT);
+        p.registerFunction("set",        Standard.SET);
+        p.registerFunction("load",       Standard.LOAD);
+        p.registerFunction("query",      Standard.QUERY);
+        p.registerFunction("prepare",    Standard.PREPARE);
+        p.registerFunction("replace",    Standard.REPLACE);
+        p.registerFunction("xml_escape", Standard.XML_ESCAPE);
+        p.registerFunction("xml_cdata",  Standard.XML_CDATA);
 
-	       String filename = fdp.getData();
-	       String file;
-	       
-                try {
-                    file = Tema.readFile(new File(filename));
-                } catch (IOException e) {
-                    System.err.println(e);
-                    return 0;
-                }
-		
-                out.write(file);
-                return 1;
-            }
-        };
-
-    public static final Function INCLUDE =
-        new Function("include") {
-           public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
-                
-                String filename = fdp.getData();
-                
-                Reader fin = new BufferedReader
-		    (Tema.createCachedFileReader(new File(filename)));
-                
-                int r = fdp.getTemplateParser().parse(fin, out);
-
-                fin.close();
-                return r;
-            }
-        };
-
-    public static final Function NULL_OUTPUT =
-        new Function("!") {
-	    public int invoke(FunctionDataParser fdp, Writer out)
-		throws IOException, TemplateException {		
-
-		/* Write nothing. */
-		return fdp.parseData(new Writer() {
-                    public void close() {}
-                    public void flush() {}
-                    public void write(char[] cbuf, int off, int len) {}
-                });
-            }
-        };
-
-    public static final Function REPLACE =
-        new Function("replace") {
-	    public int invoke(FunctionDataParser fdp, Writer out)
-		throws IOException, TemplateException {		
-
-                String arg0 = fdp.getNextArg();
-                String arg1 = fdp.getNextArg();
+        p.registerFunction("define",     Define.DEFINE);
 
-                ReplaceWriter rw = new ReplaceWriter(out, arg0, arg1);
-
-                int ret = fdp.parseData(rw);
-                rw.finish();
-                return ret;
-            }
-        };
-
-    public static final Function XML_ESCAPE =
-        new Function("xml_escape") {
-	    public int invoke(FunctionDataParser fdp, Writer out)
-		throws IOException, TemplateException {		
-
-                final String[] chars = {"&", "<", ">", "`", "\""};
-                final String[] escape = {"&amp;", "&lt;", "&gt;", "&apos;", "&quot;"};
-
-                ReplaceWriter rw = new ReplaceWriter(out, chars, escape);
-
-                int ret = fdp.parseData(rw);
-                rw.finish();
-                return ret;
-            }
-        };
-
-    public static final Function XML_CDATA =
-        new Function("xml_cdata") {
-	    public int invoke(FunctionDataParser fdp, Writer out)
-		throws IOException, TemplateException {		
-
-                ReplaceWriter rw = new ReplaceWriter
-                    (out, "]]>", "]]]><![CDATA[]>", "<![CDATA[", "]]>");
-
-                int ret = fdp.parseData(rw);
-                rw.finish();
-                return ret;
-            }
-        };
-    
-    public static final Function DEFINE = new Define();
+        p.registerFunction("write",      IO.WRITE);
+        p.registerFunction("read",       IO.READ);
+        p.registerFunction("include",    IO.INCLUDE);
+        p.registerFunction("copy",       IO.COPY);
 
-    private static void copyFile(File src, File dest) throws IOException {
-	System.err.print("Copying " + src + "... ");
-	
-	if (src.lastModified() < dest.lastModified()) {
-	    System.err.println(dest + " is up to date.");
-	    return;
-	}
+	p.registerFunction("false",      Logics.FALSE);
+	p.registerFunction("true",       Logics.TRUE);
+        p.registerFunction("optional",   Logics.OPTIONAL);
 
-	File parent = dest.getParentFile();
-	if (parent != null) parent.mkdirs();
+        p.registerFunction("image",      ImageConverter.IMAGE);
 
-        InputStream in = new FileInputStream(src);
-        OutputStream out = new FileOutputStream(dest);
-        
-        byte[] buffer = new byte[1024];
-        int len;
-        while ((len = in.read(buffer)) > 0) {
-            out.write(buffer, 0, len);
-        }
-        in.close();
-        out.close();
-
-	System.err.println("saved " + dest + ".");
-    }
-
-    static void registerAllFunctions(TemplateParser p) {
-        p.registerFunction(GET);
-        p.registerFunction(SET);
-        p.registerFunction(OPTIONAL);
-        p.registerFunction(IMAGE);
-        p.registerFunction(COPY);
-        p.registerFunction(QUERY);
-        p.registerFunction(PREPARE);
-        p.registerFunction(WRITE);
-        p.registerFunction(READ);
-        p.registerFunction(INCLUDE);
-        p.registerFunction(NULL_OUTPUT);
-        p.registerFunction(REPLACE);
-        p.registerFunction(XML_ESCAPE);
-        p.registerFunction(XML_CDATA);
-        p.registerFunction(DEFINE);
+	/* 
+	   Other functuons:
+	   db - defined in Tema.query().
+	   nextarg, data - defined in Define.invoke().
+	*/
     }
 }
--- a/src/kryshen/tema/ImageConverter.java	Tue May 16 18:04:09 2006 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2005, 2006 Mikhail A. Kryshen
- *
- * $Id: ImageConverter.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
- */
-
-package kryshen.tema;
-
-import java.util.*;
-import java.io.*;
-import java.awt.*;
-import java.awt.image.*;
-import java.awt.geom.AffineTransform;
-import javax.imageio.*;
-import javax.imageio.stream.*;
-
-/**
- * Class defines static method to convert images to specified format.
- *
- * @author Mikhail A. Kryshen
- */
-class ImageConverter {    
-
-    static void convert(File source, File dest, String format,
-		 int maxWidth, int maxHeight) 
-	throws IOException, InterruptedException {
-	
-	System.err.print("Converting image " + source + "... ");
-
-	if (source.lastModified() < dest.lastModified()) {
-	    System.err.println(dest + " is up to date.");
-	    return;
-	}
-
-	BufferedImage image = ImageIO.read(source);
-
-	//int type = image.getType();
-	//final int type = BufferedImage.TYPE_INT_RGB;
-	//ColorModel cm = image.getColorModel();
-
-        int w = image.getWidth(null);
-        int h = image.getHeight(null);
-
-	//boolean scale = false;
-
-	float scale = 1f;
-
-	if (maxWidth > 0 && w > maxWidth)
-	    scale = (float)maxWidth / w;
-	
-	if (maxHeight > 0 && h * scale > maxHeight)
-	    scale = (float)maxHeight / h;
-
-	if (scale != 1f) {
-	    w *= scale; h *= scale;
-
-// 	    ColorModel cm = image.getColorModel();	    
-// 	    boolean alphaPremultiplied = cm.isAlphaPremultiplied();
-// 	    WritableRaster raster = cm.createCompatibleWritableRaster(w, h);
-// 	    BufferedImage scaled = new BufferedImage
-// 		(cm, raster, alphaPremultiplied, null);
-
-// 	    BufferedImage scaled = new BufferedImage(w, h, image.getType());
-	    
-
- 	    Image scaled = image.getScaledInstance(w, h, Image.SCALE_SMOOTH);
-
- 	    ColorModel cm = image.getColorModel();	    
- 	    boolean alphaPremultiplied = image.isAlphaPremultiplied();
- 	    WritableRaster raster = cm.createCompatibleWritableRaster(w, h);
- 	    image = new BufferedImage(cm, raster, alphaPremultiplied, null);
-
-	    Graphics g = image.getGraphics();
-	    g.drawImage(scaled, 0, 0, null);
-	    g.dispose();
-
-//	    image = scale(image, scaled, scale);
-//	    Graphics2D g = scaled.createGraphics();
-//	    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
-// 			       RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-// 	    System.err.print(" " + g.drawImage(image, 0, 0, w, h, null) + " ");
-// 	    g.dispose();
-// 	    image = scaled;
-	}
-       
-	File parent = dest.getParentFile();
-	if (parent != null) parent.mkdirs();
-
- 	ImageIO.write(image, format, dest);
-	System.err.println("saved " + dest + ".");	
-    }
-
-//     public static ColorModel getColorModel(Image image) 
-// 	throws InterruptedException {
-
-// 	PixelGrabber grabby = new PixelGrabber(image, 0, 0, 1, 1, false);
-// 	if (!grabby.grabPixels())
-// 	    throw new RuntimeException("pixel grab fails");
-// 	return grabby.getColorModel();
-//     }
-
-//     private static BufferedImage scale(BufferedImage image, 
-// 				       BufferedImage dest, 
-// 				       float scale) {
-
-// 	AffineTransform tx = new AffineTransform();
-// 	tx.scale(scale, scale);
-
-//  	AffineTransformOp op = new AffineTransformOp
-//  	    (tx, AffineTransformOp.TYPE_BILINEAR);
-
-// 	return op.filter(image, dest);
-//     }
-
-    // TEST
-    
-    public static void main(String[] args) 
-	throws IOException, InterruptedException {
-
-	convert(new File(args[0]), new File(args[1]), "png", 300, 300);
-    }
-
-
-//     public static BufferedImage toBufferedImage(Image image, ColorModel cm) {
-// 	if (image instanceof BufferedImage)
-// 	    return (BufferedImage)image;
-
-// 	int w = image.getWidth(null);
-// 	int h = image.getHeight(null);
-
-// 	boolean alphaPremultiplied = cm.isAlphaPremultiplied();
-// 	WritableRaster raster = cm.createCompatibleWritableRaster(w, h);
-// 	BufferedImage result = new BufferedImage(cm, raster, alphaPremultiplied, null);
-// 	Graphics2D g = result.createGraphics();
-
-// 	g.drawImage(image, 0, 0, null);
-// 	g.dispose();
-
-// 	return result;
-//     }
-}
-
-
-// This is how I'd recommend it in JDK1.4 using javax.imageio.ImageIO to
-// do the reading/writing and Java2D to do the scaling.
-
-// // read in the original image
-// BufferedImage in = ImageIO.read(new File("in.jpg"));
-
-// // create a new image of the smaller size
-// BufferedImage out = new BufferedImage(newWidth, newHeight);
-
-// // get the graphics associated with the new image
-// Graphics g = out.getGraphics();
-
-// // draw the original image into the new image, scaling
-// // on the fly
-// g.drawImage(in, newWidth, newHeight, null);
-
-// // dispose the graphics object
-// g.dispose();
-
-// // write out the new image
-// ImagIO.write("out.jpg", "jpeg", new File("out.jpg"));
-
-// ------------------------------------------
-
-// For nicer scaling, you could add a rendering hint to the graphics
-// object before painting.
-
-// ie.
-
-// Graphics2D g2d = (Graphics2D)g;
-// g2d.setRenderingHint(RenderingHints.KEY_INTEPOLATION,
-//                      RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-
-// or use VALUE_INTERPOLATION_BICUBIC for the best.
-
-// But as you can guess, nicer == slower.
-
-// Shannon Hickey
-// shann...@swingteam.com
-// Swing Team  http://TheSwingConnection.com  http://TheJavaTutorial.com
-// Java Software,  a division of Sun Microsystems, Inc. 
--- a/src/kryshen/tema/ReplaceWriter.java	Tue May 16 18:04:09 2006 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2006 Mikhail A. Kryshen
- *
- * $Id: ReplaceWriter.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
- */
-
-package kryshen.tema;
-
-import java.io.Writer;
-import java.io.FilterWriter;
-import java.io.IOException;
-
-import java.util.LinkedList;
-import java.util.Iterator;
-
-/**
- * FilterWriter which replaces characters with escape strings.
- *
- * @author Mikhail A. Kryshen
- */
-class ReplaceWriter extends FilterWriter {
-    private String[] patterns;
-    private String[] replaces;
-
-    private String prefix;
-    private String postfix;
-
-    private int maxPatternLength = 0;
-
-    /** Had this Writer processed any data? */
-    private boolean processedData = false;
-
-    private StringBuffer buffer = new StringBuffer();
-
-    public ReplaceWriter(Writer w, String pattern, String replace) {
-        this(w, new String[]{pattern}, new String[]{replace});
-    }
-
-    public ReplaceWriter(Writer w, String pattern, String replace,
-                         String prefix, String postfix) {
-
-        this(w, new String[]{pattern}, new String[]{replace}, 
-             prefix, postfix);
-    }
-
-    public ReplaceWriter(Writer w, String[] patterns, String[] replaces) {
-        this(w, patterns, replaces, null, null);
-    }
-
-    public ReplaceWriter(Writer w, String[] patterns, String[] replaces, 
-                         String prefix, String postfix) {
-	super(w);
-        
-	this.patterns = patterns;
-	this.replaces = replaces;
-        
-        this.prefix = prefix;
-        this.postfix = postfix;
-        
-	for (int i = 0; i < patterns.length; i++) {
-	    int length = patterns[i].length();
-	    if (length > maxPatternLength)
-		maxPatternLength = length;
-	}
-    }
-
-    private boolean processBuffer(int minLength) throws IOException {
-        if (buffer.length() == 0) return false;
-
-        if (!processedData) {
-            if (prefix != null)
-                super.write(prefix, 0, prefix.length());
-            processedData = true;
-        }
-
-        int k;
-	for (k = 0; minLength + k < buffer.length(); k++) {
-	    for (int i = 0; i < patterns.length; i++) {
-		String pattern = patterns[i];
-		int length = pattern.length();
-
-                if (length + k > buffer.length())
-                    continue;
-
-		boolean match = true;
-
-		for (int j = 0; j < length; j++) {
-		    if (pattern.charAt(j) != buffer.charAt(j + k)) {
-			match = false;
-                        break;
-		    }
-		}
-
-		if (match) {
-                    super.write(buffer.substring(0, k), 0, k);
-                    buffer.delete(0, k + length);
-                    super.write(replaces[i], 0, replaces[i].length());
-		    return true;
-		}
-	    }
-	}
-
-        super.write(buffer.substring(0, k), 0, k);
-        buffer.delete(0, k);
-
-        return false;
-    }
-
-    public void write(int c) throws IOException {
-	buffer.append((char)c);
-	processBuffer(maxPatternLength);
-    }
-
-    public void write(char[] cbuf, int off, int len) throws IOException {
-	for (int i = off; i < off + len; i++) {
-	    buffer.append(cbuf[i]);
-	}
-	processBuffer(maxPatternLength);
-    }
-
-    public void write(String str, int off, int len) throws IOException {
-        buffer.append(str.substring(off, off + len));
-	processBuffer(maxPatternLength);
-    }
-
-    public void finish() throws IOException {
-        while (processBuffer(0));
-
-//         super.write(buffer.toString(), 0, buffer.length());
-//         buffer.delete(0, buffer.length());
-
-        if (processedData && postfix != null)
-            super.write(postfix, 0, postfix.length());
-    }
-
-    public void close() throws IOException {
-        finish();
-        super.close();
-    }
-}
--- a/src/kryshen/tema/Tema.java	Tue May 16 18:04:09 2006 +0400
+++ b/src/kryshen/tema/Tema.java	Thu Dec 14 23:22:05 2006 +0300
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
  *
- * $Id: Tema.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: Tema.java,v 1.17 2006/12/14 19:44:31 mikhail Exp $
  */
 
 package kryshen.tema;
@@ -23,31 +37,135 @@
 
 import java.io.*;
 
+import kryshen.tema.demo.DemoFrame;
+
 /**
  * Tema main class.
  *
  * @author Mikhail A. Kryshen
  */
 public class Tema {
+    public static final String TITLE = "TEMA";
+    public static final String VERSION = "0.1";
+    public static final String AUTHOR = "Mikhail A. Kryshen";
+
     private static final String CONFIG_FILE = "tema.properties";
     private static final String ENV_PREFIX = "kryshen.tema.";
 
     private static Properties config = new Properties();
-    static Connection connection = null;
+    private static Connection connection = null;
 
     private static String inputEncoding;
     private static String outputEncoding;
 
-    static Map<File, String> fileCache = null;
+    private static Map<File, String> fileCache = null;
 
     public static void main(String[] args) 
-	throws IOException, TemplateException, SQLException,
+	throws IOException, SQLException,
 	       ClassNotFoundException, InstantiationException, 
 	       IllegalAccessException {
 	
+        boolean demo = false;
+        boolean version = false;
+        boolean help = false;
+
+        // Parse arguments.
+        for (int i = 0; i < args.length; i++) {
+            if ("-demo".equals(args[i]) || 
+                "-d".equals(args[i])) {
+                demo = true;
+            } else if ("-version".equals(args[i]) ||
+                       "-v".equals(args[i])) {
+                version = true;
+            } else if ("-help".equals(args[i]) ||
+                       "-h".equals(args[i]) ||
+                       "-usage".equals(args[i]) ||
+                       "-u".equals(args[i])) {
+                help = true;
+            } else {
+                System.err.println("Invalid option: " + args[i]);
+                return;
+            }
+        }
+
+        if (version || help) {
+            System.err.println(TITLE + " " + VERSION + " by " + AUTHOR);        
+
+            if (help) {
+                PrintStream err = System.err;
+                
+                err.println();
+                err.println("Usage: tema [OPTIONS]");
+                err.println();
+                err.println("Options:");
+                err.println("  -d[emo]             Demo mode");
+                err.println("  -v[ersion]          Print version");
+                err.println("  -h[help] -u[sage]   Print this help message");
+            }
+            return;
+        }
+
+        if (demo) {
+            // Run in demo mode.
+            DemoFrame df = new DemoFrame();
+            df.pack();
+            df.setVisible(true);
+            return;
+        }
+
+        process();
+    }
+
+    /**
+     * Standard execution.
+     */
+    private static void process()
+        throws IOException, SQLException,
+               ClassNotFoundException, InstantiationException, 
+               IllegalAccessException {
+
         InputStream configIn = new FileInputStream(CONFIG_FILE);
         config.load(configIn);
         configIn.close();
+        
+        configure();
+	
+	String main = getProperty("main_template");
+	String output = getProperty("output");
+	
+	Writer out;
+
+	if ("stderr".equals(output))
+	    out = new OutputStreamWriter(System.err);
+	else
+	    out = new OutputStreamWriter(System.out);
+
+        TemplateReader in = createTemplateReader(new File(main));
+        TemplateParser p = new TemplateParser();
+        TemplateException te = null;
+
+	try {
+	    p.parse(in, out);
+        } catch (TemplateException e) {
+            te = e;
+	} finally {
+	    in.close();
+	    out.flush();
+	}  
+
+        if (te != null) {
+            System.err.println(te.getMessage());
+            if (te.getCause() != null) {
+                System.err.println("Caused by " + te.getCause());
+            }
+        } else {
+            System.err.println("Done");
+        }
+    }
+
+    private static void configure() 
+        throws IOException, SQLException, IllegalAccessException,
+               ClassNotFoundException, InstantiationException {
 
         String logFile = getProperty("log");
         if (logFile != null && logFile.length() > 0)
@@ -71,51 +189,14 @@
 	    // Open connection.
 	    connection = DriverManager.getConnection(resourceProperty);
 	}
-	
-// 	String maxFilesString = getProperty("max_output_files");
-// 	int maxFiles = -1;
-// 	if (maxFilesString != null)
-// 	    maxFiles = Integer.parseInt(maxFilesString);
-
-	String main = getProperty("main_template");
-	String output = getProperty("output");
-	
-	Writer out;
-
-	if ("stderr".equals(output))
-	    out = new OutputStreamWriter(System.err);
-	else
-	    out = new OutputStreamWriter(System.out);
-
-	InputStream is = new FileInputStream(main);
-        Reader in = new BufferedReader(new InputStreamReader(is, inputEncoding));
-
-        TemplateParser p = new TemplateParser();
-	try {
-	    p.parse(in, out);
-	} finally {
-	    in.close();
-	    out.flush();
-	}  
-
-	System.err.println("Done");
     }
 
-//     private static void readEscapes() {
-// 	for (int i = 1;; i++) {
-// 	    String pattern = getProperty("escape_char_" + i);
-
-// 	    if (pattern == null) break;
-
-// 	    escapes.put(pattern.charAt(0),
-// 			getProperty("escape_string_" + i));
-// 	}
-//     }
+    
 
     /**
      * Get configuration property.
      */
-    static String getProperty(String name) {
+    public static String getProperty(String name) {
         String value = System.getProperty(ENV_PREFIX + name);
         if (value != null) return value;
         return config.getProperty(name);
@@ -124,16 +205,17 @@
     /**
      * Get configuration property.
      */
-    static String getProperty(String name, String fallback) {
+    public static String getProperty(String name, String fallback) {
 	String value = getProperty(name);
 	if (value != null) return value;
 	return fallback;
     }
 
     /**
-     * Process subquery and template.
+     * Process database query.
      *
-     * @param template template to fill with data.
+     * @param templateReader reader for the template 
+     *                       to fill with data.
      * @param ps query statement to execute.
      * @param args list of query parameters.
      * @param superParser invoking object.
@@ -141,14 +223,13 @@
      *
      * @return number of processed rows in query result.
      */
-    static int query(String template, PreparedStatement ps,
-		     List<String> args,
-		     TemplateParser superParser, 
-                     Writer out)
+    public static int query(TemplateReader templateReader,
+                            PreparedStatement ps,
+                            List<String> args,
+                            TemplateParser superParser, 
+                            Writer out)
 	throws IOException, SQLException, TemplateException {
 
-	StringReader templateReader = new StringReader(template);
-
 	int i = 1;
 
 	for (String arg : args) {
@@ -165,21 +246,34 @@
 	    names[j] = rm.getColumnName(j + 1);
 	}
 
-	TemplateParser p = new TemplateParser(superParser);
-	
+	final TemplateParser p = new TemplateParser(superParser);
+	final HashMap<String, Object> fields = new HashMap<String, Object>();
+
+        p.registerFunction("db", new Function() {
+                public int invoke(FunctionDataParser fdp, 
+                                  final Writer out)
+                    throws IOException, TemplateException {
+                    
+                    String name = fdp.getData();
+                    Object value = fields.get(name);
+		    
+                    return p.parseValue(value, out);
+                }
+            });
+
 	for (i = 1; r.next(); i++) {
-	    p.clearValues();
 
 	    for (int j = 0; j < columnCount; j++) {
 		Object value = r.getObject(j + 1);
 		if (r.wasNull()) value = null;
-
-		p.setValue(names[j], value);
+                
+		fields.put(names[j], value);
 	    }
 
-	    p.setValue("NUMBER", i);
+	    p.setValue("number", i);
             p.parse(templateReader, out);
 	    templateReader.reset();
+            fields.clear();
 	}
 
 	r.close();
@@ -187,32 +281,56 @@
 	return i - 1;
     }
 
-    static Writer createFileWriter(String filename) throws IOException {
+    public static BufferedWriter createFileWriter(String filename) 
+        throws IOException {
+
 	OutputStream os = new FileOutputStream(filename);
-	return new OutputStreamWriter(os, Tema.outputEncoding);
+	return new BufferedWriter
+            (new OutputStreamWriter(os, Tema.outputEncoding));
+    }
+
+    public static BufferedReader createFileReader(File file)
+        throws IOException {
+
+	InputStream is = new FileInputStream(file);
+	return new BufferedReader
+            (new InputStreamReader(is, Tema.inputEncoding));
     }
 
-    static Reader createFileReader(File file) throws IOException {
-	InputStream is = new FileInputStream(file);
-	return new InputStreamReader(is, Tema.inputEncoding);
+    public static TemplateReader createTemplateReader(File file)
+        throws IOException {
+
+        return new TemplateReader
+            (new LineNumberReader(createCachedFileReader(file)), file.getPath());
     }
 
-    static Reader createCachedFileReader(File file) throws IOException {
+    /**
+     * Creates StringReader for a cached file if caching is enabled or
+     * a BufferedReader.
+     *
+     * @param file File to read.
+     * @return Reader for a file.
+     */
+    public static Reader createCachedFileReader(File file) 
+        throws IOException {
+        
 	if (fileCache != null) return new StringReader(readFile(file));
 	else return createFileReader(file);
     }
 
     /**
      * Read text file.
+     *
+     * @return file contents.
      */
-    static String readFile(File file) throws IOException {
+    public static String readFile(File file) throws IOException {
 	String data;
-
+        
 	if (fileCache != null) {
 	    data = fileCache.get(file);	    
 	    if (data != null) return data;
 	}
-
+        
         Reader r = new BufferedReader(createFileReader(file));
 
         // FIXME: possible overflow (long -> int).
@@ -234,4 +352,13 @@
 
 	return data;
     }
+
+    /**
+     * Get database connection.
+     *
+     * @return database connection if any was configured.
+     */
+    public static Connection getDbConnection() {
+        return connection;
+    }
 }
--- a/src/kryshen/tema/TemplateException.java	Tue May 16 18:04:09 2006 +0400
+++ b/src/kryshen/tema/TemplateException.java	Thu Dec 14 23:22:05 2006 +0300
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
  *
- * $Id: TemplateException.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: TemplateException.java,v 1.5 2006/12/14 14:39:22 mikhail Exp $
  */
 
 package kryshen.tema;
@@ -12,13 +26,36 @@
  * @author Mikhail A. Kryshen
  */
 public class TemplateException extends Exception {
-    // TODO: store the number of template line with error.
+    //private String source;
+    //private int line;
+
+//     public TemplateException(String message) {
+// 	super(message);
+//     }
 
-    public TemplateException(String message) {
-	super(message);
+//     public TemplateException(String message, Throwable cause) {
+// 	super(message, cause);
+//     }
+
+    public TemplateException(String message, Throwable cause,
+                             TemplateReader tr) {
+        this(message, cause, tr.getSource(), tr.getLineNumber() + 1);
     }
 
-    public TemplateException(String message, Throwable cause) {
-	super(message, cause);
+    public TemplateException(String message, TemplateReader tr) {
+        this(message, tr.getSource(), tr.getLineNumber() + 1);
+    }
+
+    public TemplateException(String message, Throwable cause,
+                             String source, int line) {
+        super(source + ":" + line + ": " + message, cause);
+        //this.source = source;
+        //this.line = line;
+    }
+    
+    public TemplateException(String message, String source, int line) {
+        super(source + ":" + line + ": " + message);
+        //this.source = source;
+        //this.line = line;
     }
 }
--- a/src/kryshen/tema/TemplateParser.java	Tue May 16 18:04:09 2006 +0400
+++ b/src/kryshen/tema/TemplateParser.java	Thu Dec 14 23:22:05 2006 +0300
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
  *
- * $Id: TemplateParser.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: TemplateParser.java,v 1.11 2006/12/14 19:44:31 mikhail Exp $
  */
 
 package kryshen.tema;
@@ -10,12 +24,14 @@
 import java.util.*;
 
 /**
- * Parser for DbReader templates.
+ * Parser for TEMA templates.
  *
  * @author Mikhail A. Kryshen
  */
 public class TemplateParser {
-    static final String SUPER = "SUPER.";
+    // TODO: report non-critical errors and warnings.
+
+    static final String SUPER_PREFIX = "super.";
 
     /* Brackets. */
     static final char[] BR_LEFT = {'<', '['};
@@ -28,6 +44,9 @@
     static final char[] NONREC_DATA_SEPARATORS = {'\\', '`'}; 
     static final char[] LIST_SEPARATORS = {' ', '\t', '\r', '\n'};
 
+    /**
+     * Methods of parsing function arguments and data.
+     */
     static enum FunctionData {
 	SUBFUNCTION, RECURSIVE, NONRECURSIVE;
     };
@@ -38,24 +57,22 @@
 
     static class Result {
 	Terminator terminator;
-	int substitutions;
+	int retCode;
 
         /** No text had been parsed. */
         boolean empty; 
 
-	Result(Terminator terminator, int substitutions, boolean empty) {
+	Result(Terminator terminator, int retCode, boolean empty) {
             this.terminator = terminator;
-            this.substitutions = substitutions;
+            this.retCode = retCode;
             this.empty = empty;
         }
 
         Result() {
-            this(null, 0, true);
+            this(null, -1, true);
         }       
     };    
 
-    private Map<String, Function> functions = new HashMap<String, Function>();
-
     private Map<String, Object> variables = new HashMap<String, Object>();
     private TemplateParser superParser;
 
@@ -71,26 +88,26 @@
         Functions.registerAllFunctions(this);
     }
 
-    public void registerFunction(Function f) {
-        functions.put(f.name, f);
+    public void registerFunction(String name, Function f) {
+        variables.put(name, f);
     }
 
-    public int parse(Reader in, Writer out) 
+    public int parse(TemplateReader in, Writer out) 
 	throws IOException, TemplateException {
 	
-	return parse(in, out, true, null, null).substitutions;
+	return parse(in, out, true, null, null).retCode;
     }
 
     /**
      *  Parse template.
      *
-     * @param in Reader to read template data.
+     * @param in TemplateReader to read template data.
      * @param out Writer to write processed data.
      * @param recursive enables recursive processing.
      * @param separators characters which terminate parsing block.
      * @param leading characters to ignore at the beginning of the block.
      */
-    Result parse(Reader in, Writer out, boolean recursive, 
+    Result parse(TemplateReader in, Writer out, boolean recursive, 
                  char[] separators, char[] leading)
 	throws IOException, TemplateException {
 
@@ -117,10 +134,13 @@
 
 	    if (recursive && leftBracket && c == BR_IN) {
 		
+                if (result.retCode < 0)
+                    result.retCode = 0;
+
 		lc = -1;
                 int tb = termBracket;
                 termBracket = BR_RIGHT[index];
-		result.substitutions += parseFunction(in, out);
+		result.retCode += parseFunction(in, out);
                 termBracket = tb;
 		
 	    } else if (lc == BR_IN && c == termBracket) {
@@ -151,22 +171,6 @@
 	return result;
     }
 
-    int parseVariable(String name, Writer out) 
-	throws IOException, TemplateException {
-
-        Object ovalue = getValue(name);
-        if (ovalue == null)
-            return 0;
-
-	String svalue = ovalue.toString();
-
-	if (svalue == null || svalue.length() == 0)
-	    return 0;
-       
-	out.write(svalue);
-	return 1;
-    }
-
     boolean isSeparator(int c, char[] separators) {
         for (char p : separators) {
             if (c == p) return true;
@@ -175,7 +179,7 @@
         return false;
     }
 
-    int parseFunction(Reader in, Writer out)
+    int parseFunction(TemplateReader in, Writer out)
 	throws IOException, TemplateException {
         
 	StringBuffer sb = new StringBuffer();
@@ -196,16 +200,28 @@
 				      FunctionData.NONRECURSIVE, 
 				      in, out);
             } else if (c < 0) {
-		System.err.println("Error: unexpected end of file.");
-		return 0;
+		throw new TemplateException("Unexpected end of file.", in);
             } else {
 		sb.append((char)c);
 	    }
 	}
     }
 
+    int parseValue(Object value, Writer out) throws IOException {
+        if (value == null)
+            return 0;
+
+        String svalue = value.toString();
+        
+        if (svalue == null || svalue.length() == 0)
+            return 0;
+
+        out.write(svalue);
+        return 1;
+    }
+
     private int invokeFunction(String name, FunctionData fd,
-			       Reader in, Writer out)
+			       TemplateReader in, Writer out)
 	throws IOException, TemplateException {
 
         FunctionDataParser fdp = new FunctionDataParser(this, fd, in);
@@ -214,14 +230,15 @@
             return fdp.parseData(out);
 	}
         
-        Function f = getFunction(name);
+        Object value = getValue(name);
+        int r;
 
-        if (f == null) {
-	    throw new TemplateException("Unknown function: " + name);
+        if (value instanceof Function) {            
+            r = ((Function) value).invoke(fdp, out);           
+        } else { 
+            r = parseValue(value, out);
         }
 
-        int r = f.invoke(fdp, out);
-
 	if (fdp.hasMoreData()) {
 	    /* Skip remaining function data. */
 	    fdp.parseData(new Writer() {
@@ -234,27 +251,14 @@
 	return r;
     }
 
-    Function getFunction(String name) throws TemplateException {
-	Function function = functions.get(name);
-	if (function != null) return function;   
-
-	if (superParser != null) {
-	    if (name.startsWith(SUPER))
-		return superParser.getFunction(name.substring(SUPER.length()));
-	    else
-		return superParser.getFunction(name);
-	}
-
-	return null;
-    }
-
-    Object getValue(String name) throws TemplateException {
+    public Object getValue(String name) throws TemplateException {
 	Object value = variables.get(name);
 	if (value != null) return value;   
 
 	if (superParser != null) {
-	    if (name.startsWith(SUPER))
-		return superParser.getValue(name.substring(SUPER.length()));
+	    if (name.startsWith(SUPER_PREFIX))
+		return superParser.getValue
+                    (name.substring(SUPER_PREFIX.length()));
 	    else
 		return superParser.getValue(name);
 	}
@@ -262,21 +266,26 @@
 	return null;
     }
 
-    void setValue(String name, Object value) {
+    public void setValue(String name, Object value) {
 	variables.put(name, value);
     }
 
-    void clearValues() {
+    public void clearValues() {
 	variables.clear();
+        Functions.registerAllFunctions(this);
     }
 
     // TEST
-    public static void main(String[] args) 
+    public static void main(String[] args)
 	throws IOException, TemplateException {
 	
 	TemplateParser p = new TemplateParser();
 	
-	FileReader in = new FileReader("parser.in");
+	TemplateReader in = 
+            new TemplateReader
+            (new LineNumberReader
+             (new FileReader("parser.in")), "parser.in");
+
 	FileWriter out = new FileWriter("parser.out");
 
 	p.parse(in, out);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/kryshen/tema/TemplateReader.java	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,64 @@
+/*
+ *  Copyright (C) 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: TemplateReader.java,v 1.5 2006/12/14 14:39:22 mikhail Exp $
+ */
+
+package kryshen.tema;
+
+import java.io.Reader;
+import java.io.LineNumberReader;
+import java.io.FilterReader;
+import java.io.IOException;
+
+/**
+ * Reader for TEMA templates. Stores data source name (commonly
+ * filename) and tracks line numbers for error reporting.
+ *
+ * @author Mikhail A. Kryshen
+ */
+public class TemplateReader extends FilterReader {
+    private String source = null;
+
+    private LineNumberReader lnReader = null;
+    private TemplateReader parentReader = null;
+
+    public TemplateReader(LineNumberReader in, String source) {
+        super(in);
+        this.lnReader = in;
+        this.source = source;
+    }
+
+    public TemplateReader(Reader in, TemplateReader parent) {
+        super(in);
+        this.source = source;
+    }
+
+    public String getSource() {
+        if (source != null)
+            return source;
+        
+        return parentReader.getSource();
+    }
+    
+    public int getLineNumber() {
+        if (lnReader != null)
+            return lnReader.getLineNumber();
+        
+        return parentReader.getLineNumber();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/kryshen/tema/demo/DemoFrame.java	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,181 @@
+/*
+ *  Copyright (C) 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: DemoFrame.java,v 1.3 2006/12/14 15:59:48 mikhail Exp $
+ */
+
+package kryshen.tema.demo;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.URL;
+
+import kryshen.tema.*;
+
+/**
+ * TEMA demonstation console.
+ */
+public class DemoFrame extends Frame {
+    private TextArea input;
+    private TextArea output;
+    private TextArea error;
+    
+    public DemoFrame() {
+        super("TEMA demo console");
+
+        GridBagLayout gbl = new GridBagLayout();
+
+        GridBagConstraints c = new GridBagConstraints();
+
+        setLayout(gbl);
+
+        input = new TextArea(25, 40);
+        input.setEditable(true);
+
+        output = new TextArea(25, 40);
+        output.setEditable(false);
+
+        error = new TextArea(5, 80);
+        error.setEditable(false);
+
+        Label l;
+
+        c.insets = new Insets(3, 3, 3, 3);
+        c.fill = GridBagConstraints.BOTH;
+        c.gridwidth = GridBagConstraints.RELATIVE;
+
+        l = new Label("Enter your code and click \"Process\".");
+
+        gbl.setConstraints(l, c);
+        add(l);
+
+        Panel buttons = new Panel();
+        buttons.setLayout(new FlowLayout(FlowLayout.LEFT));
+        
+        Button process = new Button("Process");
+        Button clear = new Button("Clear");
+        Button close = new Button("Close");
+
+        buttons.add(process);
+        buttons.add(clear);
+        buttons.add(close);
+
+        c.gridwidth = GridBagConstraints.REMAINDER; 
+
+        gbl.setConstraints(buttons, c);
+        add(buttons);
+
+        c.fill = GridBagConstraints.BOTH;
+        c.anchor = GridBagConstraints.CENTER;
+        c.gridwidth = GridBagConstraints.RELATIVE;
+
+        l = new Label("Input:");
+
+        gbl.setConstraints(l, c);
+        add(l);
+
+        c.gridwidth = GridBagConstraints.REMAINDER; 
+
+        l = new Label("Output:");
+
+        gbl.setConstraints(l, c);
+        add(l);
+
+        c.gridwidth = GridBagConstraints.RELATIVE; 
+
+        gbl.setConstraints(input, c);
+        add(input);
+       
+        c.gridwidth = GridBagConstraints.REMAINDER; 
+
+        gbl.setConstraints(output, c);
+        add(output);
+
+        l = new Label("Errors:");
+
+        gbl.setConstraints(l, c);
+        add(l);
+
+        gbl.setConstraints(error, c);
+        add(error);      
+
+        process.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    process();
+                }
+            });
+
+        clear.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    error.setText("");
+                    output.setText("");
+                    input.setText("");
+                    
+                }
+            });
+
+        close.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    dispose();
+                }
+            });
+
+        addWindowListener(new WindowAdapter() {
+                public void windowClosing(WindowEvent e) {
+                    dispose();
+                }
+            });
+        
+        URL source = getClass().getResource("demo.template");
+        StringBuffer buffer = new StringBuffer();
+
+        try {
+            Reader in = new BufferedReader
+                (new InputStreamReader(source.openStream(), "UTF-8"));           
+            
+            for (int ch = in.read(); ch >= 0; ch = in.read()) {
+                buffer.append((char)ch);
+            }
+
+            in.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        
+        input.setText(buffer.toString());
+    }
+
+    private void process() {
+        String s = input.getText();
+        
+        TemplateReader tr = new TemplateReader
+            (new LineNumberReader(new StringReader(s)), "input");
+        TemplateParser tp = new TemplateParser();
+        StringWriter sw = new StringWriter();
+
+        error.setText("");
+
+        try {
+            tp.parse(tr, sw);
+        } catch (Exception e) {
+            error.setText(e.toString());
+        }
+
+        output.setText(sw.toString());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/kryshen/tema/demo/Hello.java	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,43 @@
+/*
+ *  Copyright (C) 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: Hello.java,v 1.2 2006/12/14 14:39:24 mikhail Exp $
+ */
+
+package kryshen.tema.demo;
+
+import java.io.Writer;
+import java.io.IOException;
+import kryshen.tema.*;
+
+/**
+ * Example function implementation.
+ * 
+ * @author Mikhail A. Kryshen
+ */
+public class Hello extends Function {
+    public Hello() {}
+
+    public int invoke(FunctionDataParser fdp, Writer out)
+        throws IOException, TemplateException {
+        
+        out.write("Hello, ");
+        fdp.parseData(out);
+
+        return 1;
+    }
+}
--- a/src/kryshen/tema/functions/Define.java	Tue May 16 18:04:09 2006 +0400
+++ b/src/kryshen/tema/functions/Define.java	Thu Dec 14 23:22:05 2006 +0300
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
  *
- * $Id: Define.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: Define.java,v 1.7 2006/12/14 14:35:00 mikhail Exp $
  */
 
 package kryshen.tema.functions;
@@ -11,9 +25,15 @@
 
 import kryshen.tema.*;
 
+/**
+ * Define function implementation.
+ *
+ * @author Mikhail A. Kryshen
+ */
 public class Define extends Function {
+    public static final Function DEFINE = new Define();
+
     public Define() {
-        super("define");
     }
 
     public int invoke(FunctionDataParser fdp, Writer out)
@@ -22,37 +42,42 @@
         final String arg0 = fdp.getNextArg();
         final String data = fdp.getData();
 
-        Function newFunction = new Function(arg0) {
+        Function newFunction = new Function() {
                 public int invoke(final FunctionDataParser fdp, final Writer out)
                     throws IOException, TemplateException {
-
+		    
                     TemplateParser functionParser = 
                         new TemplateParser(fdp.getTemplateParser());
                     
-                    functionParser.registerFunction(new Function("nextarg") {
-                            public int invoke(FunctionDataParser unusedFdp, 
-                                              final Writer out)
-                                throws IOException, TemplateException {
-
-                                return fdp.parseNextArg(out);
-                            }
-                        });
+                    functionParser.registerFunction
+			("nextarg", new Function() {
+				public int invoke(FunctionDataParser unusedFdp, 
+						  final Writer out)
+				    throws IOException, TemplateException {
+				    
+				    return fdp.parseNextArg(out);
+				}
+			    });
+		    
+                    functionParser.registerFunction
+			("data",new Function() {
+				public int invoke(FunctionDataParser unusedFdp, 
+						  final Writer out)
+				    throws IOException, TemplateException {
+				    
+				    return fdp.parseData(out);
+				}
+			    });
+		    
+                    TemplateReader dataReader = new TemplateReader
+                        (new StringReader(data), fdp.getTemplateReader());
 
-                    functionParser.registerFunction(new Function("data") {
-                            public int invoke(FunctionDataParser unusedFdp, 
-                                              final Writer out)
-                                throws IOException, TemplateException {
-
-                                return fdp.parseData(out);
-                            }
-                        });
-
-                    return functionParser.parse(new StringReader(data), out);                                       
+                    return functionParser.parse(dataReader, out);
                 }
             };
-
-        fdp.getTemplateParser().registerFunction(newFunction);
-
+	
+        fdp.getTemplateParser().registerFunction(arg0, newFunction);
+	
         out.write(arg0);
         return 1;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/kryshen/tema/functions/IO.java	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,163 @@
+/*
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: IO.java,v 1.4 2006/12/14 15:59:49 mikhail Exp $
+ */
+
+package kryshen.tema.functions;
+
+import java.io.*;
+import java.util.*;
+
+import kryshen.tema.*;
+
+/**
+ * I/O functions.
+ */
+public class IO {
+    public static final Function COPY =
+        new Function() {
+           public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+
+                String arg0 = fdp.getNextArg();
+                String arg1 = fdp.getNextArg();
+
+                File source = new File
+                    (Tema.getProperty("resource_base"), arg0);
+                File dest = new File(arg1);
+                
+                try {
+                    copyFile(source, dest);
+                } catch (IOException e) {
+                    System.err.println(e);
+                    return 0;
+                }
+                out.write(arg1);
+                return 1;
+            }
+        };
+
+    public static final Function WRITE =
+        new Function() {
+           public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+
+                String arg0 = fdp.getNextArg();
+
+		System.err.println("Writing " + arg0 + "...");
+
+                Writer fw;
+
+                try {
+		    fw = Tema.createFileWriter(arg0);
+                } catch (IOException e) {
+                    System.err.println(e);
+                    //throw new TemplateException(e.getMessage(), e, in);
+                    return 0;
+                }
+
+                try {
+		    fdp.parseData(fw);
+                } finally {
+                    fw.close();
+                }
+
+                out.write(arg0);
+
+		System.err.println("Saved " + arg0 + ".");
+                return 1;
+            }
+        };
+
+    public static final Function READ =
+        new Function() {
+           public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+
+	       String filename = fdp.getData();
+	       String file;
+	       
+                try {
+                    file = Tema.readFile(new File(filename));
+                } catch (IOException e) {
+                    System.err.println(e);
+                    //throw new TemplateException(e.getMessage(), e, in);
+                    return 0;
+                }
+		
+                out.write(file);
+                return 1;
+            }
+        };
+
+    public static final Function INCLUDE =
+        new Function() {
+           public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+                
+                String filename = fdp.getData();
+                
+                TemplateReader fin;
+
+                try {
+                    fin = Tema.createTemplateReader(new File(filename));
+                } catch (IOException e) {
+                    throw new TemplateException(e.getMessage(), e,
+                                                fdp.getTemplateReader());
+                }
+
+                int r = fdp.getTemplateParser().parse(fin, out);
+
+                fin.close();
+                return r;
+            }
+        };
+
+    /**
+     * Update file (copy if newer).
+     *
+     * @param src source file.
+     * @param dest destination file.
+     *
+     * @trows IOException on copying error.
+     */
+    private static void copyFile(File src, File dest) throws IOException {
+	System.err.print("Copying " + src + "... ");
+	
+	if (src.lastModified() < dest.lastModified()) {
+	    System.err.println(dest + " is up to date.");
+	    return;
+	}
+
+	File parent = dest.getParentFile();
+	if (parent != null) parent.mkdirs();
+
+        InputStream in = new FileInputStream(src);
+        OutputStream out = new FileOutputStream(dest);
+        
+        byte[] buffer = new byte[1024];
+        int len;
+        while ((len = in.read(buffer)) > 0) {
+            out.write(buffer, 0, len);
+        }
+        in.close();
+        out.close();
+
+	System.err.println("saved " + dest + ".");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/kryshen/tema/functions/ImageConverter.java	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,188 @@
+/*
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: ImageConverter.java,v 1.4 2006/12/14 19:44:32 mikhail Exp $
+ */
+
+package kryshen.tema.functions;
+
+import java.util.*;
+import java.io.*;
+import java.awt.*;
+import java.awt.image.*;
+import java.awt.geom.AffineTransform;
+import javax.imageio.*;
+import javax.imageio.stream.*;
+
+import kryshen.tema.*;
+
+/**
+ * Convert images to specified format.
+ *
+ * @author Mikhail A. Kryshen
+ */
+public class ImageConverter extends Function {    
+
+    public static final Function IMAGE = new ImageConverter();
+
+    public int invoke(FunctionDataParser fdp, Writer out)
+        throws IOException, TemplateException {
+        
+        String arg0 = fdp.getNextArg();
+        String arg1 = fdp.getNextArg();
+        String arg2 = fdp.getNextArg();
+        int arg3 = fdp.hasMoreData() ? 
+            Integer.parseInt(fdp.getNextArg()) : -1;
+        int arg4 = fdp.hasMoreData() ? 
+            Integer.parseInt(fdp.getNextArg()) : -1;
+        
+        File source = new File
+            (Tema.getProperty("resource_base"), arg0);
+        
+        try {
+            convert
+                (source,                  /* source file */
+                 new File(arg1),          /* dest file */
+                 arg2,                    /* format */
+                 arg3,                    /* max width */
+                 arg4);                   /* max height */
+        } catch (Exception e) {
+            System.err.println(e);
+            return 0;
+        }
+        
+        out.write(arg1);
+        return 1;
+    }    
+    
+    public static void convert(File source, File dest, String format,
+                               int maxWidth, int maxHeight) 
+	throws IOException, InterruptedException {
+	
+	System.err.print("Converting image " + source + "... ");
+        
+	if (source.lastModified() < dest.lastModified()) {
+	    System.err.println(dest + " is up to date.");
+	    return;
+	}
+
+	BufferedImage image = ImageIO.read(source);
+
+	//int type = image.getType();
+	//final int type = BufferedImage.TYPE_INT_RGB;
+	//ColorModel cm = image.getColorModel();
+
+        int w = image.getWidth(null);
+        int h = image.getHeight(null);
+
+	//boolean scale = false;
+
+	float scale = 1f;
+
+	if (maxWidth > 0 && w > maxWidth)
+	    scale = (float)maxWidth / w;
+	
+	if (maxHeight > 0 && h * scale > maxHeight)
+	    scale = (float)maxHeight / h;
+
+	if (scale != 1f) {
+	    w *= scale; h *= scale;
+
+// 	    ColorModel cm = image.getColorModel();	    
+// 	    boolean alphaPremultiplied = cm.isAlphaPremultiplied();
+// 	    WritableRaster raster = cm.createCompatibleWritableRaster(w, h);
+// 	    BufferedImage scaled = new BufferedImage
+// 		(cm, raster, alphaPremultiplied, null);
+
+// 	    BufferedImage scaled = new BufferedImage(w, h, image.getType());
+	    
+
+ 	    Image scaled = image.getScaledInstance(w, h, Image.SCALE_SMOOTH);
+
+ 	    ColorModel cm = image.getColorModel();	    
+ 	    boolean alphaPremultiplied = image.isAlphaPremultiplied();
+ 	    WritableRaster raster = cm.createCompatibleWritableRaster(w, h);
+ 	    image = new BufferedImage(cm, raster, alphaPremultiplied, null);
+
+	    Graphics g = image.getGraphics();
+	    g.drawImage(scaled, 0, 0, null);
+	    g.dispose();
+
+//	    image = scale(image, scaled, scale);
+//	    Graphics2D g = scaled.createGraphics();
+//	    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+// 			       RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+// 	    System.err.print(" " + g.drawImage(image, 0, 0, w, h, null) + " ");
+// 	    g.dispose();
+// 	    image = scaled;
+	}
+       
+	File parent = dest.getParentFile();
+	if (parent != null) parent.mkdirs();
+
+ 	ImageIO.write(image, format, dest);
+	System.err.println("saved " + dest + ".");	
+    }
+
+//     public static ColorModel getColorModel(Image image) 
+// 	throws InterruptedException {
+
+// 	PixelGrabber grabby = new PixelGrabber(image, 0, 0, 1, 1, false);
+// 	if (!grabby.grabPixels())
+// 	    throw new RuntimeException("pixel grab fails");
+// 	return grabby.getColorModel();
+//     }
+
+//     private static BufferedImage scale(BufferedImage image, 
+// 				       BufferedImage dest, 
+// 				       float scale) {
+
+// 	AffineTransform tx = new AffineTransform();
+// 	tx.scale(scale, scale);
+
+//  	AffineTransformOp op = new AffineTransformOp
+//  	    (tx, AffineTransformOp.TYPE_BILINEAR);
+
+// 	return op.filter(image, dest);
+//     }
+
+    // TEST    
+    public static void main(String[] args) 
+	throws IOException, InterruptedException {
+
+	convert(new File(args[0]), new File(args[1]), "png", 300, 300);
+    }
+
+
+//     public static BufferedImage toBufferedImage(Image image, ColorModel cm) {
+// 	if (image instanceof BufferedImage)
+// 	    return (BufferedImage)image;
+
+// 	int w = image.getWidth(null);
+// 	int h = image.getHeight(null);
+
+// 	boolean alphaPremultiplied = cm.isAlphaPremultiplied();
+// 	WritableRaster raster = cm.createCompatibleWritableRaster(w, h);
+// 	BufferedImage result = new BufferedImage(cm, raster, alphaPremultiplied, null);
+// 	Graphics2D g = result.createGraphics();
+
+// 	g.drawImage(image, 0, 0, null);
+// 	g.dispose();
+
+// 	return result;
+//     }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/kryshen/tema/functions/Logics.java	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,75 @@
+/*
+ *  Copyright (C) 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: Logics.java,v 1.6 2006/12/14 14:39:26 mikhail Exp $
+ */
+
+package kryshen.tema.functions;
+
+import java.io.*;
+import java.util.*;
+
+import kryshen.tema.*;
+
+/**
+ * Logical and conditional functions.
+ *
+ * @author Mikhail A. Kryshen
+ */
+public class Logics {
+    public static final Function FALSE =
+        new Function() {
+            public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+		
+		fdp.parseData(out);
+                return 0;
+            }
+        };
+
+    public static final Function TRUE =
+        new Function() {
+            public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+		
+		fdp.parseData(out);
+                return 1;
+            }
+        };
+
+    /**
+     * Outputs it's data it has non-zero value.
+     */
+    public static final Function OPTIONAL =
+        new Function() {
+            public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+
+                StringWriter sw = new StringWriter();
+                int s = fdp.parseData(sw);
+                sw.close();
+                String data = sw.toString();
+
+                if (s != 0) {
+                    out.write(data);
+                    return 1;
+                } else {
+                    return 0;
+                }
+            }
+        };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/kryshen/tema/functions/ReplaceWriter.java	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,154 @@
+/*
+ *  Copyright (C) 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: ReplaceWriter.java,v 1.2 2006/12/14 14:39:26 mikhail Exp $
+ */
+
+package kryshen.tema.functions;
+
+import java.io.Writer;
+import java.io.FilterWriter;
+import java.io.IOException;
+
+import java.util.LinkedList;
+import java.util.Iterator;
+
+/**
+ * FilterWriter which replaces characters with escape strings.
+ *
+ * @author Mikhail A. Kryshen
+ */
+class ReplaceWriter extends FilterWriter {
+    private String[] patterns;
+    private String[] replaces;
+
+    private String prefix;
+    private String postfix;
+
+    private int maxPatternLength = 0;
+
+    /** Have this Writer processed any data? */
+    private boolean processedData = false;
+
+    private StringBuffer buffer = new StringBuffer();
+
+    public ReplaceWriter(Writer w, String pattern, String replace) {
+        this(w, new String[]{pattern}, new String[]{replace});
+    }
+
+    public ReplaceWriter(Writer w, String pattern, String replace,
+                         String prefix, String postfix) {
+
+        this(w, new String[]{pattern}, new String[]{replace}, 
+             prefix, postfix);
+    }
+
+    public ReplaceWriter(Writer w, String[] patterns, String[] replaces) {
+        this(w, patterns, replaces, null, null);
+    }
+
+    public ReplaceWriter(Writer w, String[] patterns, String[] replaces, 
+                         String prefix, String postfix) {
+	super(w);
+        
+	this.patterns = patterns;
+	this.replaces = replaces;
+        
+        this.prefix = prefix;
+        this.postfix = postfix;
+        
+	for (int i = 0; i < patterns.length; i++) {
+	    int length = patterns[i].length();
+	    if (length > maxPatternLength)
+		maxPatternLength = length;
+	}
+    }
+
+    private boolean processBuffer(int minLength) throws IOException {
+        if (buffer.length() == 0) return false;
+
+        if (!processedData) {
+            if (prefix != null)
+                super.write(prefix, 0, prefix.length());
+            processedData = true;
+        }
+
+        int k;
+	for (k = 0; minLength + k < buffer.length(); k++) {
+	    for (int i = 0; i < patterns.length; i++) {
+		String pattern = patterns[i];
+		int length = pattern.length();
+
+                if (length + k > buffer.length())
+                    continue;
+
+		boolean match = true;
+
+		for (int j = 0; j < length; j++) {
+		    if (pattern.charAt(j) != buffer.charAt(j + k)) {
+			match = false;
+                        break;
+		    }
+		}
+
+		if (match) {
+                    super.write(buffer.substring(0, k), 0, k);
+                    buffer.delete(0, k + length);
+                    super.write(replaces[i], 0, replaces[i].length());
+		    return true;
+		}
+	    }
+	}
+
+        super.write(buffer.substring(0, k), 0, k);
+        buffer.delete(0, k);
+
+        return false;
+    }
+
+    public void write(int c) throws IOException {
+	buffer.append((char)c);
+	processBuffer(maxPatternLength);
+    }
+
+    public void write(char[] cbuf, int off, int len) throws IOException {
+	for (int i = off; i < off + len; i++) {
+	    buffer.append(cbuf[i]);
+	}
+	processBuffer(maxPatternLength);
+    }
+
+    public void write(String str, int off, int len) throws IOException {
+        buffer.append(str.substring(off, off + len));
+	processBuffer(maxPatternLength);
+    }
+
+    public void finish() throws IOException {
+        while (processBuffer(0));
+
+//         super.write(buffer.toString(), 0, buffer.length());
+//         buffer.delete(0, buffer.length());
+
+        if (processedData && postfix != null)
+            super.write(postfix, 0, postfix.length());
+    }
+
+    public void close() throws IOException {
+        finish();
+        super.close();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/kryshen/tema/functions/Standard.java	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,258 @@
+/*
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  $Id: Standard.java,v 1.5 2006/12/14 14:39:26 mikhail Exp $
+ */
+
+package kryshen.tema.functions;
+
+import java.io.*;
+import java.util.*;
+import java.net.*;
+
+import java.sql.SQLException;
+import java.sql.PreparedStatement;
+
+import kryshen.tema.*;
+
+/**
+ * Standard TEMA functions.
+ */
+public class Standard {
+    public static final Function SET =
+        new Function() {
+            public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+
+                String arg0 = fdp.getNextArg();
+                String arg1 = fdp.getData();
+                
+                fdp.getTemplateParser().setValue(arg0, arg1);
+                
+		out.write(arg0);
+                return 1;
+            }
+        };
+
+    /* prepare:qury_name sql_statement */
+    public static final Function PREPARE =
+        new Function() {
+            public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+
+                String arg0 = fdp.getNextArg();
+                String data = fdp.getData();
+
+		PreparedStatement statement;
+
+		try {
+		    statement = Tema.getDbConnection()
+                        .prepareStatement(data);
+		} catch (SQLException e) {
+		    throw new TemplateException
+                        (e.getMessage(), e, fdp.getTemplateReader());
+		}
+                
+                fdp.getTemplateParser().setValue(arg0, statement);   
+             
+		out.write(arg0);
+                return 1;
+            }
+        };
+
+    /* query:sql_query template arg1 arg2 ... argN */
+    public static final Function QUERY =
+        new Function() {
+           public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+                
+                String arg0 = fdp.getNextArg();
+                String arg1 = fdp.getNextArg();
+		List<String> args = fdp.getArgs();				
+
+		TemplateParser tp = fdp.getTemplateParser();
+
+                TemplateReader tr = 
+                    new TemplateReader(new StringReader(arg1),
+                                       fdp.getTemplateReader());
+
+		try {
+		    return Tema.query
+			(tr, (PreparedStatement)tp.getValue(arg0),
+			 args, tp, out);
+		} catch (SQLException e) {
+		    throw new TemplateException
+                        (e.getMessage(), e, fdp.getTemplateReader());
+		}
+            }
+        };
+
+    public static final Function REPLACE =
+        new Function() {
+	    public int invoke(FunctionDataParser fdp, Writer out)
+		throws IOException, TemplateException {		
+
+                String arg0 = fdp.getNextArg();
+                String arg1 = fdp.getNextArg();
+
+                ReplaceWriter rw = new ReplaceWriter(out, arg0, arg1);
+
+                int ret = fdp.parseData(rw);
+                rw.finish();
+                return ret;
+            }
+        };
+
+    public static final Function LOAD =
+
+        new Function() {
+            public int invoke(FunctionDataParser fdp, Writer out)
+                throws IOException, TemplateException {
+                
+                final String name = fdp.getNextArg();
+                final String className = fdp.getNextArg();
+
+                List<URL> urls = new ArrayList<URL>(1);
+
+                while (fdp.hasMoreData()) {
+                    urls.add(new URL(fdp.getNextArg()));                    
+                }
+                
+                ClassLoader loader = this.getClass().getClassLoader();
+
+                if (urls.size() > 0) {
+                    loader = new URLClassLoader
+                        (urls.toArray(new URL[0]), loader);
+                }                
+
+                Class<Function> functionClass;
+                Function function;
+
+                try {
+                    functionClass = loadFunctionClass(loader, className);
+                } catch (ClassNotFoundException e) {
+                    throw new TemplateException
+                        ("Class not found", e, fdp.getTemplateReader());
+                } catch (ClassCastException e) {
+                    throw new TemplateException
+                        ("Not a function class", e, fdp.getTemplateReader());
+                }
+                
+                try {
+                    function = functionClass.newInstance();
+                } catch (InstantiationException e) {
+                    throw new TemplateException
+                        ("Could not load class", e, fdp.getTemplateReader());
+                } catch (IllegalAccessException e) {
+                    throw new TemplateException
+                        ("Could not load class", e, fdp.getTemplateReader());
+                }
+
+                fdp.getTemplateParser().registerFunction(name, function);
+                                
+
+                out.write(name);
+                return 1;
+            }
+        };
+
+    public static final Function NULL_OUTPUT =
+        new Function() {
+	    public int invoke(FunctionDataParser fdp, Writer out)
+		throws IOException, TemplateException {		
+
+		/* Write nothing. */
+		return fdp.parseData(new Writer() {
+                    public void close() {}
+                    public void flush() {}
+                    public void write(char[] cbuf, int off, int len) {}
+                });
+            }
+        };
+
+    public static final Function XML_ESCAPE =
+        new Function() {
+	    public int invoke(FunctionDataParser fdp, Writer out)
+		throws IOException, TemplateException {		
+
+                final String[] chars = {"&", "<", ">", "`", "\""};
+                final String[] escape = {"&amp;", "&lt;", "&gt;", "&apos;", "&quot;"};
+
+                ReplaceWriter rw = new ReplaceWriter(out, chars, escape);
+
+                int ret = fdp.parseData(rw);
+                rw.finish();
+                return ret;
+            }
+        };
+
+    public static final Function XML_CDATA =
+        new Function() {
+	    public int invoke(FunctionDataParser fdp, Writer out)
+		throws IOException, TemplateException {		
+
+                ReplaceWriter rw = new ReplaceWriter
+                    (out, "]]>", "]]]><![CDATA[]>", "<![CDATA[", "]]>");
+
+                int ret = fdp.parseData(rw);
+                rw.finish();
+                return ret;
+            }
+        };
+
+
+    @SuppressWarnings("unchecked")
+    private static Class<Function> loadFunctionClass
+        (ClassLoader loader, String className)
+        throws ClassNotFoundException {
+        
+        return (Class<Function>)loader.loadClass(className);
+    }
+
+    /**
+     * Update file (copy if newer).
+     *
+     * @param src source file.
+     * @param dest destination file.
+     *
+     * @trows IOException on copying error.
+     */
+    private static void copyFile(File src, File dest) throws IOException {
+	System.err.print("Copying " + src + "... ");
+	
+	if (src.lastModified() < dest.lastModified()) {
+	    System.err.println(dest + " is up to date.");
+	    return;
+	}
+
+	File parent = dest.getParentFile();
+	if (parent != null) parent.mkdirs();
+
+        InputStream in = new FileInputStream(src);
+        OutputStream out = new FileOutputStream(dest);
+        
+        byte[] buffer = new byte[1024];
+        int len;
+        while ((len = in.read(buffer)) > 0) {
+            out.write(buffer, 0, len);
+        }
+        in.close();
+        out.close();
+
+	System.err.println("saved " + dest + ".");
+    }
+}
Binary file test/Hello.class has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/Hello.java	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,21 @@
+import java.io.Writer;
+import java.io.IOException;
+import kryshen.tema.*;
+
+/**
+ * Test function class.
+ * 
+ * @author Mikhail A. Kryshen
+ */
+public class Hello extends Function {
+    public Hello() {}
+
+    public int invoke(FunctionDataParser fdp, Writer out)
+        throws IOException, TemplateException {
+        
+        out.write("Hello, ");
+        fdp.parseData(out);
+
+        return 1;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/demo	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+#java -jar ../dist/tema.jar
+java -classpath .:../dist/tema.jar kryshen.tema.Tema
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/demo.bat	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,1 @@
+java -classpath .:..\dist\tema.jar kryshen.tema.Tema
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/include.template	Thu Dec 14 23:22:05 2006 +0300
@@ -0,0 +1,1 @@
+<%define\test test arg1:[%nextarg:%], test arg2:[%nextarg:%], test data:[%data:%].%>
\ No newline at end of file
--- a/test/inctest.template	Tue May 16 18:04:09 2006 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<%test:1 2 3%>
--- a/test/macrotest	Tue May 16 18:04:09 2006 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-java -jar ../dist/tema.jar
--- a/test/main.template	Tue May 16 18:04:09 2006 +0400
+++ b/test/main.template	Thu Dec 14 23:22:05 2006 +0300
@@ -1,3 +1,28 @@
-<%define`test [%nextarg:%]+[%nextarg:%]=[%data:]%>
-<%include`inctest.template%>
-<%test:<%test:ab cd x%> [%\<%test\1 2 3%>%] y%>
+[%!\ UTF-8 text %]
+
+п▓я▀п╡п╬п╢ я┌п╣п╨я│я┌п╟:
+test text
+
+п▓я▀п╡п╬п╢ я│п©п╣я├п╦п╟п╩я▄п╫я▀я┘ я│п╦п╪п╡п╬п╩п╬п╡:
+[%\<%test%>%]
+
+п▓п╨п╩я▌я┤п╣п╫п╦п╣ я└п╟п╧п╩п╟ я│ п╬п╠я┼я▐п╡п╩п╣п╫п╦п╣п╪ я└я┐п╫п╨я├п╦п╦:
+<%include:include.template%>
+
+п▓я▀п╥п╬п╡ п╫п╬п╡п╬п╧ я└я┐п╫п╨я├п╦п╦:
+<%test:1 2 3%>
+
+п╒п╣я│я┌п╦я─п╬п╡п╟п╫п╦п╣ read:
+<%read\include.template%>
+
+п≈п╟пЁя─я┐п╥п╨п╟ я└я┐п╫п╨я├п╦п╦ п╦п╥ п╨п╩п╟я│я│п╟:
+<%load\hello Hello%>
+
+п▓я▀п©п╬п╩п╣п╫п╣п╫п╦п╣ п╥п╟пЁя─я┐п╤п╣п╫п╫п╬п╧ я└я┐п╫п╨я├п╦п╦:
+<%hello\TEMA%>
+
+пёя│п╩п╬п╡п╫п╬п╣ п╡я▀п©п╬п╩п╫п╣п╫п╦п╣:
+<%optional:<%true:%>True%> <%optional:<%false:%>False%>
+
+п╒п╣я│я┌п╦я─п╬п╡п╟п╫п╦п╣ xml_escape:
+<%xml_escape\x < a & b%>
--- a/test/tema.properties	Tue May 16 18:04:09 2006 +0400
+++ b/test/tema.properties	Thu Dec 14 23:22:05 2006 +0300
@@ -1,16 +1,16 @@
 # Data source configuration
-# resource          : jdbc:odbc:biotopes-data
+# resource          : jdbc:odbc:database
 # driver            : sun.jdbc.odbc.JdbcOdbcDriver
 
 # Base directory for images and files
-# resource_base     : C:\\biotopes\\db
+# resource_base     : .
 
 # Template to start processing with
 main_template     : main.template
 
 # File encodings
-# input_encoding    : ISO-8859-5
-# output_encoding   : ISO-8859-5
+input_encoding    : UTF-8
+output_encoding   : UTF-8
 
 # Cache templates
 # cache_read        : true