вторник, 10 ноября 2009 г.

ADP shell wrapper

Для некоторых пакетных операций, например, построения и рассылки отчетов по расписанию, удобно иметь возможность запуска adp-скриптов напрямую из консоли. Задача, таким образом, состоит в том, чтобы обеспечить среду исполнения наподобии предоставляемой AOL Server. Воспользуемся интерпретатором nstclsh для доступа к функциям, предоставляемым AOL Server, а функции обращения к конфигурации и подобные подменим своими.

adp-wrapper.tcl

#!/usr/bin/nstclsh
# ./adp-wrapper.tcl .../report_ext_01/report_html_all.adp date 2009-10-01 user_id 265

set aol_root [string range [lindex $argv 0] 0 [string last www [lindex $argv 0]]-2]
array set args [lrange $argv 1 end]

rename puts _puts
proc puts {message} {}
proc ns_log {args} {}
proc ns_config {unit key} {
global aol_root
# puts "$unit => $key"
if {$key eq "timeout"} {return 10000}
if {$key eq "location"} {return [file join $aol_root res dataset]}
return
}
proc ns_register_proc {args} {}
proc ns_register_filter {args} {}
proc <% {} {}
proc %> {} {}
proc ns_adp_puts {val} {puts $val}
proc ns_adp_append {args} {foreach arg $args {puts -nonewline $arg}}
# значения переменных GET берутся из аргументов командной строки
proc ns_queryget {name} {
global args
if {[info exists args($name)] == 1} {
return $args($name)
}
return
}
proc ns_backup_cache_sheduler {args} {}
proc ns_backup_nsv_array_sheduler {args} {}
# загрузить необходимые функции
foreach unit {tcl_module ns_dataset ns_keys ns_menu ns_html populate billing telephony internet} {
foreach fname [glob [file join $aol_root tcl $unit *.tcl]] {
source $fname
}
}
rename puts {}
rename _puts puts
rename ns_adp_include orig_ns_adp_include
proc ns_adp_include {src} {
global argv
source [file join [file dirname [lindex $argv 0]] $src]
}

# выполнить adp-скрипт в подготовленном окружении
ns_adp_include [file tail [lindex $argv 0]]


Здесь функции ns_backup_cache_sheduler и ns_backup_nsv_array_sheduler обеспечивают восстановление состояние AOL Server после перезапуска, что нам, естественно, не требуется для эмуляции. Кроме того, структура директорий в корне веб-сервера жестко фиксирована, что позволяет по пути к adp-скрипту определить местонахождение баз данных и тиклевских скриптов, загружаемых при инициализации.

Использование "обертки" довольно тривиально:

#!/usr/bin/tclsh8.5
# start as
# AOLROOT=... ./telephony_report.tcl
package require sqlite3

if {[info exists env(AOLROOT)] == 0} {
puts stderr {Not defined environment variable AOLROOT}
exit
}

sqlite3 db :memory:
db restore [file join $env(AOLROOT) res dataset work].db
db timeout 2000
db cache size 100

set date [db onecolumn {select date('now','start of month','-1 month')}]
set user_id 229

db eval {SELECT id, login FROM view_user WHERE type != 'system' AND is_provider = 0
AND delete_date IS NULL ORDER BY name ASC} {
exec ./adp-wrapper.tcl $env(AOLROOT)/www/ext/report_ext_01/report_html_all.adp date $date user_id $id > \
$env(AOLROOT)/www/share/${login}_all.html 2>/dev/null
exec ./adp-wrapper.tcl $env(AOLROOT)/www/ext/report_ext_01/report_html_c.adp date $date user_id $id > \
$env(AOLROOT)/www/share/${login}_c.html 2>/dev/null
exec ./adp-wrapper.tcl $env(AOLROOT)/www/ext/report_ext_01/report_html_cd.adp date $date user_id $id > \
$env(AOLROOT)/www/share/${login}_cd.html 2>/dev/null
}
db close


Многоточием я заменил реальные пути на своем сервере.

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

