tema
 
(Mikhail Kryshen)
2006-12-14: Tema 0.1jk - Javakonkurs edition (imported from CVS). release_0_1jk

Tema 0.1jk - Javakonkurs edition (imported from CVS).

diff --git a/TODO b/TODO
new file mode 100644
--- /dev/null
+++ b/TODO
@@ -0,0 +1,1 @@
+if, <, >, =, +, -, *, /
diff --git a/build.xml b/build.xml
--- a/build.xml
+++ b/build.xml
@@ -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>
diff --git a/dist/biotopes/biotope.template b/dist/biotopes/biotope.template
--- a/dist/biotopes/biotope.template
+++ b/dist/biotopes/biotope.template
@@ -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%></_>
 </_>
diff --git a/dist/biotopes/classes.template b/dist/biotopes/classes.template
--- a/dist/biotopes/classes.template
+++ b/dist/biotopes/classes.template
@@ -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%></>%]%>
 </>
diff --git a/dist/biotopes/doc/article.txt b/dist/biotopes/doc/article.txt
new file mode 100644
--- /dev/null
+++ b/dist/biotopes/doc/article.txt
@@ -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-    .
diff --git a/dist/biotopes/doc/readme.html b/dist/biotopes/doc/readme.html
new file mode 100644
--- /dev/null
+++ b/dist/biotopes/doc/readme.html
@@ -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><%<em>_</em>[:|\]<em></em>%></code>, <em> </em> -    ,    .    ,     .</p>
+
+<h3>     </h3>
+
+<ul>
+  <li><code>:</code> -    .</li>
+  <li><code>\</code> -      .       <code><%</code>  <code>%></code> (<code><%\<%%></code>  <code><%\%%>></code>).</li>
+</ul>
+
+<h3></h3>
+
+<ul>
+  <li><code><%get:<em></em>%></code><br />
+         .</li>
+
+  <li><code><%optional:<em></em>%></code><br />
+    <em></em>,     ,    ,     .</li>
+
+  <li><code><%escape:<em></em>%></code><br />
+     ""  (     ).</li>
+
+  <li><code><%invoke:<em></em>; <em>1</em> <em>2</em> ... <em>N</em>%></code><br />
+      <em></em>.sql       <em></em>.template.        '?'.</li>
+
+  <li><code><%image:<em>_</em> <em>_</em> <em></em> [<em>_</em>] [<em>_</em>]%></code><br />
+      <em>_</em> (     "resource_base")      ,    <em>_</em>.      ,    .
+  ,  <code><%image:...%></code>     <em>_</em>,  -   .</li>
+
+  <li><code><%copy:<em>_</em> <em>_</em>%></code><br />
+    <em>_</em> (     "resource_base")  <em>_</em>.   ,  <code><%copy:...%></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><%escape get:name%></code></p>
+
+<p align="right"><i><a href="mailto:kryshen@cs.karelia.ru"> </a></i></p>
+
+</body>
+</html>
diff --git a/dist/biotopes/doc/readme.txt b/dist/biotopes/doc/readme.txt
new file mode 100644
--- /dev/null
+++ b/dist/biotopes/doc/readme.txt
@@ -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%>
diff --git a/dist/biotopes/main.template b/dist/biotopes/main.template
--- a/dist/biotopes/main.template
+++ b/dist/biotopes/main.template
@@ -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%>%>
diff --git a/dist/biotopes/photo.template b/dist/biotopes/photo.template
--- a/dist/biotopes/photo.template
+++ b/dist/biotopes/photo.template
@@ -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%>"/>
diff --git a/dist/biotopes/plants.template b/dist/biotopes/plants.template
--- a/dist/biotopes/plants.template
+++ b/dist/biotopes/plants.template
@@ -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%></>%]%>
 </>
