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