вторник, 3 ноября 2009 г.

Фильтры для преобразования ms excel и ms powerpoint в html и текст

Вот и еще пара нужных фильтров, притом шустрых и без ветвистых зависимостей.

application/vnd.ms-excel

#!/bin/sh

nice -n19 xlhtml "$1" | \
grep -v 'Created with <a href="http://chicago.sf.net/xlhtml">xlhtml 0.5.1' \
|w3m \
-o indent_incr=0 \
-o multicol=false \
-o no_cache=true \
-o use_cookie=false \
-o display_charset=utf8 \
-o system_charset=utf8 \
-o follow_locale=false \
-o use_language_tag=true \
-o ucs_conv=true \
-T text/html \
-dump



application/vnd.ms-office

#!/bin/sh

nice -n19 ppthtml "$1" \
|grep -v 'Created with <a href="http://chicago.sf.net/xlhtml">pptHtml' \
|w3m \
-o indent_incr=0 \
-o multicol=false \
-o no_cache=true \
-o use_cookie=false \
-o display_charset=utf8 \
-o system_charset=utf8 \
-o follow_locale=false \
-o use_language_tag=true \
-o ucs_conv=true \
-T text/html \
-dump


И все больше не понимаю, чем руководствуются разработчики поисковых систем, использующие, например, gnumeric для преобразования Excel файлов (в частности, tracker делает именно так). Чем-то мне это напоминает песенку "я тебя слепила из того, что было". Что интересно, для консольных почтовых клиентов есть прекрасные фильтры, например, одна из вышеприведенных утилит используется в модуле к emacs, где я ее и нашел. Понятно, там нужно просматривать вложения к сообщениям, а они бывают самые разные. Притом с электронной почтой работали еще тогда, когда программирование было искусством, а не ремеслом.

понедельник, 2 ноября 2009 г.

Фильтр для преобразования odt в html

Самый что ни на есть распрекрасный odt тоже нужно индексировать. "С помощью лома и какой-то матери" нашел ODF Tools, которые производят на свет неплохой html. Опять же, плодя временные файлы. Единственный плюс сего проекта - дают адекватную xslt-таблицу преобразования. Но что они с ней вытворяют, это же уму не постижимо. Чтение исходников привело к мысли, что все гораздо проще делается, например, так


cat <(echo "<?xml version='1.0' encoding='UTF-8'?>")\
<(echo "<office:document xmlns:office='urn:oasis:names:tc:opendocument:xmlns:office:1.0'>")\
<(unzip -p 101.odt meta.xml |grep -v "<?xml version=") \
<(unzip -p 101.odt content.xml |grep -v "<?xml version=") \
<(unzip -p 101.odt styles.xml |grep -v "<?xml version=") \
<(echo "</office:document>")\
|xsltproc odt2html.xsl -


Оно работает, и без временных файлов. Но, как я понимаю, это "башизм" и с ним надо бороться. Вопрос - как? Не соображу, как бы это переписать да еще без вызова лишних утилит...

Upd.

Спасибо Serhiy Storchaka за подсказку, все решается просто:


(echo "<?xml version='1.0' encoding='UTF-8'?>"
echo "<office:document xmlns:office='urn:oasis:names:tc:opendocument:xmlns:office:1.0'>"
unzip -p 101.odt meta.xml |grep -v "<?xml version="
unzip -p 101.odt content.xml |grep -v "<?xml version="
unzip -p 101.odt styles.xml |grep -v "<?xml version="
echo "</office:document>") | xsltproc odt2html.xsl -

Some enhancements for FTS3

The FTS3 work fine but is really unfriendly to developers. As example it is easy to write
tcl interface code for snowball stemmer utility "stemwords" and for stopwords dictionary
but there are no ways to use it in FTS3. The user functions can be easy writed on C or
any other language but FTS3 does not work with these.

1. There are no interfaces for stemmer, stopwords dictionary, etc. in the FTS3 extension.
It's very difficult to understand the code of FTS3 extension and change it. Is it possible to
add calls of user-defined functions for this tasks?

