3086d262fbe32a8d95522cb45215045ff45ba6c4
[yaz-moved-to-github.git] / src / charconv.tcl
1 #!/usr/bin/tclsh
2 # $Id: charconv.tcl,v 1.21 2008-01-06 13:02:48 adam Exp $
3
4 proc usage {} {
5     puts {charconv.tcl: [-p prefix] [-s split] [-o ofile] file ... }
6     exit 1
7 }
8
9 proc preamble_trie {ofilehandle ifiles ofile} {
10     set f $ofilehandle
11
12     set totype {unsigned }
13
14     puts $f "/** \\file $ofile"
15     puts $f "    \\brief Character conversion, generated from [lindex $ifiles 0]"
16     puts $f ""
17     puts $f "    Generated automatically by charconv.tcl"
18     puts $f "*/"
19     puts $f "\#include <string.h>"
20     puts $f "
21         struct yaz_iconv_trie_flat {
22             char from\[6\];
23             unsigned combining : 1;
24             $totype to : 24;
25         };
26         struct yaz_iconv_trie_dir {
27             int ptr : 15;
28             unsigned combining : 1;
29             $totype to : 24;
30         };
31         
32         struct yaz_iconv_trie {
33             struct yaz_iconv_trie_flat *flat;
34             struct yaz_iconv_trie_dir *dir;
35         };
36     "
37     puts $f {
38         static unsigned long lookup(struct yaz_iconv_trie **ptrs, int ptr, unsigned char *inp,
39                                     size_t inbytesleft, size_t *no_read, int *combining, unsigned mask, int boffset)
40         {
41             struct yaz_iconv_trie *t = (ptr > 0) ? ptrs[ptr-1] : 0;
42             if (!t || inbytesleft < 1)
43                 return 0;
44             if (t->dir)
45             {
46                 size_t ch = (inp[0] & mask) + boffset;
47                 unsigned long code =
48                 lookup(ptrs, t->dir[ch].ptr, inp+1, inbytesleft-1, no_read, combining, mask, boffset);
49                 if (code)
50                 {
51                     (*no_read)++;
52                     return code;
53                 }
54                 if (t->dir[ch].to)
55                 {
56                     code = t->dir[ch].to;
57                     *combining = t->dir[ch].combining;
58                     *no_read = 1;
59                     return code;
60                 }
61             }
62             else
63             {
64                 struct yaz_iconv_trie_flat *flat = t->flat;
65                 while (flat->to)
66                 {
67                     size_t len = strlen(flat->from);
68                     if (len <= inbytesleft)
69                     {
70                         size_t i;
71                         for (i = 0; i < len; i++)
72                         {
73                             if (((unsigned char *) flat->from)[i] != (inp[i] & mask) + boffset)
74                                 break;
75                         }
76                         if (i == len)
77                         {
78                             *no_read = len;
79                             *combining = flat->combining;
80                             return flat->to;
81                         }
82                     }
83                     flat++;
84                 }
85             }
86             return 0;
87         }
88     }
89 }
90
91 proc reset_trie {} {
92     global trie
93
94     foreach x [array names trie] {
95         unset trie($x)
96     }
97
98     set trie(no) 1
99     set trie(size) 0
100     set trie(max) 0
101     set trie(split) 50
102     set trie(prefix) {}
103 }
104
105 proc ins_trie {from to combining codename} {
106     global trie
107     if {![info exists trie(no)]} {
108         set trie(no) 1
109         set trie(size) 0
110         set trie(max) 0
111     }
112     if {$trie(max) < $to} {
113         set trie(max) $to
114     }
115     incr trie(size)
116     ins_trie_r [split $from] $to $combining $codename 0
117 }
118
119 proc split_trie {this} {
120     global trie
121     set trie($this,type) d
122     foreach e $trie($this,content) {
123         set from [lindex $e 0]
124         set to [lindex $e 1]
125         set combining [lindex $e 2]
126         set codename [lindex $e 3]
127         
128         set ch [lindex $from 0]
129         set rest [lrange $from 1 end]
130         
131         if {[llength $rest]} {
132             if {![info exist trie($this,ptr,$ch)]} {
133                 set trie($this,ptr,$ch) $trie(no)
134                 incr trie(no)
135             }
136             ins_trie_r $rest $to $combining $codename $trie($this,ptr,$ch)
137         } else {
138             set trie($this,to,$ch) $to
139             set trie($this,combining,$ch) $combining
140             set trie($this,codename,$ch) $codename
141         }
142     }
143     set trie($this,content) missing
144 }
145
146 proc ins_trie_r {from to combining codename this} {
147     global trie
148
149     if {![info exist trie($this,type)]} {
150         set trie($this,type) f
151     }
152     if {$trie($this,type) == "f"} {
153         set dup 0
154         if {[info exists trie($this,content)]} {
155             foreach e $trie($this,content) {
156                 set efrom [lindex $e 0]
157                 if { $efrom == $from } {
158                     set dup 1
159                 }
160             }
161         }
162         if { $dup == 0 } {
163             lappend trie($this,content) [list $from $to $combining $codename]
164         }
165         
166         # split ?
167         if {[llength $trie($this,content)] > $trie(split)} {
168             split_trie $this
169             return [ins_trie_r $from $to $combining $codename $this]
170         }
171     } else {
172         set ch [lindex $from 0]
173         set rest [lrange $from 1 end]
174
175         if {[llength $rest]} {
176             if {![info exist trie($this,ptr,$ch)]} {
177                 set trie($this,ptr,$ch) $trie(no)
178                 incr trie(no)
179             }
180             ins_trie_r $rest $to $combining $codename $trie($this,ptr,$ch)
181         } else {
182             if {![info exist trie($this,to,$ch)]} {
183                 set trie($this,to,$ch) $to
184                 set trie($this,combining,$ch) $combining
185                 set trie($this,codename,$ch) $codename
186             }
187         }
188     }
189 }
190
191 proc dump_trie {ofilehandle} {
192     global trie
193
194     set f $ofilehandle
195
196     puts $f "/* TRIE: size $trie(size) */"
197
198     set this $trie(no)
199     while { [incr this -1] >= 0 } {
200         puts $f "/* PAGE $this */"
201         if {$trie($this,type) == "f"} {
202             puts $f "struct yaz_iconv_trie_flat $trie(prefix)page${this}_flat\[\] = \{"
203             foreach m $trie($this,content) {
204                 puts -nonewline $f "  \{\""
205                 foreach d [lindex $m 0] {
206                     puts -nonewline $f "\\x$d"
207                 }
208                 puts -nonewline $f "\", [lindex $m 2], 0x[lindex $m 1]"
209                 set v [lindex $m 3]
210                 puts $f "\}, /* $v */"
211             }
212             puts $f "  \{\"\", 0\}"
213             puts $f "\};"
214             puts $f "struct yaz_iconv_trie $trie(prefix)page${this} = \{"
215             puts $f "  $trie(prefix)page${this}_flat, 0"
216             puts $f "\};"
217         } else {
218             puts $f "struct yaz_iconv_trie_dir $trie(prefix)page${this}_dir\[256\] = \{"
219             for {set i 0} {$i < 256} {incr i} {
220                 puts -nonewline $f "  \{"
221                 set ch [format %02X $i]
222                 set null 1
223                 if {[info exist trie($this,ptr,$ch)]} {
224                     puts -nonewline $f "[expr $trie($this,ptr,$ch)+1], "
225                     set null 0
226                 } else {
227                     puts -nonewline $f "0, "
228                 }
229                 if {[info exist trie($this,combining,$ch)]} {
230                     puts -nonewline $f "$trie($this,combining,$ch), "
231                 } else {
232                     puts -nonewline $f "0, "
233                 }
234                 if {[info exist trie($this,to,$ch)]} {
235                     puts -nonewline $f "0x$trie($this,to,$ch)\}"
236                     set null 0
237                 } else {
238                     puts -nonewline $f "0\}"
239                 }
240                 if {[info exist trie($this,codename,$ch)]} {
241                     set v $trie($this,codename,$ch)
242                     puts -nonewline $f " /* $v */"
243                 }
244                 if {$i < 255} {
245                     puts $f ","
246                 } else {
247                     puts $f ""
248                 }
249             }
250             puts $f "\};"
251             puts $f "struct yaz_iconv_trie $trie(prefix)page${this} = \{"
252             puts $f "  0, $trie(prefix)page${this}_dir"
253             puts $f "\};"
254         }
255     }
256
257     puts $f "struct yaz_iconv_trie *$trie(prefix)ptrs \[\] = {"
258     for {set this 0} {$this < $trie(no)} {incr this} {
259         puts $f " &$trie(prefix)page$this,"
260     }
261     puts $f "0, };"
262     puts $f ""
263
264     puts $f "unsigned long yaz_$trie(prefix)_conv
265             (unsigned char *inp, size_t inbytesleft, size_t *no_read, int *combining, unsigned mask, int boffset)
266         {
267             unsigned long code;
268             
269             code = lookup($trie(prefix)ptrs, 1, inp, inbytesleft, no_read, combining, mask, boffset);
270             if (!code)
271             {
272                 *no_read = 1;
273             }
274             return code;
275         }
276     "
277 }
278
279 proc readfile {fname ofilehandle prefix omits reverse} {
280     global trie
281
282     set marc_lines 0
283     set ucs_lines 0
284     set utf_lines 0
285     set altutf_lines 0
286     set codename_lines 0
287     set lineno 0
288     set f [open $fname r]
289     set tablenumber x
290     set combining 0
291     set codename {}
292     set altutf {}
293     while {1} {
294         incr lineno
295         set cnt [gets $f line]
296         if {$cnt < 0} {
297             break
298         }
299         if {[regexp {</characterSet>} $line s]} {
300             dump_trie $ofilehandle
301         } elseif {[regexp {<characterSet .*ISOcode="([0-9A-Fa-f]+)"} $line s tablenumber]} {
302             reset_trie
303             set trie(prefix) "${prefix}_$tablenumber"
304             set combining 0
305         } elseif {[regexp {</code>} $line s]} {
306             if {[string length $ucs]} {
307                 if {$reverse} {
308                     for {set i 0} {$i < [string length $utf]} {incr i 2} {
309                         lappend hex [string range $utf $i [expr $i+1]]
310                     }
311                     # puts "ins_trie $hex $marc
312                     ins_trie $hex $marc $combining $codename
313                     unset hex
314
315                 } else {
316                     for {set i 0} {$i < [string length $marc]} {incr i 2} {
317                         lappend hex [string range $marc $i [expr $i+1]]
318                     }
319                     # puts "ins_trie $hex $ucs"
320                     ins_trie $hex $ucs $combining $codename
321                     unset hex
322                 }
323             }
324             if {$reverse && [string length $marc]} {
325                 for {set i 0} {$i < [string length $altutf]} {incr i 2} {
326                     lappend hex [string range $altutf $i [expr $i+1]]
327                 }
328                 if {[info exists hex]} {
329                     ins_trie $hex $marc $combining $codename
330                     unset hex
331                 }
332             }
333             set marc {}
334             set uni {}
335             set codename {}
336             set combining 0
337             set altutf {}
338         } elseif {[regexp {<marc>([0-9A-Fa-f]*)</marc>} $line s marc]} {
339             incr marc_lines
340         } elseif {[regexp {<name>(.*)</name>} $line s codename]} {
341             incr codename_lines
342         } elseif {[regexp {<name>(.*)} $line s codename]} {
343             incr codename_lines
344             incr lineno
345             set cnt [gets $f line]
346             if {$cnt < 0} {
347                 break
348             }
349             if {[regexp {(.*)</name>} $line s codename_ex]} {
350                 set codename "${codename} ${codename_ex}"
351             }
352         } elseif {[regexp {<isCombining>true</isCombining>} $line s]} {
353             set combining 1
354         } elseif {[regexp {<ucs>([0-9A-Fa-f]*)</ucs>} $line s ucs]} {
355             incr ucs_lines
356         } elseif {[regexp {<utf-8>([0-9A-Fa-f]*)</utf-8>} $line s utf]} {
357             incr utf_lines
358         } elseif {[regexp {<altutf-8>([0-9A-Fa-f]*)</altutf-8>} $line s altutf]} {
359             incr altutf_lines
360         }
361     }
362     close $f
363 }
364
365 set verbose 0
366 set ifile {}
367 set ofile out.c
368 set prefix {c}
369 set reverse_map 0
370 # Parse command line
371 set l [llength $argv]
372 set i 0
373 set omits {}
374 while {$i < $l} {
375     set arg [lindex $argv $i]
376     switch -glob -- $arg {
377         -v {
378             incr verbose
379         }
380         -s {
381             if {[string length $arg]} {
382                 set arg [lindex $argv [incr i]]
383             }
384             set trie(split) $arg
385         }
386         -p {
387             if {[string length $arg]} {
388                 set arg [lindex $argv [incr i]]
389             }
390             set prefix $arg
391         }
392         -o {
393             if {[string length $arg]} {
394                 set arg [lindex $argv [incr i]]
395             }
396             set ofile $arg
397         }
398         -O {
399             if {[string length $arg]} {
400                 set arg [lindex $argv [incr i]]
401             }
402             lappend omits $arg
403         }
404         -r {
405             set reverse_map 1
406         }
407         default {
408             lappend ifiles $arg
409         }
410     }
411     incr i
412 }
413 if {![info exists ifiles]} {
414     puts "charconv.tcl: missing input file(s)"
415     usage
416 }
417
418 set ofilehandle [open $ofile w]
419 preamble_trie $ofilehandle $ifiles $ofile
420
421 foreach ifile $ifiles {
422     readfile $ifile $ofilehandle $prefix $omits $reverse_map
423 }
424 close $ofilehandle
425
426