1 // This file is part of The New Aspell
2 // Copyright (C) 2001 by Kevin Atkinson under the GNU LGPL license
3 // version 2.0 or 2.1.  You should have received a copy of the LGPL
4 // license along with this library if you did not you can find
5 // it at http://www.gnu.org/.
6 
7 #include <stdlib.h>
8 #include <assert.h>
9 #include <string.h>
10 #include <ctype.h>
11 #include <dirent.h>
12 
13 // POSIX includes
14 #ifdef __bsdi__
15  /* BSDi defines u_intXX_t types in machine/types.h */
16 #include <machine/types.h>
17 #endif
18 #ifdef WIN32
19 #  include <windows.h>
20 #  include <winbase.h>
21 #endif
22 
23 #include "iostream.hpp"
24 
25 #include "asc_ctype.hpp"
26 #include "config.hpp"
27 #include "errors.hpp"
28 #include "fstream.hpp"
29 #include "getdata.hpp"
30 #include "info.hpp"
31 #include "itemize.hpp"
32 #include "string.hpp"
33 #include "string_list.hpp"
34 #include "vector.hpp"
35 #include "stack_ptr.hpp"
36 #include "strtonum.hpp"
37 #include "lock.hpp"
38 #include "string_map.hpp"
39 
40 #include "gettext.h"
41 
42 namespace acommon {
43 
44   class Dir {
45     DIR * d_;
46     Dir(const Dir &);
47     Dir & operator=(const Dir &);
48   public:
operator DIR*()49     operator DIR * () {return d_;}
Dir(DIR * d)50     Dir(DIR * d) : d_(d) {}
~Dir()51     ~Dir() {if (d_) closedir(d_);}
52   };
53 
54   /////////////////////////////////////////////////////////////////
55   //
56   // Lists of Info Lists
57   //
58 
59   static void get_data_dirs (Config *,
60 			     StringList &);
61 
62   struct DictExt
63   {
64     static const size_t max_ext_size = 15;
65     const ModuleInfo * module;
66     size_t ext_size;
67     char ext[max_ext_size + 1];
68     DictExt(ModuleInfo * m, const char * e);
69   };
70 
71   typedef Vector<DictExt> DictExtList;
72 
73   struct MDInfoListAll
74   // this is in an invalid state if some of the lists
75   // has data but others don't
76   {
77     StringList key;
78     StringList for_dirs;
79     ModuleInfoList module_info_list;
80     StringList dict_dirs;
81     DictExtList    dict_exts;
82     DictInfoList   dict_info_list;
83     StringMap      dict_aliases;
84     void clear();
85     PosibErr<void> fill(Config *, StringList &);
has_dataacommon::MDInfoListAll86     bool has_data() const {return module_info_list.head_ != 0;}
87     void fill_helper_lists(const StringList &);
88     PosibErr<void> fill_dict_aliases(Config *);
89   };
90 
91   class MDInfoListofLists
92   {
93     Mutex lock;
94 
95     MDInfoListAll * data;
96 
97     int       offset;
98     int       size;
99 
valid_pos(int pos)100     int valid_pos(int pos) {return offset <= pos && pos < size + offset;}
101 
102     void clear(Config * c);
103     int find(const StringList &);
104 
105   public:
106 
107     MDInfoListofLists();
108     ~MDInfoListofLists();
109 
110     PosibErr<const MDInfoListAll *> get_lists(Config * c);
111 
flush()112     void flush() {} // unimplemented
113   };
114 
115   static MDInfoListofLists md_info_list_of_lists;
116 
117   /////////////////////////////////////////////////////////////////
118   //
119   // Utility functions declaration
120   //
121 
122   static const char * strnchr(const char * i, char c, unsigned int size);
123   static const char * strnrchr(const char * stop, char c, unsigned int size);
124 
125   /////////////////////////////////////////////////////////////////
126   //
127   // Built in modules
128   //
129 
130   struct ModuleInfoDefItem {
131     const char * name;
132     const char * data;
133   };
134 
135   static const ModuleInfoDefItem module_info_list_def_list[] = {
136     {"default",
137      "order-num 0.50;"
138      "dict-exts .multi,.alias"}
139   };
140 
141   /////////////////////////////////////////////////////////////////
142   //
143   // ModuleInfoList Impl
144   //
145 
146   struct ModuleInfoNode
147   {
148     ModuleInfo c_struct;
149     ModuleInfoNode * next;
ModuleInfoNodeacommon::ModuleInfoNode150     ModuleInfoNode(ModuleInfoNode * n = 0) : next(n) {}
151     String name;
152     String lib_dir;
153     StringList dict_exts;
154     StringList dict_dirs;
155   };
156 
clear()157   void ModuleInfoList::clear()
158   {
159     while (head_ != 0) {
160       ModuleInfoNode * to_del = head_;
161       head_ = head_->next;
162       delete to_del;
163     }
164   }
165 
fill(MDInfoListAll & list_all,Config * config)166   PosibErr<void> ModuleInfoList::fill(MDInfoListAll & list_all,
167 				      Config * config)
168   {
169     const ModuleInfoDefItem * i   = module_info_list_def_list;
170     const ModuleInfoDefItem * end = module_info_list_def_list
171       + sizeof(module_info_list_def_list)/sizeof(ModuleInfoDefItem);
172     for (; i != end; ++i)
173     {
174       StringIStream in(i->data);
175       proc_info(list_all, config, i->name, strlen(i->name), in);
176     }
177 
178     StringListEnumeration els = list_all.for_dirs.elements_obj();
179     const char * dir;
180     while ( (dir = els.next()) != 0) {
181       Dir d(opendir(dir));
182       if (d==0) continue;
183 
184       struct dirent * entry;
185       while ( (entry = readdir(d)) != 0) {
186 	const char * name = entry->d_name;
187 	const char * dot_loc = strrchr(name, '.');
188 	unsigned int name_size = dot_loc == 0 ? strlen(name) :  dot_loc - name;
189 
190 	// check if it ends in suffix
191 	if (strcmp(name + name_size, ".asmi") != 0)
192 	  continue;
193 
194 	String path;
195 	path += dir;
196 	path += '/';
197 	path += name;
198 	FStream in;
199 	RET_ON_ERR(in.open(path, "r"));
200 	RET_ON_ERR(proc_info(list_all, config, name, name_size, in));
201       }
202     }
203     return no_err;
204   }
205 
proc_info(MDInfoListAll &,Config * config,const char * name,unsigned int name_size,IStream & in)206   PosibErr<void> ModuleInfoList::proc_info(MDInfoListAll &,
207 					   Config * config,
208 					   const char * name,
209 					   unsigned int name_size,
210 					   IStream & in)
211   {
212     ModuleInfoNode * * prev = &head_;
213     ModuleInfoNode * to_add = new ModuleInfoNode();
214     to_add->c_struct.name = 0;
215     to_add->c_struct.order_num = -1;
216     to_add->c_struct.lib_dir = 0;
217     to_add->c_struct.dict_dirs = 0;
218 
219     to_add->name.assign(name, name_size);
220     to_add->c_struct.name = to_add->name.c_str();
221 
222     PosibErr<void> err;
223 
224     String buf; DataPair d;
225     while (getdata_pair(in, d, buf)) {
226       if (d.key == "order-num") {
227 	to_add->c_struct.order_num = strtod_c(d.value.str, NULL);
228 	if (!(0 < to_add->c_struct.order_num &&
229 	      to_add->c_struct.order_num < 1))
230 	  {
231 	    err.prim_err(bad_value, d.key, d.value,
232 			 _("a number between 0 and 1"));
233 	    goto RETURN_ERROR;
234 	  }
235       } else if (d.key == "lib-dir") {
236 	to_add->lib_dir = d.value.str;
237 	to_add->c_struct.lib_dir = to_add->lib_dir.c_str();
238       } else if (d.key == "dict-dir" || d.key == "dict-dirs") {
239 	to_add->c_struct.dict_dirs = &(to_add->dict_dirs);
240 	itemize(d.value, to_add->dict_dirs);
241       } else if (d.key == "dict-exts") {
242 	to_add->c_struct.dict_dirs = &(to_add->dict_exts);
243 	itemize(d.value, to_add->dict_exts);
244       } else {
245 	err.prim_err(unknown_key, d.key);
246 	goto RETURN_ERROR;
247       }
248     }
249 
250     while (*prev != 0 &&
251 	   (*prev)->c_struct.order_num < to_add->c_struct.order_num)
252       prev = &(*prev)->next;
253     to_add->next = *prev;
254     *prev = to_add;
255     return err;
256 
257   RETURN_ERROR:
258     delete to_add;
259     return err;
260   }
261 
find(const char * to_find,unsigned int to_find_len)262   ModuleInfoNode * ModuleInfoList::find(const char * to_find,
263 					unsigned int to_find_len)
264   {
265     for (ModuleInfoNode * n = head_;
266 	 n != 0;
267 	 n = n->next)
268     {
269       if (n->name.size() == to_find_len
270 	  && strncmp(n->name.c_str(), to_find, to_find_len) == 0) return n;
271     }
272     return 0;
273   }
274 
275   /////////////////////////////////////////////////////////////////
276   //
277   // DictInfoList Impl
278   //
279 
280   struct DictInfoNode
281   {
282     DictInfo c_struct;
283     DictInfoNode * next;
DictInfoNodeacommon::DictInfoNode284     DictInfoNode(DictInfoNode * n = 0) : next(n) {}
285     String name;
286     String code;
287     String variety;
288     String size_str;
289     String info_file;
290     bool direct;
291   };
292 
293   bool operator< (const DictInfoNode & r, const DictInfoNode & l);
294 
clear()295   void DictInfoList::clear()
296   {
297     while (head_ != 0) {
298       DictInfoNode * to_del = head_;
299       head_ = head_->next;
300       delete to_del;
301     }
302   }
303 
find_dict_ext(const DictExtList & l,ParmStr name)304   const DictExt * find_dict_ext(const DictExtList & l, ParmStr name)
305   {
306     DictExtList::const_iterator   i = l.begin();
307     DictExtList::const_iterator end = l.end();
308     for (; i != end; ++i)
309     {
310       if (i->ext_size <= name.size()
311           && strncmp(name + (name.size() - i->ext_size),
312                      i->ext, i->ext_size) == 0)
313         break;
314     }
315 
316     if (i == end) // does not end in one of the extensions in list
317       return 0;
318     else
319       return &*i;
320   }
321 
322 
fill(MDInfoListAll & list_all,Config * config)323   PosibErr<void> DictInfoList::fill(MDInfoListAll & list_all,
324 				    Config * config)
325   {
326     StringList aliases;
327     config->retrieve_list("dict-alias", &aliases);
328     StringListEnumeration els = aliases.elements_obj();
329     const char * str;
330     while ( (str = els.next()) != 0) {
331       const char * end = strchr(str, ' ');
332       assert(end != 0); // FIXME: Return error
333       String name(str, end - str);
334       RET_ON_ERR(proc_file(list_all, config,
335                            0, name.str(), name.size(),
336                            find_dict_ext(list_all.dict_exts, ".alias")->module));
337     }
338 
339     els = list_all.dict_dirs.elements_obj();
340     const char * dir;
341     while ( (dir = els.next()) != 0) {
342       Dir d(opendir(dir));
343       if (d==0) continue;
344 
345       struct dirent * entry;
346       while ( (entry = readdir(d)) != 0) {
347 	const char * name = entry->d_name;
348 	unsigned int name_size = strlen(name);
349 
350 	const DictExt * i = find_dict_ext(list_all.dict_exts,
351                                           ParmString(name, name_size));
352 	if (i == 0) // does not end in one of the extensions in list
353 	  continue;
354 
355 	name_size -= i->ext_size;
356 
357 	RET_ON_ERR(proc_file(list_all, config,
358 			     dir, name, name_size, i->module));
359       }
360     }
361     return no_err;
362   }
363 
proc_file(MDInfoListAll & list_all,Config * config,const char * dir,const char * name,unsigned int name_size,const ModuleInfo * module)364   PosibErr<void> DictInfoList::proc_file(MDInfoListAll & list_all,
365 					 Config * config,
366 					 const char * dir,
367 					 const char * name,
368 					 unsigned int name_size,
369 					 const ModuleInfo * module)
370   {
371     DictInfoNode * * prev = &head_;
372     StackPtr<DictInfoNode> to_add(new DictInfoNode());
373     const char * p0;
374     const char * p1;
375     const char * p2;
376     p0 = strnchr(name, '-', name_size);
377     if (!module)
378       p2 = strnrchr(name, '-', name_size);
379     else
380       p2 = name + name_size;
381     if (p0 == 0)
382       p0 = p2;
383     p1 = p2;
384     if (p0 + 2 < p1 && asc_isdigit(p1[-1]) && asc_isdigit(p1[-2]) && p1[-3] == '-')
385       p1 -= 2;
386 
387     to_add->name.assign(name, p2-name);
388     to_add->c_struct.name = to_add->name.c_str();
389 
390     to_add->code.assign(name, p0-name);
391     to_add->c_struct.code = to_add->code.c_str();
392 
393     // check if the code is in a valid form and normalize entry.
394     // If its not in a valid form than ignore this entry
395 
396     if (to_add->code.size() >= 2
397 	&& asc_isalpha(to_add->code[0]) && asc_isalpha(to_add->code[1]))
398     {
399       unsigned s = strcspn(to_add->code.str(), "_");
400       if (s > 3) return no_err;
401       unsigned i = 0;
402       for (; i != s; ++i)
403         to_add->name[i] = to_add->code[i] = asc_tolower(to_add->code[i]);
404       i++;
405       for (; i < to_add->code.size(); ++i)
406         to_add->name[i] = to_add->code[i] = asc_toupper(to_add->code[i]);
407     } else {
408       return no_err;
409     }
410 
411     // Need to do it here as module is about to get a value
412     // if it is null
413     to_add->direct = module == 0 ? false : true;
414 
415     if (!module) {
416       assert(p2 != 0); //FIXME: return error
417       ModuleInfoNode * mod
418 	= list_all.module_info_list.find(p2+1, name_size - (p2+1-name));
419       //FIXME: Check for null and return an error on an unknown module
420       module = &(mod->c_struct);
421     }
422     to_add->c_struct.module = module;
423 
424     if (p0 + 1 < p1)
425       to_add->variety.assign(p0+1, p1 - p0 - 1);
426     to_add->c_struct.variety = to_add->variety.c_str();
427 
428     if (p1 != p2)
429       to_add->size_str.assign(p1, 2);
430     else
431       to_add->size_str = "60";
432     to_add->c_struct.size_str = to_add->size_str.c_str();
433     to_add->c_struct.size = atoi(to_add->c_struct.size_str);
434 
435     if (dir) {
436       to_add->info_file  = dir;
437       to_add->info_file += '/';
438     }
439     to_add->info_file += name;
440 
441     while (*prev != 0 && *(DictInfoNode *)*prev < *to_add)
442       prev = &(*prev)->next;
443     to_add->next = *prev;
444     *prev = to_add.release();
445 
446     return no_err;
447   }
448 
operator <(const DictInfoNode & r,const DictInfoNode & l)449   bool operator< (const DictInfoNode & r, const DictInfoNode & l)
450   {
451     const DictInfo & rhs = r.c_struct;
452     const DictInfo & lhs = l.c_struct;
453     int res = strcmp(rhs.code, lhs.code);
454     if (res < 0) return true;
455     if (res > 0) return false;
456     res = strcmp(rhs.variety,lhs.variety);
457     if (res < 0) return true;
458     if (res > 0) return false;
459     if (rhs.size < lhs.size) return true;
460     if (rhs.size > lhs.size) return false;
461     res = strcmp(rhs.module->name,lhs.module->name);
462     if (res < 0) return true;
463     return false;
464   }
465 
get_dict_file_name(const DictInfo * mi,String & main_wl,String & flags)466   PosibErr<void> get_dict_file_name(const DictInfo * mi,
467 				    String & main_wl, String & flags)
468   {
469     const DictInfoNode * node = reinterpret_cast<const DictInfoNode *>(mi);
470     if (node->direct) {
471       main_wl = node->info_file;
472       flags   = "";
473       return no_err;
474     } else {
475       FStream f;
476       RET_ON_ERR(f.open(node->info_file, "r"));
477       String buf; DataPair dp;
478       bool res = getdata_pair(f, dp, buf);
479       main_wl = dp.key; flags = dp.value;
480       f.close();
481       if (!res)
482 	return make_err(bad_file_format,  node->info_file, "");
483       return no_err;
484     }
485   }
486 
487   /////////////////////////////////////////////////////////////////
488   //
489   // Lists of Info Lists Impl
490   //
491 
get_data_dirs(Config * config,StringList & lst)492   void get_data_dirs (Config * config,
493 		      StringList & lst)
494   {
495     lst.clear();
496     lst.add(config->retrieve("data-dir"));
497     lst.add(config->retrieve("dict-dir"));
498   }
499 
DictExt(ModuleInfo * m,const char * e)500   DictExt::DictExt(ModuleInfo * m, const char * e)
501   {
502     module = m;
503     ext_size = strlen(e);
504     assert(ext_size <= max_ext_size);
505     memcpy(ext, e, ext_size + 1);
506   }
507 
clear()508   void MDInfoListAll::clear()
509   {
510     module_info_list.clear();
511     dict_dirs.clear();
512     dict_exts.clear();
513     dict_info_list.clear();
514   }
515 
fill(Config * c,StringList & dirs)516   PosibErr<void> MDInfoListAll::fill(Config * c,
517                                      StringList & dirs)
518   {
519     PosibErr<void> err;
520 
521     err = fill_dict_aliases(c);
522     if (err.has_err()) goto RETURN_ERROR;
523 
524     for_dirs = dirs;
525     err = module_info_list.fill(*this, c);
526     if (err.has_err()) goto RETURN_ERROR;
527 
528     fill_helper_lists(dirs);
529     err = dict_info_list.fill(*this, c);
530     if (err.has_err()) goto RETURN_ERROR;
531 
532     return err;
533 
534   RETURN_ERROR:
535     clear();
536     return err;
537   }
538 
fill_helper_lists(const StringList & def_dirs)539   void MDInfoListAll::fill_helper_lists(const StringList & def_dirs)
540   {
541     dict_dirs = def_dirs;
542     dict_exts.append(DictExt(0, ".awli"));
543 
544     for (ModuleInfoNode * n = module_info_list.head_; n != 0; n = n->next)
545     {
546       {
547 	StringListEnumeration e = n->dict_dirs.elements_obj();
548 	const char * item;
549 	while ( (item = e.next()) != 0 )
550 	  dict_dirs.add(item);
551       }{
552 	StringListEnumeration e = n->dict_exts.elements_obj();
553 	const char * item;
554 	while ( (item = e.next()) != 0 )
555 	  dict_exts.append(DictExt(&n->c_struct, item));
556       }
557     }
558   }
559 
fill_dict_aliases(Config * c)560   PosibErr<void> MDInfoListAll::fill_dict_aliases(Config * c)
561   {
562     StringList aliases;
563     c->retrieve_list("dict-alias", &aliases);
564     StringListEnumeration els = aliases.elements_obj();
565     const char * str;
566     while ( (str = els.next()) != 0) {
567       const char * end = strchr(str, ' ');
568       if (!end) return make_err(bad_value, "dict-alias", str,
569                                 _("in the form \"<name> <value>\""));
570       String name(str, end - str);
571       while (asc_isspace(*end)) ++end;
572       dict_aliases.insert(name.str(), end);
573     }
574     return no_err;
575   }
576 
577 
MDInfoListofLists()578   MDInfoListofLists::MDInfoListofLists()
579     : data(0), offset(0), size(0)
580   {
581   }
582 
~MDInfoListofLists()583   MDInfoListofLists::~MDInfoListofLists() {
584     for (int i = offset; i != offset + size; ++i)
585       data[i].clear();
586     delete[] data;
587   }
588 
clear(Config * c)589   void MDInfoListofLists::clear(Config * c)
590   {
591     StringList dirs;
592     get_data_dirs(c, dirs);
593     int pos = find(dirs);
594     if (pos == -1) {
595       data[pos - offset].clear();
596     }
597   }
598 
find(const StringList & key)599   int MDInfoListofLists::find(const StringList & key)
600   {
601     for (int i = 0; i != size; ++i) {
602       if (data[i].key == key)
603 	return i + offset;
604     }
605     return -1;
606   }
607 
608   PosibErr<const MDInfoListAll *>
get_lists(Config * c)609   MDInfoListofLists::get_lists(Config * c)
610   {
611     LOCK(&lock);
612     Config * config = (Config *)c; // FIXME: WHY?
613     int & pos = config->md_info_list_index;
614     StringList dirs;
615     StringList key;
616     if (!valid_pos(pos)) {
617       get_data_dirs(config, dirs);
618       key = dirs;
619       key.add("////////");
620       config->retrieve_list("dict-alias", &key);
621       pos = find(key);
622     }
623     if (!valid_pos(pos)) {
624       MDInfoListAll * new_data = new MDInfoListAll[size + 1];
625       for (int i = 0; i != size; ++i) {
626 	new_data[i] = data[i];
627       }
628       ++size;
629       delete[] data;
630       data = new_data;
631       pos = size - 1 + offset;
632     }
633     MDInfoListAll & list_all = data[pos - offset];
634     if (list_all.has_data())
635       return &list_all;
636 
637     list_all.key = key;
638     RET_ON_ERR(list_all.fill(config, dirs));
639 
640     return &list_all;
641   }
642 
643   /////////////////////////////////////////////////////////////////
644   //
645   // utility functions
646   //
647 
strnchr(const char * i,char c,unsigned int size)648   static const char * strnchr(const char * i, char c, unsigned int size)
649   {
650     const char * stop = i + size;
651     while (i != stop) {
652       if (*i == c)
653 	return i;
654       ++i;
655     }
656     return 0;
657   }
658 
strnrchr(const char * stop,char c,unsigned int size)659   static const char * strnrchr(const char * stop, char c, unsigned int size)
660   {
661     const char * i = stop + size - 1;
662     --stop;
663     while (i != stop) {
664       if (*i == c)
665 	return i;
666       --i;
667     }
668     return 0;
669   }
670 
671   /////////////////////////////////////////////////////////////////
672   //
673   // user visible functions and enumeration impl
674   //
675 
676   //
677   // ModuleInfo
678   //
679 
get_module_info_list(Config * c)680   PosibErr<const ModuleInfoList *> get_module_info_list(Config * c)
681   {
682     RET_ON_ERR_SET(md_info_list_of_lists.get_lists(c), const MDInfoListAll *, la);
683     return &la->module_info_list;
684   }
685 
elements() const686   ModuleInfoEnumeration * ModuleInfoList::elements() const
687   {
688     return new ModuleInfoEnumeration((ModuleInfoNode *)head_);
689   }
690 
size() const691   unsigned int ModuleInfoList::size() const
692   {
693     return size_;
694   }
695 
empty() const696   bool ModuleInfoList::empty() const
697   {
698     return size_ != 0;
699   }
700 
clone() const701   ModuleInfoEnumeration * ModuleInfoEnumeration::clone () const
702   {
703     return new ModuleInfoEnumeration(*this);
704   }
705 
assign(const ModuleInfoEnumeration * other)706   void ModuleInfoEnumeration::assign(const ModuleInfoEnumeration * other)
707   {
708     *this = *other;
709   }
710 
at_end() const711   bool ModuleInfoEnumeration::at_end () const
712   {
713     return node_ == 0;
714   }
715 
next()716   const ModuleInfo * ModuleInfoEnumeration::next ()
717   {
718     if (node_ == 0) return 0;
719     const ModuleInfo * data = &(node_->c_struct);
720     node_ = (ModuleInfoNode *)(node_->next);
721     return data;
722   }
723 
724   //
725   // DictInfo
726   //
727 
get_dict_info_list(Config * c)728   const DictInfoList * get_dict_info_list(Config * c)
729   {
730     const MDInfoListAll * la = md_info_list_of_lists.get_lists(c);
731     if (la == 0) return 0;
732     else return &la->dict_info_list;
733   }
734 
get_dict_aliases(Config * c)735   const StringMap * get_dict_aliases(Config * c)
736   {
737     const MDInfoListAll * la = md_info_list_of_lists.get_lists(c);
738     if (la == 0) return 0;
739     else return &la->dict_aliases;
740   }
741 
elements() const742   DictInfoEnumeration * DictInfoList::elements() const
743   {
744     return new DictInfoEnumeration(static_cast<DictInfoNode *>(head_));
745   }
746 
size() const747   unsigned int DictInfoList::size() const
748   {
749     return size_;
750   }
751 
empty() const752   bool DictInfoList::empty() const
753   {
754     return size_ != 0;
755   }
756 
clone() const757   DictInfoEnumeration * DictInfoEnumeration::clone() const
758   {
759     return new DictInfoEnumeration(*this);
760   }
761 
assign(const DictInfoEnumeration * other)762   void DictInfoEnumeration::assign(const DictInfoEnumeration * other)
763   {
764     *this = *other;
765   }
766 
at_end() const767   bool DictInfoEnumeration::at_end() const
768   {
769     return node_ == 0;
770   }
771 
next()772   const DictInfo * DictInfoEnumeration::next ()
773   {
774     if (node_ == 0) return 0;
775     const DictInfo * data = &(node_->c_struct);
776     node_ = (DictInfoNode *)(node_->next);
777     return data;
778   }
779 
780 }
781