The virtual table creating command may be extended as

sql-command ::= CREATE VIRTUAL TABLE [ database-name .] table-name USING fts3
[( [ argument [, argument, [argument, ...] ]*] )]
argument ::= name | TOKENIZE tokenizer | FUNCTION user_function
tokenizer ::= SIMPLE | PORTER | user-defined

When FUNCTION return null than the word must be ignored else the tokenized word is
replaced by returned from function.

As example application can bind these functions like as

#!/usr/bin/tclsh8.5
package require sqlite3
sqlite3 db :memory:
proc stopword {word} {
...
}
proc stemmer {word} {
...
}
db function stopword stopword
db function stemmer stemmer
db eval {CREATE VIRTUAL TABLE t USING fts3(content, TOKENIZE icu ru_RU,
FUNCTION stopword, FUNCTION stemmer);}

Of cource we can extend the example above with a synonyms dictionary function or
internal soundex() function or other.

I think the feature is "must have".

2. The snippet function have now the ability for change snippet text size and return
very small text fragment. As example the standalone unix diff -u command return 3
lines before and after context and this can easy be changed by command-line
arguments. Yes, application can use self snippet realization on base of offsets()
information but it's produce additional difficults.

3. The user defined tokenizer function will be very helpful. The tokenizer is stream
interface and must have the stream position so the user defined tokenizer can have
the interface like to

tokenizer (document_text, document_position)

This function can be called from xNext() interface function.

I don't sure about the realization and may be the interface will be different.

The Tcl documents scanner for FTS3

The useful scanner may collect as documents body as meta information. For start we can store the type, size and some checksum for original file.


#!/usr/bin/tclsh8.5
# find /mnt/backup/project/offline1/www/share | ./scan.tcl /mnt/backup/project/offline1/www/share
package require sqlite3
catch {file delete scan.db}
sqlite3 db scan.db
#sqlite3 db :memory:
db eval {
CREATE TABLE file (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
save_date REAL NOT NULL DEFAULT (julianday('now')),
delete_date REAL,
checksum text not null,
uri text not null,
size int not null,
mime text not null default ''
);
CREATE VIRTUAL TABLE file_text USING fts3(content, TOKENIZE icu ru_RU);
}
# cut the root directory name from file names
set root [lindex $argv 0]
while {[eof stdin] == 0} {
set file [gets stdin]
catch {
if {[file type $file] ne {file}} continue
set mime [exec file --brief --mime-type $file]
if {[file exists ./filters/${mime}_filter]} {
set md5 [string range [exec md5sum $file] 0 31]
set text [exec ./filters/${mime}_filter $file]
set size [file size $file]
if {$root ne {} && [string range $file 0 [string length $root]-1] eq $root} {
set file [string range $file [string length $root] end]
}
puts "$file => $mime => $size"
db eval {insert into file (checksum,uri,size,mime) values ($md5,$file,$size,$mime)}
db eval {insert into file_text (rowid,content) values (last_insert_rowid(),$text)}
}
}
}
db eval {vacuum}


The simple search query with the text results can be writed like as:

select uri,mime,size,snippet(file_text, '[', ']', '%%')
from file_text,file
where file_text.rowid=file.rowid and file_text match 'коды def';

/Uslugi1.html|text/html|35621|%% информация по кодам [DEF]

[Коды] [DEF]

Автоматические %%
/Uslugi.html|text/html|32928|%% информация по кодам [DEF]

[Коды] [DEF]

Автоматические %%



And with the HTML results:

select uri,mime,size,snippet(file_text)
from file_text,file
where file_text.rowid=file.rowid and file_text match 'коды def';


/Uslugi1.html|text/html|35621|... информация по кодам DEF

Коды DEF

Автоматические ...
/Uslugi.html|text/html|32928|... информация по кодам DEF

Коды DEF

Автоматические ...

(C) Печников Алексей, mobigroup.ru
При перепечатке должно быть указано авторство статьи с гиперссылкой на сайт.