fixes compile
[platform/upstream/ibus-libpinyin.git] / src / PYEnglishEditor.cc
1 /* vim:set et ts=4 sts=4:
2  *
3  * ibus-libpinyin - Intelligent Pinyin engine based on libpinyin for IBus
4  *
5  * Copyright (c) 2010-2011 Peng Wu <alexepico@gmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  */
21
22 #include "PYEnglishEditor.h"
23 #include <string.h>
24 #include <string>
25 #include <vector>
26 #include <stdio.h>
27 #include <libintl.h>
28 #include <sqlite3.h>
29 #include <glib.h>
30 #include <glib/gstdio.h>
31 #include "PYConfig.h"
32 #include "PYString.h"
33
34 #define _(text) (gettext(text))
35
36 namespace PY {
37
38 #define DB_BACKUP_TIMEOUT   (60)
39
40 class EnglishDatabase{
41 public:
42     EnglishDatabase(){
43         m_sqlite = NULL;
44         m_sql = "";
45         m_user_db = "";
46         m_timeout_id = 0;
47         m_timer = g_timer_new ();
48     }
49
50     ~EnglishDatabase(){
51         g_timer_destroy (m_timer);
52         if (m_timeout_id != 0) {
53             saveUserDB ();
54             g_source_remove (m_timeout_id);
55         }
56
57         if (m_sqlite){
58             sqlite3_close (m_sqlite);
59             m_sqlite = NULL;
60         }
61         m_sql = "";
62         m_user_db = NULL;
63     }
64
65     gboolean isDatabaseExisted(const char *filename) {
66          gboolean result = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
67          if (!result)
68              return FALSE;
69
70          sqlite3 *tmp_db = NULL;
71          if (sqlite3_open_v2 (filename, &tmp_db,
72                               SQLITE_OPEN_READONLY, NULL) != SQLITE_OK){
73              return FALSE;
74          }
75
76          /* Check the desc table */
77          sqlite3_stmt *stmt = NULL;
78          const char *tail = NULL;
79          m_sql = "SELECT value FROM desc WHERE name = 'version';";
80          result = sqlite3_prepare_v2 (tmp_db, m_sql.c_str(), -1, &stmt, &tail);
81          if (result != SQLITE_OK)
82              return FALSE;
83
84          result = sqlite3_step (stmt);
85          if (result != SQLITE_ROW)
86              return FALSE;
87
88          result = sqlite3_column_type (stmt, 0);
89          if (result != SQLITE_TEXT)
90              return FALSE;
91
92          const char *version = (const char *) sqlite3_column_text (stmt, 0);
93          if (strcmp("1.2.0", version ) != 0)
94              return FALSE;
95
96          result = sqlite3_finalize (stmt);
97          g_assert (result == SQLITE_OK);
98          sqlite3_close (tmp_db);
99          return TRUE;
100     }
101
102     gboolean createDatabase(const char *filename) {
103         /* unlink the old database. */
104         gboolean retval = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
105         if (retval) {
106             int result = g_unlink (filename);
107             if (result == -1)
108                 return FALSE;
109         }
110
111         char *dirname = g_path_get_dirname (filename);
112         g_mkdir_with_parents (dirname, 0700);
113         g_free (dirname);
114
115         sqlite3 *tmp_db = NULL;
116         if (sqlite3_open_v2 (filename, &tmp_db,
117                              SQLITE_OPEN_READWRITE | 
118                              SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
119             return FALSE;
120         }
121
122         /* Create DESCription table */
123         m_sql = "BEGIN TRANSACTION;\n";
124         m_sql << "CREATE TABLE IF NOT EXISTS desc (name TEXT PRIMARY KEY, value TEXT);\n";
125         m_sql << "INSERT OR IGNORE INTO desc VALUES ('version', '1.2.0');";
126         m_sql << "COMMIT;\n";
127
128         if (!executeSQL (tmp_db)) {
129             sqlite3_close (tmp_db);
130             return FALSE;
131         }
132
133         /* Create Schema */
134         m_sql = "CREATE TABLE IF NOT EXISTS english ("
135                 "word TEXT NOT NULL PRIMARY KEY,"
136                 "freq FLOAT NOT NULL DEFAULT(0)"
137                 ");";
138         if (!executeSQL (tmp_db)) {
139             sqlite3_close (tmp_db);
140             return FALSE;
141         }
142         return TRUE;
143     }
144
145     gboolean openDatabase(const char *system_db, const char *user_db){
146         if (!isDatabaseExisted (system_db))
147             return FALSE;
148         if (!isDatabaseExisted (user_db)) {
149             gboolean result = createDatabase (user_db);
150             if (!result)
151                 return FALSE;
152         }
153         /* cache the user db name. */
154         m_user_db = user_db;
155
156         /* do database attach here. :) */
157         if (sqlite3_open_v2 (system_db, &m_sqlite,
158                              SQLITE_OPEN_READWRITE |
159                              SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
160             m_sqlite = NULL;
161             return FALSE;
162         }
163
164 #if 0
165         m_sql.printf (SQL_ATTACH_DB, user_db);
166         if (!executeSQL (m_sqlite)) {
167             sqlite3_close (m_sqlite);
168             m_sqlite = NULL;
169             return FALSE;
170         }
171         return TRUE;
172 #endif
173         return loadUserDB();
174     }
175
176     /* List the words in freq order. */
177     gboolean listWords(const char *prefix, std::vector<std::string> & words){
178         sqlite3_stmt *stmt = NULL;
179         const char *tail = NULL;
180         words.clear ();
181
182         /* list words */
183         const char *SQL_DB_LIST = 
184             "SELECT word FROM ( "
185             "SELECT * FROM english UNION ALL SELECT * FROM userdb.english) "
186             " WHERE word LIKE \"%s%\" GROUP BY word ORDER BY SUM(freq) DESC;";
187         m_sql.printf (SQL_DB_LIST, prefix);
188         int result = sqlite3_prepare_v2 (m_sqlite, m_sql.c_str(), -1, &stmt, &tail);
189         if (result != SQLITE_OK)
190             return FALSE;
191
192         result = sqlite3_step (stmt);
193         while (result == SQLITE_ROW){
194             /* get the words. */
195             result = sqlite3_column_type (stmt, 0);
196             if (result != SQLITE_TEXT)
197                 return FALSE;
198
199             const char *word = (const char *)sqlite3_column_text (stmt, 0);
200             words.push_back (word);
201             result = sqlite3_step (stmt);
202         }
203
204         sqlite3_finalize (stmt);
205         if (result != SQLITE_DONE)
206             return FALSE;
207         return TRUE;
208     }
209
210     /* Get the freq of user sqlite db. */
211     gboolean getWordInfo(const char *word, float & freq){
212         sqlite3_stmt *stmt = NULL;
213         const char *tail = NULL;
214         /* get word info. */
215         const char *SQL_DB_SELECT = 
216             "SELECT freq FROM userdb.english WHERE word = \"%s\";";
217         m_sql.printf (SQL_DB_SELECT, word);
218         int result = sqlite3_prepare_v2 (m_sqlite, m_sql.c_str(), -1, &stmt, &tail);
219         g_assert (result == SQLITE_OK);
220         result = sqlite3_step (stmt);
221         if (result != SQLITE_ROW)
222             return FALSE;
223         result = sqlite3_column_type (stmt, 0);
224         if (result != SQLITE_FLOAT)
225             return FALSE;
226         freq = sqlite3_column_double (stmt, 0);
227         result = sqlite3_finalize (stmt);
228         g_assert (result == SQLITE_OK);
229         return TRUE;
230     }
231
232     /* Update the freq with delta value. */
233     gboolean updateWord(const char *word, float freq){
234         const char *SQL_DB_UPDATE =
235             "UPDATE userdb.english SET freq = \"%f\" WHERE word = \"%s\";";
236         m_sql.printf (SQL_DB_UPDATE, freq, word);
237         gboolean retval =  executeSQL (m_sqlite);
238         modified ();
239         return retval;
240     }
241
242     /* Insert the word into user db with the initial freq. */
243     gboolean insertWord(const char *word, float freq){
244         const char *SQL_DB_INSERT =
245             "INSERT INTO userdb.english (word, freq) VALUES (\"%s\", \"%f\");";
246         m_sql.printf (SQL_DB_INSERT, word, freq);
247         gboolean retval = executeSQL (m_sqlite);
248         modified ();
249         return retval;
250     }
251
252 private:
253     gboolean executeSQL(sqlite3 *sqlite){
254         gchar *errmsg = NULL;
255         if (sqlite3_exec (sqlite, m_sql.c_str (), NULL, NULL, &errmsg)
256              != SQLITE_OK) {
257             g_warning ("%s: %s", errmsg, m_sql.c_str());
258             sqlite3_free (errmsg);
259             return FALSE;
260         }
261         m_sql.clear ();
262         return TRUE;
263     }
264
265     gboolean loadUserDB (void){
266         sqlite3 *userdb =  NULL;
267         /* Attach user database */
268         do {
269             const char *SQL_ATTACH_DB =
270                 "ATTACH DATABASE ':memory:' AS userdb;";
271             m_sql.printf (SQL_ATTACH_DB);
272             if (!executeSQL (m_sqlite))
273                 break;
274
275             /* Note: user db is always created by openDatabase. */
276             if (sqlite3_open_v2 ( m_user_db, &userdb,
277                                   SQLITE_OPEN_READWRITE |
278                                   SQLITE_OPEN_CREATE, NULL) != SQLITE_OK)
279                 break;
280
281             sqlite3_backup *backup = sqlite3_backup_init (m_sqlite, "userdb", userdb, "main");
282
283             if (backup) {
284                 sqlite3_backup_step (backup, -1);
285                 sqlite3_backup_finish (backup);
286             }
287
288             sqlite3_close (userdb);
289             return TRUE;
290         } while (0);
291
292         if (userdb)
293             sqlite3_close (userdb);
294         return FALSE;
295     }
296
297     gboolean saveUserDB (void){
298         sqlite3 *userdb = NULL;
299         String tmpfile = String(m_user_db) + "-tmp";
300         do {
301             /* remove tmpfile if it exist */
302             g_unlink(tmpfile);
303
304             if (sqlite3_open_v2 (tmpfile, &userdb,
305                                  SQLITE_OPEN_READWRITE |
306                                  SQLITE_OPEN_CREATE, NULL) != SQLITE_OK)
307                 break;
308
309             sqlite3_backup *backup = sqlite3_backup_init (userdb, "main", m_sqlite, "userdb");
310
311             if (backup == NULL)
312                 break;
313
314             sqlite3_backup_step (backup, -1);
315             sqlite3_backup_finish (backup);
316             sqlite3_close (userdb);
317
318             g_rename(tmpfile, m_user_db);
319             return TRUE;
320         } while (0);
321
322         if (userdb)
323             sqlite3_close (userdb);
324         g_unlink (tmpfile);
325         return FALSE;
326     }
327
328     void modified (void){
329         /* Restart the timer */
330         g_timer_start (m_timer);
331
332         if (m_timeout_id != 0)
333             return;
334
335         m_timeout_id = g_timeout_add_seconds (DB_BACKUP_TIMEOUT,
336                                               EnglishDatabase::timeoutCallback,
337                                               static_cast<gpointer> (this));
338     }
339
340     static gboolean timeoutCallback (gpointer data){
341         EnglishDatabase *self = static_cast<EnglishDatabase *> (data);
342
343         /* Get elapsed time since last modification of database. */
344         guint elapsed = (guint) g_timer_elapsed (self->m_timer, NULL);
345
346         if (elapsed >= DB_BACKUP_TIMEOUT &&
347             self->saveUserDB ()) {
348             self->m_timeout_id = 0;
349             return FALSE;
350         }
351
352         return TRUE;
353     }
354
355     sqlite3 *m_sqlite;
356     String m_sql;
357     const char *m_user_db;
358
359     guint m_timeout_id;
360     GTimer *m_timer;
361 };
362
363 EnglishEditor::EnglishEditor (PinyinProperties & props, Config &config)
364     : Editor (props, config), m_train_factor (0.1)
365 {
366     m_english_database = new EnglishDatabase;
367
368     gchar *path = g_build_filename (g_get_user_cache_dir (),
369                                      "ibus", "pinyin", "english-user.db", NULL);
370
371     gboolean result = m_english_database->openDatabase
372         (".." G_DIR_SEPARATOR_S "data" G_DIR_SEPARATOR_S "english.db",
373          "english-user.db") ||
374         m_english_database->openDatabase
375         (PKGDATADIR G_DIR_SEPARATOR_S "db" G_DIR_SEPARATOR_S "english.db", path);
376     if (!result)
377         g_warning ("can't open english word list database.\n");
378 }
379
380 EnglishEditor::~EnglishEditor ()
381 {
382     delete m_english_database;
383     m_english_database = NULL;
384 }
385
386 gboolean
387 EnglishEditor::processKeyEvent (guint keyval, guint keycode, guint modifiers)
388 {
389     //IBUS_SHIFT_MASK is removed.
390     modifiers &= (IBUS_CONTROL_MASK |
391                   IBUS_MOD1_MASK |
392                   IBUS_SUPER_MASK |
393                   IBUS_HYPER_MASK |
394                   IBUS_META_MASK |
395                   IBUS_LOCK_MASK);
396     if (modifiers)
397         return FALSE;
398
399     //handle backspace/delete here.
400     if (processEditKey (keyval))
401         return TRUE;
402
403     //handle page/cursor up/down here.
404     if (processPageKey (keyval))
405         return TRUE;
406
407     //handle label key select here.
408     if (processLabelKey (keyval))
409         return TRUE;
410
411     if (processSpace (keyval))
412         return TRUE;
413
414     if (processEnter (keyval))
415         return TRUE;
416
417     m_cursor = std::min (m_cursor, (guint)m_text.length ());
418
419     /* Remember the input string. */
420     if (m_cursor == 0) {
421         g_return_val_if_fail ('v' == keyval, FALSE);
422         m_text = "v";
423         m_cursor ++;
424     } else {
425         g_return_val_if_fail ('v' == m_text[0], FALSE);
426         if ((keyval >= 'a' && keyval <= 'z') ||
427             (keyval >= 'A' && keyval <= 'Z')) {
428             m_text.insert (m_cursor, keyval);
429             m_cursor ++;
430         }
431     }
432
433     /* Deal other staff with updateStateFromInput (). */
434     updateStateFromInput ();
435     update ();
436     return TRUE;
437 }
438
439 gboolean
440 EnglishEditor::processEditKey (guint keyval)
441 {
442     switch (keyval) {
443     case IBUS_Delete:
444     case IBUS_KP_Delete:
445         removeCharAfter ();
446         updateStateFromInput ();
447         update ();
448         return TRUE;
449     case IBUS_BackSpace:
450         removeCharBefore ();
451         updateStateFromInput ();
452         update ();
453         return TRUE;
454     }
455     return FALSE;
456 }
457
458 gboolean
459 EnglishEditor::processPageKey (guint keyval)
460 {
461     switch (keyval) {
462     case IBUS_comma:
463         if (m_config.commaPeriodPage ()) {
464             pageUp ();
465             return TRUE;
466         }
467         break;
468     case IBUS_minus:
469         if (m_config.minusEqualPage ()) {
470             pageUp ();
471             return TRUE;
472         }
473         break;
474     case IBUS_period:
475         if (m_config.commaPeriodPage ()) {
476             pageDown ();
477             return TRUE;
478         }
479         break;
480     case IBUS_equal:
481         if (m_config.minusEqualPage ()) {
482             pageDown ();
483             return TRUE;
484         }
485         break;
486     case IBUS_Up:
487     case IBUS_KP_Up:
488         cursorUp ();
489         return TRUE;
490
491     case IBUS_Down:
492     case IBUS_KP_Down:
493         cursorDown ();
494         return TRUE;
495
496     case IBUS_Page_Up:
497     case IBUS_KP_Page_Up:
498         pageUp ();
499         return TRUE;
500
501     case IBUS_Page_Down:
502     case IBUS_KP_Page_Down:
503         pageDown ();
504         return TRUE;
505
506     case IBUS_Escape:
507         reset ();
508         return TRUE;
509     }
510     return FALSE;
511 }
512
513 gboolean
514 EnglishEditor::processLabelKey (guint keyval)
515 {
516     switch (keyval) {
517     case '1' ... '9':
518         return selectCandidateInPage (keyval - '1');
519         break;
520     case '0':
521         return selectCandidateInPage (9);
522         break;
523     }
524     return FALSE;
525 }
526
527 gboolean
528 EnglishEditor::processEnter (guint keyval)
529 {
530     if (keyval != IBUS_Return)
531         return FALSE;
532
533     if (m_text.length () == 0)
534         return FALSE;
535
536     Text text(m_text);
537     commitText (text);
538     train (m_text.c_str (), m_train_factor);
539     reset ();
540     return TRUE;
541 }
542
543 gboolean
544 EnglishEditor::processSpace (guint keyval)
545 {
546     if (!(keyval == IBUS_space || keyval == IBUS_KP_Space))
547         return FALSE;
548
549     guint cursor_pos = m_lookup_table.cursorPos ();
550     return selectCandidate (cursor_pos);
551 }
552
553 void
554 EnglishEditor::candidateClicked (guint index, guint button, guint state)
555 {
556     selectCandidateInPage (index);
557 }
558
559 gboolean
560 EnglishEditor::selectCandidateInPage (guint index)
561 {
562     guint page_size = m_lookup_table.pageSize ();
563     guint cursor_pos = m_lookup_table.cursorPos ();
564
565     if (G_UNLIKELY (index >= page_size))
566         return FALSE;
567     index += (cursor_pos / page_size) * page_size;
568
569     return selectCandidate (index);
570 }
571
572 gboolean
573 EnglishEditor::selectCandidate (guint index)
574 {
575     if (index >= m_lookup_table.size ())
576         return FALSE;
577
578     IBusText *candidate = m_lookup_table.getCandidate (index);
579     Text text (candidate);
580     commitText (text);
581     train (candidate->text, m_train_factor);
582     reset ();
583     return TRUE;
584 }
585
586 gboolean
587 EnglishEditor::updateStateFromInput (void)
588 {
589     /* Do parse and candidates update here. */
590     /* prefix v double check here. */
591     if (m_text.empty ()) {
592         m_preedit_text = "";
593         m_auxiliary_text = "";
594         m_cursor = 0;
595         clearLookupTable ();
596         return FALSE;
597     }
598
599     if ('v' != m_text[0]) {
600         g_warning ("v is expected in m_text string.\n");
601         m_auxiliary_text = "";
602         clearLookupTable ();
603         return FALSE;
604     }
605
606     m_auxiliary_text = "v";
607     if (1 == m_text.length ()) {
608         clearLookupTable ();
609
610         const char * help_string = _("Please input the english word.");
611         int space_len = std::max ( 0, m_aux_text_len
612                                    - (int) g_utf8_strlen (help_string, -1));
613         m_auxiliary_text.append (space_len, ' ');
614         m_auxiliary_text += help_string;
615
616         return TRUE;
617     }
618
619     m_auxiliary_text += " ";
620
621     String prefix = m_text.substr (1);
622     m_auxiliary_text += prefix;
623
624     /* lookup table candidate fill here. */
625     std::vector<std::string> words;
626     gboolean retval = m_english_database->listWords (prefix.c_str (), words);
627     if (!retval)
628         return FALSE;
629
630     clearLookupTable ();
631     std::vector<std::string>::iterator iter;
632     for (iter = words.begin (); iter != words.end (); ++iter){
633         Text text (*iter);
634         m_lookup_table.appendCandidate (text);
635     }
636     return TRUE;
637 }
638
639 /* Auxiliary Functions */
640
641 void
642 EnglishEditor::pageUp (void)
643 {
644     if (G_LIKELY (m_lookup_table.pageUp ())) {
645         update ();
646     }
647 }
648
649 void
650 EnglishEditor::pageDown (void)
651 {
652     if (G_LIKELY (m_lookup_table.pageDown ())) {
653         update ();
654     }
655 }
656
657 void
658 EnglishEditor::cursorUp (void)
659 {
660     if (G_LIKELY (m_lookup_table.cursorUp ())) {
661         update ();
662     }
663 }
664
665 void
666 EnglishEditor::cursorDown (void)
667 {
668     if (G_LIKELY (m_lookup_table.cursorDown ())) {
669         update ();
670     }
671 }
672
673 void
674 EnglishEditor::update (void)
675 {
676     updateLookupTable ();
677     updatePreeditText ();
678     updateAuxiliaryText ();
679 }
680
681 void
682 EnglishEditor::reset (void)
683 {
684     m_text = "";
685     updateStateFromInput ();
686     update ();
687 }
688
689 void
690 EnglishEditor::clearLookupTable (void)
691 {
692     m_lookup_table.clear ();
693     m_lookup_table.setPageSize (m_config.pageSize ());
694     m_lookup_table.setOrientation (m_config.orientation ());
695 }
696
697 void
698 EnglishEditor::updateLookupTable (void)
699 {
700     if (m_lookup_table.size ()) {
701         Editor::updateLookupTableFast (m_lookup_table, TRUE);
702     } else {
703         hideLookupTable ();
704     }
705 }
706
707 void
708 EnglishEditor::updatePreeditText (void)
709 {
710     if (G_UNLIKELY (m_preedit_text.empty ())) {
711         hidePreeditText ();
712         return;
713     }
714
715     StaticText preedit_text (m_preedit_text);
716     Editor::updatePreeditText (preedit_text, m_cursor, TRUE);
717 }
718
719 void
720 EnglishEditor::updateAuxiliaryText (void)
721 {
722     if (G_UNLIKELY (m_auxiliary_text.empty ())) {
723         hideAuxiliaryText ();
724         return;
725     }
726     
727     StaticText aux_text (m_auxiliary_text);
728     Editor::updateAuxiliaryText (aux_text, TRUE);
729 }
730
731 gboolean
732 EnglishEditor::removeCharBefore (void)
733 {
734     if (G_UNLIKELY (m_cursor <= 0)) {
735         m_cursor = 0;
736         return FALSE;
737     }
738
739     if (G_UNLIKELY (m_cursor > m_text.length ())) {
740         m_cursor = m_text.length ();
741         return FALSE;
742     }
743
744     m_text.erase (m_cursor - 1, 1);
745     m_cursor = std::max (0, static_cast<int>(m_cursor) - 1);
746     return TRUE;
747 }
748
749 gboolean
750 EnglishEditor::removeCharAfter (void)
751 {
752     if (G_UNLIKELY (m_cursor < 0)) {
753         m_cursor = 0;
754         return FALSE;
755     }
756
757     if (G_UNLIKELY (m_cursor >= m_text.length ())) {
758         m_cursor = m_text.length ();
759         return FALSE;
760     }
761
762     m_text.erase (m_cursor, 1);
763     m_cursor = std::min (m_cursor, (guint) m_text.length ());
764     return TRUE;
765 }
766
767 gboolean
768 EnglishEditor::train (const char *word, float delta)
769 {
770     float freq = 0;
771     gboolean retval = m_english_database->getWordInfo (word, freq);
772     if (retval) {
773         freq += delta;
774         m_english_database->updateWord (word, freq);
775     } else {
776         m_english_database->insertWord (word, delta);
777     }
778     return TRUE;
779 }
780
781 #if 0
782
783 /* using static initializor to test english database here. */
784 static class TestEnglishDatabase{
785 public:
786     TestEnglishDatabase (){
787         EnglishDatabase *db = new EnglishDatabase ();
788         bool retval = db->isDatabaseExisted ("/tmp/english-user.db");
789         g_assert (!retval);
790         retval = db->createDatabase ("english-user.db");
791         g_assert (retval);
792         retval = db->openDatabase ("english.db", "english-user.db");
793         g_assert (retval);
794         float freq = 0;
795         retval = db->getWordInfo ("hello", freq);
796         printf ("word hello:%d, %f.\n", retval, freq);
797         if (retval) {
798             db->updateWord ("hello", 0.1);
799         } else {
800             db->insertWord ("hello", 0.1);
801         }
802         printf ("english database test ok.\n");
803     }
804 } test_english_database;
805
806 #endif
807 };