diff --git a/dist/readme.html b/dist/readme.html
deleted file mode 100644
--- a/dist/readme.html
+++ /dev/null
@@ -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><%<em>_</em>[:|\]<em></em>%></code>, <em> </em> -    ,    .    ,     .</p>
-
-<h3>     </h3>
-
-<ul>
-  <li><code>:</code> -    .</li>
-  <li><code>\</code> -      .       <code><%</code>  <code>%></code> (<code><%\<%%></code>  <code><%\%%>></code>).</li>
-</ul>
-
-<h3></h3>
-
-<ul>
-  <li><code><%get:<em></em>%></code><br />
-         .</li>
-
-  <li><code><%optional:<em></em>%></code><br />
-    <em></em>,     ,    ,     .</li>
-
-  <li><code><%escape:<em></em>%></code><br />
-     ""  (     ).</li>
-
-  <li><code><%invoke:<em></em>; <em>1</em> <em>2</em> ... <em>N</em>%></code><br />
-      <em></em>.sql       <em></em>.template.        '?'.</li>
-
-  <li><code><%image:<em>_</em> <em>_</em> <em></em> [<em>_</em>] [<em>_</em>]%></code><br />
-      <em>_</em> (     "resource_base")      ,    <em>_</em>.      ,    .
-  ,  <code><%image:...%></code>     <em>_</em>,  -   .</li>
-
-  <li><code><%copy:<em>_</em> <em>_</em>%></code><br />
-    <em>_</em> (     "resource_base")  <em>_</em>.   ,  <code><%copy:...%></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><%escape get:name%></code></p>
-
-<p align="right"><i><a href="mailto:kryshen@cs.karelia.ru"> </a></i></p>
-
-</body>
-</html>
diff --git a/dist/readme.txt b/dist/readme.txt
deleted file mode 100644
--- a/dist/readme.txt
+++ /dev/null
@@ -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%>
diff --git a/dist/tema b/dist/tema
--- a/dist/tema
+++ b/dist/tema
@@ -1,3 +1,3 @@
 #!/bin/sh
 
-java -jar tema.jar
+java -jar tema.jar "$@"
diff --git a/dist/tema.bat b/dist/tema.bat
--- a/dist/tema.bat
+++ b/dist/tema.bat
@@ -1,1 +1,1 @@
-java -jar tema.jar
+java -jar tema.jar %1
diff --git a/dist/tema.properties b/dist/tema.properties
--- a/dist/tema.properties
+++ b/dist/tema.properties
@@ -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
diff --git a/doc/article.txt b/doc/article.txt
deleted file mode 100644
--- a/doc/article.txt
+++ /dev/null
@@ -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-    .
diff --git a/doc/manual/index.html b/doc/manual/index.html
new file mode 100644
--- /dev/null
+++ b/doc/manual/index.html
@@ -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><%<i>список_функций</i>{:|\|`}<i>данные</i>%></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>
+Кроме скобок '<', '>', можно использовать скобки '[', ']'.
+</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>, в котором символы
+'&', '<', '>', '`', '\' заменены на соответствующие сущности
+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>
diff --git a/res/kryshen/tema/demo/demo.template b/res/kryshen/tema/demo/demo.template
new file mode 100644
--- /dev/null
+++ b/res/kryshen/tema/demo/demo.template
@@ -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%>
diff --git a/sample/class.xml b/sample/class.xml
deleted file mode 100644
--- a/sample/class.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-5"?>
-<>
-  < id="1"> </>
-  < id="2">   </>
-  < id="3">   </>
-  < id="4">,    </>
-  < id="5">   </>
-  < id="6"> </>
-</>
diff --git a/sample/plants.xml b/sample/plants.xml
deleted file mode 100644
--- a/sample/plants.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-5"?>
-<>
-  <>Betula sp.</>
-  <>Calamagrostis arundinacea</>
-  <>Geranium sylvaticum</>
-</>
diff --git a/src/kryshen/tema/Function.java b/src/kryshen/tema/Function.java
--- a/src/kryshen/tema/Function.java
+++ b/src/kryshen/tema/Function.java
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
  *
- * $Id: Function.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  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: 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;
 }
diff --git a/src/kryshen/tema/FunctionDataParser.java b/src/kryshen/tema/FunctionDataParser.java
--- a/src/kryshen/tema/FunctionDataParser.java
+++ b/src/kryshen/tema/FunctionDataParser.java
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2006 Mikhail A. Kryshen
  *
- * $Id: FunctionDataParser.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  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: 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;
+    }
 }
diff --git a/src/kryshen/tema/Functions.java b/src/kryshen/tema/Functions.java
--- a/src/kryshen/tema/Functions.java
+++ b/src/kryshen/tema/Functions.java
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
  *
- * $Id: Functions.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  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: 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 {
+    // TODO: arithmetics, conditions
 
-		String name = fdp.getData();
-                return fdp.getTemplateParser().parseVariable(name, out);
-            }
-        };
+    /**
+     * 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);
 
-    public static final Function SET =
-        new Function("set") {
-            public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
+        p.registerFunction("define",     Define.DEFINE);
 
-                String arg0 = fdp.getNextArg();
-                String arg1 = fdp.getData();
-                
-                fdp.getTemplateParser().setValue(arg0, arg1);
-                
-		out.write(arg0);
-                return 1;
-            }
-        };
+        p.registerFunction("write",      IO.WRITE);
+        p.registerFunction("read",       IO.READ);
+        p.registerFunction("include",    IO.INCLUDE);
+        p.registerFunction("copy",       IO.COPY);
 
-    /* prepare:qury_name sql_statement */
-    public static final Function PREPARE =
-        new Function("prepare") {
-            public int invoke(FunctionDataParser fdp, Writer out)
-                throws IOException, TemplateException {
+	p.registerFunction("false",      Logics.FALSE);
+	p.registerFunction("true",       Logics.TRUE);
+        p.registerFunction("optional",   Logics.OPTIONAL);
 
-                String arg0 = fdp.getNextArg();
-                String data = fdp.getData();
+        p.registerFunction("image",      ImageConverter.IMAGE);
 
-		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;
-
-                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 {
-
-	       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();
-
-                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 = {"&", "<", ">", "'", """};
-
-                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();
-
-    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 + ".");
-    }
-
-    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().
+	*/
     }
 }
diff --git a/src/kryshen/tema/ImageConverter.java b/src/kryshen/tema/ImageConverter.java
deleted file mode 100644
--- a/src/kryshen/tema/ImageConverter.java
+++ /dev/null
@@ -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. 
diff --git a/src/kryshen/tema/ReplaceWriter.java b/src/kryshen/tema/ReplaceWriter.java
deleted file mode 100644
--- a/src/kryshen/tema/ReplaceWriter.java
+++ /dev/null
@@ -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();
-    }
-}
diff --git a/src/kryshen/tema/Tema.java b/src/kryshen/tema/Tema.java
--- a/src/kryshen/tema/Tema.java
+++ b/src/kryshen/tema/Tema.java
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
  *
- * $Id: Tema.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  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: 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));
     }
 
-    static Reader createFileReader(File file) throws IOException {
+    public static BufferedReader createFileReader(File file)
+        throws IOException {
+
 	InputStream is = new FileInputStream(file);
-	return new InputStreamReader(is, Tema.inputEncoding);
+	return new BufferedReader
+            (new InputStreamReader(is, Tema.inputEncoding));
     }
 
-    static Reader createCachedFileReader(File file) throws IOException {
+    public static TemplateReader createTemplateReader(File file)
+        throws IOException {
+
+        return new TemplateReader
+            (new LineNumberReader(createCachedFileReader(file)), file.getPath());
+    }
+
+    /**
+     * 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;
+    }
 }
diff --git a/src/kryshen/tema/TemplateException.java b/src/kryshen/tema/TemplateException.java
--- a/src/kryshen/tema/TemplateException.java
+++ b/src/kryshen/tema/TemplateException.java
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2006 Mikhail A. Kryshen
  *
- * $Id: TemplateException.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  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: 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;
     }
 }
diff --git a/src/kryshen/tema/TemplateParser.java b/src/kryshen/tema/TemplateParser.java
--- a/src/kryshen/tema/TemplateParser.java
+++ b/src/kryshen/tema/TemplateParser.java
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2005, 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
  *
- * $Id: TemplateParser.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  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: 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);
diff --git a/src/kryshen/tema/TemplateReader.java b/src/kryshen/tema/TemplateReader.java
new file mode 100644
--- /dev/null
+++ b/src/kryshen/tema/TemplateReader.java
@@ -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();
+    }
+}
diff --git a/src/kryshen/tema/demo/DemoFrame.java b/src/kryshen/tema/demo/DemoFrame.java
new file mode 100644
--- /dev/null
+++ b/src/kryshen/tema/demo/DemoFrame.java
@@ -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());
+    }
+}
diff --git a/src/kryshen/tema/demo/Hello.java b/src/kryshen/tema/demo/Hello.java
new file mode 100644
--- /dev/null
+++ b/src/kryshen/tema/demo/Hello.java
@@ -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;
+    }
+}
diff --git a/src/kryshen/tema/functions/Define.java b/src/kryshen/tema/functions/Define.java
--- a/src/kryshen/tema/functions/Define.java
+++ b/src/kryshen/tema/functions/Define.java
@@ -1,7 +1,21 @@
 /*
- * Copyright (C) 2006 Mikhail A. Kryshen
+ *  Copyright (C) 2006 Mikhail A. Kryshen
  *
- * $Id: Define.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
+ *  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: 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 {
+                    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());
 
-                                return fdp.parseNextArg(out);
-                            }
-                        });
-
-                    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;
     }
diff --git a/src/kryshen/tema/functions/IO.java b/src/kryshen/tema/functions/IO.java
new file mode 100644
--- /dev/null
+++ b/src/kryshen/tema/functions/IO.java
@@ -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 + ".");
+    }
+}
diff --git a/src/kryshen/tema/functions/ImageConverter.java b/src/kryshen/tema/functions/ImageConverter.java
new file mode 100644
--- /dev/null
+++ b/src/kryshen/tema/functions/ImageConverter.java
@@ -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;
+//     }
+}
diff --git a/src/kryshen/tema/functions/Logics.java b/src/kryshen/tema/functions/Logics.java
new file mode 100644
--- /dev/null
+++ b/src/kryshen/tema/functions/Logics.java
@@ -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;
+                }
+            }
+        };
+}
diff --git a/src/kryshen/tema/functions/ReplaceWriter.java b/src/kryshen/tema/functions/ReplaceWriter.java
new file mode 100644
--- /dev/null
+++ b/src/kryshen/tema/functions/ReplaceWriter.java
@@ -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();
+    }
+}
diff --git a/src/kryshen/tema/functions/Standard.java b/src/kryshen/tema/functions/Standard.java
new file mode 100644
--- /dev/null
+++ b/src/kryshen/tema/functions/Standard.java
@@ -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 = {"&", "<", ">", "'", """};
+
+                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 + ".");
+    }
+}
diff --git a/test/Hello.class b/test/Hello.class
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2d1fdaa33410bda45c7010390483529e8b870369
GIT binary patch
literal 555
zc$|e%%T5A85Uk;~upm$I&C3%gVPoPA<3%Ns5H*3wdB6#dEW2b`Q2)t`7fd|(0e+OR
zXN^RniF4@gsi~Uk>3RQneFJcST^%8$!^r4ZMOMce)-`Ns*klMDNlUuN4E}QEnjvsv
z-w1|8OIqS$I_e3h!+RznA!*I*Ar*(^*3fyJ+zQKZ#fTe?snvI-ZJlzLUvXz5oO<hy
z&$uCNqw7eQ_LU}se)iB8V?~^3*wU~~P}`n5ebJEWe*IjSrd?CY7&L?#G!Lm3HSENY
zLtaB6h9XJ~LGOei`)4BznQ#B*<-(OAzr^l{(b(j!_(6y-w@p6xsvw{8*NaSU4UD$y
zNNZ46!z%nU2!_a56$+uQ%D?A=5`y$KYWcv_yD3I!m!r%m2lrmUo~fV-Q4D!tlwuSy
tk|=78vY&Fin)c1%->=RQ=mwq^nz(nGByEZ`8LW8s_y|uBBk5VEJ^`$~eV70M

diff --git a/test/Hello.java b/test/Hello.java
new file mode 100644
--- /dev/null
+++ b/test/Hello.java
@@ -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;
+    }
+}
diff --git a/test/demo b/test/demo
new file mode 100755
--- /dev/null
+++ b/test/demo
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+#java -jar ../dist/tema.jar
+java -classpath .:../dist/tema.jar kryshen.tema.Tema
diff --git a/test/demo.bat b/test/demo.bat
new file mode 100644
--- /dev/null
+++ b/test/demo.bat
@@ -0,0 +1,1 @@
+java -classpath .:..\dist\tema.jar kryshen.tema.Tema
diff --git a/test/include.template b/test/include.template
new file mode 100644
--- /dev/null
+++ b/test/include.template
@@ -0,0 +1,1 @@
+<%define\test test arg1:[%nextarg:%], test arg2:[%nextarg:%], test data:[%data:%].%>
\ No newline at end of file
diff --git a/test/inctest.template b/test/inctest.template
deleted file mode 100644
--- a/test/inctest.template
+++ /dev/null
@@ -1,1 +0,0 @@
-<%test:1 2 3%>
diff --git a/test/macrotest b/test/macrotest
deleted file mode 100644
--- a/test/macrotest
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-java -jar ../dist/tema.jar
diff --git a/test/main.template b/test/main.template
--- a/test/main.template
+++ b/test/main.template
@@ -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%>
diff --git a/test/tema.properties b/test/tema.properties
--- a/test/tema.properties
+++ b/test/tema.properties
@@ -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