COOPY » Guide
version 0.6.5
|
00001 #include <coopy/SheetPatcher.h> 00002 #include <coopy/NameSniffer.h> 00003 00004 #include <map> 00005 #include <algorithm> 00006 00007 #include <stdio.h> 00008 00009 #define WANT_MAP2STRING 00010 #define WANT_VECTOR2STRING 00011 #include <coopy/Stringer.h> 00012 00013 using namespace std; 00014 using namespace coopy::cmp; 00015 using namespace coopy::store; 00016 00017 #define FULL_COLOR (65535) 00018 #define HALF_COLOR (65535/2) 00019 00020 #define FULL_DEBUG 00021 00022 static bool null_like(const SheetCell& a) { 00023 return a.escaped||a.text==""||a.text=="NULL"; 00024 } 00025 00026 static bool is_match(const SheetCell& a, const SheetCell& b) { 00027 return a==b; 00028 //if (a==b) return true; 00029 //if (!null_like(a)) return false; 00030 //return null_like(b); 00031 } 00032 00033 void SheetPatcher::checkHeader() { 00034 if (checkedHeader) return; 00035 checkedHeader = true; 00036 if (flags.assume_header==false) return; 00037 PolySheet sheet = getSheet(); 00038 if (!sheet.isValid()) return; 00039 int c = 0; 00040 if (activeCol.width() == sheet.width()) { 00041 for (c=0; c<sheet.width(); c++) { 00042 if (sheet.cellString(c,0) != activeCol.cellString(c,0)) { 00043 break; 00044 } 00045 } 00046 } 00047 if (c==sheet.width()) { 00048 if (!sheet.hasExternalColumnNames()) { 00049 dbg_printf("HEADER LINE NUDGE\n"); 00050 //sheet.mustHaveSchema(); 00051 rowCursor = 1; 00052 } 00053 } 00054 } 00055 00056 int SheetPatcher::matchRow(const std::vector<int>& active_cond, 00057 const std::vector<std::string>& active_name, 00058 const std::vector<SheetCell>& cond, 00059 int width, bool show) { 00060 PolySheet sheet = getSheet(); 00061 if (!sheet.isValid()) return false; 00062 { 00063 int c; 00064 for (c=0; c<width; c++) { 00065 if (active_cond[c]) { 00066 if (cond[c].text != active_name[c]) { 00067 break; 00068 } 00069 } 00070 } 00071 if (c==width) { 00072 //printf("WORKING WITH: %s // %s\n", sheet.toString().c_str(), 00073 //vector2string(sheet.getNestedDescription()).c_str()); 00074 //if (sheet.hasRowOffset()||sheet.hasExternalColumnNames()) { 00075 if (sheet.getSchema()&&!sheet.hasExternalColumnNames()) { 00076 dbg_printf("HEADER LINE MATCH\n"); 00077 return -2; 00078 } 00079 } 00080 } 00081 00082 int r = -1; 00083 int bct = 0; 00084 int rbest = -1; 00085 for (r=0; r<sheet.height(); r++) { 00086 int ct = 0; 00087 if (activeRow.cellString(0,r)!="---") { 00088 bool match = true; 00089 for (int c=0; c<width; c++) { 00090 if (active_cond[c]) { 00091 if (!is_match(sheet.cellSummary(c,r),cond[c])) { 00092 match = false; 00093 if (!show) { 00094 break; 00095 } 00096 } else { 00097 ct++; 00098 if (ct>bct) { 00099 bct = ct; 00100 rbest = r; 00101 } 00102 } 00103 } 00104 } 00105 if (match) { 00106 dbg_printf("Found row %d\n", r); 00107 return r; 00108 } 00109 } 00110 } 00111 if (!show) { 00112 dbg_printf("No match for update\n"); 00113 matchRow(active_cond,active_name,cond,width,true); 00114 } 00115 if (show) { 00116 fprintf(stderr,"# No match for update"); 00117 if (sheet.getSchema()) { 00118 fprintf(stderr," in %s",sheet.getSchema()->getSheetName().c_str()); 00119 } 00120 if (rbest>=0) { 00121 fprintf(stderr," - closest was:\n"); 00122 for (int c=0; c<width; c++) { 00123 if (active_cond[c]) { 00124 fprintf(stderr,"# '%s' <-> '%s' %s\n", 00125 sheet.cellSummary(c,rbest).text.c_str(), 00126 cond[c].text.c_str(), 00127 (sheet.cellSummary(c,rbest)!=cond[c])?"FAIL":"OK"); 00128 } 00129 } 00130 } else { 00131 fprintf(stderr,"\n"); 00132 } 00133 } 00134 return -1; 00135 } 00136 00137 int SheetPatcher::matchCol(const std::string& mover) { 00138 string imover = mover; 00139 if (syn2name.find(mover)!=syn2name.end()) { 00140 imover = syn2name[mover]; 00141 } 00142 for (int i=0; i<activeCol.width(); i++) { 00143 //printf("Checking %s against %s\n", activeCol.cellString(i,0).c_str(), 00144 //mover.c_str()); 00145 if (activeCol.cellString(i,0)==imover) { 00146 if (statusCol.cellString(i,0)!="---") { 00147 //printf("Match at %d (%s)\n", i, mover.c_str()); 00148 return i; 00149 } 00150 } 00151 } 00152 fprintf(stderr,"column not found: %s\n", mover.c_str()); 00153 //PolySheet sheet = getSheet(); 00154 exit(1); 00155 return -1; 00156 } 00157 00158 int SheetPatcher::updateCols() { 00159 PolySheet sheet = getSheet(); 00160 /* 00161 { 00162 printf("<activeCol>"); 00163 for (int i=0; i<activeCol.width(); i++) { 00164 string name = activeCol.cellString(i,0); 00165 printf(" %s", name.c_str()); 00166 } 00167 printf("\n"); 00168 SheetSchema *ss = sheet.getSchema(); 00169 if (ss!=NULL) { 00170 printf("<schema> "); 00171 for (int i=0; i<ss->getColumnCount(); i++) { 00172 string name = ss->getColumnInfo(i).getName(); 00173 printf(" %s", name.c_str()); 00174 } 00175 printf("\n"); 00176 for (int i=0; i<ss->getColumnCount(); i++) { 00177 string name = ss->getColumnInfo(i).getName(); 00178 if (i<activeCol.width()) { 00179 string name2 = activeCol.cellString(i,0); 00180 if (name!=name2) { 00181 printf(" %s <---> %s\n", name.c_str(), name2.c_str()); 00182 exit(1); 00183 break; 00184 } 00185 } 00186 } 00187 } 00188 printf("<sheet> "); 00189 for (int i=0; i<sheet.width(); i++) { 00190 string name = sheet.cellString(i,0); 00191 printf(" %s", name.c_str()); 00192 } 00193 printf("\n"); 00194 } 00195 */ 00196 if (activeCol.width()!=sheet.width()) { 00197 printf("* Please report bug: activeCol drift, %s %d\n", 00198 __FILE__, 00199 __LINE__); 00200 exit(1); 00201 } 00202 name2col.clear(); 00203 for (int i=0; i<activeCol.width(); i++) { 00204 string name = activeCol.cellString(i,0); 00205 //printf("NAME %d %s\n", i, name.c_str()); 00206 if (name!="---") { 00207 name2col[name] = i; 00208 col2name[i] = name; 00209 if (name2syn.find(name)!=name2syn.end()) { 00210 name2col[name2syn[name]] = i; 00211 } 00212 } 00213 } 00214 return 0; 00215 } 00216 00217 bool SheetPatcher::renameColumn(int idx, const std::string& name, 00218 const std::string& oldName) { 00219 PolySheet sheet = getSheet(); 00220 dbg_printf("Should rename column %d to %s\n", idx, name.c_str()); 00221 bool ok = sheet.modifyColumn(ColumnRef(idx),ColumnInfo(name)); 00222 activeCol.cellString(idx,0,name); 00223 statusCol.cellString(idx,0, 00224 statusCol.cellString(idx,0) + 00225 string("(") + oldName + ")"); 00226 if (!ok) { 00227 fprintf(stderr,"Failed to rename column to %s\n", name.c_str()); 00228 } 00229 updateCols(); 00230 return true; 00231 } 00232 00233 bool SheetPatcher::moveColumn(int idx, int idx2) { 00234 //printf("MOVE %d to %d\n", idx, idx2); 00235 PolySheet sheet = getSheet(); 00236 ColumnRef from(idx); 00237 ColumnRef to(idx2); 00238 bool ok = sheet.moveColumn(from,to).isValid(); 00239 activeCol.moveColumn(from,to); 00240 ColumnRef at = statusCol.moveColumn(from,to); 00241 if (descriptive) { 00242 /* 00243 int first = idx; 00244 int final = at.getIndex(); 00245 int xfinal = final; 00246 int sgn = 1; 00247 string name = ""; 00248 string ch = ">"; 00249 if (final<first) { 00250 first = final; 00251 final = idx; 00252 sgn = -1; 00253 ch = "<"; 00254 } 00255 for (int i=first; i<final; i++) { 00256 if (statusCol.cellString(i,0)!="---") { 00257 name += ch; 00258 } 00259 } 00260 */ 00261 for (int k=0; k<(int)activeCol.width(); k++) { 00262 string name = activeCol.cellString(k,0); 00263 string code = statusCol.cellString(k,0); 00264 char ch = code[0]; 00265 if (ch=='-'||ch=='+') continue; 00266 map<string,int>::iterator it = 00267 original_index.find(name); 00268 if (it == original_index.end()) { 00269 continue; 00270 } 00271 int first = k; 00272 int last = it->second; 00273 ch = '<'; 00274 string txt = ""; 00275 int sgn = 1; 00276 if (last<first) { 00277 sgn = -1; 00278 ch = '>'; 00279 } 00280 for (int i=first; i!=last; i += sgn) { 00281 txt += ch; 00282 } 00283 statusCol.cellString(k,0,txt); 00284 } 00285 } 00286 updateCols(); 00287 return ok; 00288 } 00289 00290 bool SheetPatcher::changeColumn(const OrderChange& change) { 00291 sheetUpdateNeeded = true; 00292 changeCount++; 00293 if (chain) chain->changeColumn(change); 00294 00295 dbg_printf("\n======================\nChange column...\n"); 00296 00297 PolySheet sheet = getSheet(); 00298 if (!sheet.isValid()) { 00299 fprintf(stderr,"No sheet available to patch\n"); 00300 return false; 00301 } 00302 00303 #ifdef FULL_DEBUG 00304 dbg_printf("STATE<<EOF\n%sEOF\n",sheet.toString().c_str()); 00305 #endif 00306 00307 switch (change.mode) { 00308 case ORDER_CHANGE_DELETE: 00309 //return sheet->deleteColumn(ColumnRef(change.subject)); 00310 { 00311 string mover = change.namesBefore[change.identityToIndex(change.subject)]; 00312 //printf("Deleting %s\n", mover.c_str()); 00313 int idx = matchCol(mover); 00314 bool ok = true; 00315 if (!descriptive) { 00316 ColumnRef col(idx); //change.identityToIndex(change.subject)); 00317 ok = sheet.deleteColumn(col); 00318 activeCol.deleteColumn(col); 00319 statusCol.deleteColumn(col); 00320 if (sheet.width()==0) { 00321 sheet.deleteData(); 00322 rowCursor = -1; 00323 } 00324 } else { 00325 statusCol.cellString(idx,0,"---"); 00326 if (descriptive) { 00327 Poly<Appearance> appear = sheet.getColAppearance(idx); 00328 if (appear.isValid()) { 00329 appear->begin(); 00330 appear->setBackgroundRgb16(FULL_COLOR, 00331 HALF_COLOR, 00332 HALF_COLOR, 00333 AppearanceRange::full()); 00334 appear->setStrikethrough(true,AppearanceRange::full()); 00335 appear->end(); 00336 } 00337 } 00338 bool gone = true; 00339 for (int i=0; i<(int)statusCol.width(); i++) { 00340 if (statusCol.cellString(i,0)=="") { 00341 gone = false; 00342 break; 00343 } 00344 } 00345 if (gone) { 00346 killNeutral = true; 00347 } 00348 } 00349 updateCols(); 00350 return ok; 00351 } 00352 break; 00353 case ORDER_CHANGE_INSERT: 00354 { 00355 int toSheet = change.identityToIndexAfter(change.subject); 00356 string mover = change.namesAfter[toSheet]; 00357 string before = ""; 00358 int idx = -1; 00359 if (toSheet!=change.indicesAfter.size()-1) { 00360 before = change.namesAfter[toSheet+1]; 00361 idx = matchCol(before); 00362 } 00363 ColumnInfo ci(mover); 00364 dbg_printf("Inserting column [%s]\n", mover.c_str()); 00365 bool ok = sheet.insertColumn(ColumnRef(idx),ci).isValid(); 00366 ColumnRef at = activeCol.insertColumn(ColumnRef(idx),ci); 00367 statusCol.insertColumn(ColumnRef(idx),ci); 00368 activeCol.cellString(at.getIndex(),0,mover); 00369 statusCol.cellString(at.getIndex(),0,"+++"); 00370 if (descriptive) { 00371 Poly<Appearance> appear = sheet.getColAppearance(at.getIndex()); 00372 if (appear.isValid()) { 00373 appear->begin(); 00374 appear->setBackgroundRgb16(HALF_COLOR, 00375 FULL_COLOR, 00376 HALF_COLOR, 00377 AppearanceRange::full()); 00378 appear->end(); 00379 } 00380 } 00381 updateCols(); 00382 return ok; 00383 } 00384 break; 00385 case ORDER_CHANGE_MOVE: 00386 //return sheet->moveColumn(ColumnRef(change.subject), 00387 //ColumnRef(change.object) 00388 //).isValid(); 00389 { 00390 int toSheet = change.identityToIndexAfter(change.subject); 00391 string mover = change.namesAfter[toSheet]; 00392 int idx = matchCol(mover); 00393 string before = ""; 00394 int idx2 = -1; 00395 //printf("after %d for %d\n", toSheet, change.subject); 00396 if (toSheet!=change.indicesAfter.size()-1) { 00397 before = change.namesAfter[toSheet+1]; 00398 //printf("Before %s\n", before.c_str()); 00399 idx2 = matchCol(before); 00400 } 00401 //printf("Moving %s %d->%d\n", mover.c_str(), idx, idx2); 00402 bool ok = moveColumn(idx,idx2); 00403 return ok; 00404 } 00405 break; 00406 case ORDER_CHANGE_RENAME: 00407 { 00408 int fromSheet = change.identityToIndex(change.subject); 00409 int toSheet = change.identityToIndexAfter(change.subject); 00410 string target = change.namesBefore[fromSheet]; 00411 string newName = change.namesAfter[toSheet]; 00412 int idx = matchCol(target); 00413 return renameColumn(idx,newName,target); 00414 } 00415 break; 00416 default: 00417 fprintf(stderr,"* ERROR: Unknown column operation\n"); 00418 break; 00419 } 00420 return false; 00421 } 00422 00423 static string colorEncode(const SheetCell& c) { 00424 std::string str = c.text; 00425 if (!c.escaped) { 00426 int score = 0; 00427 for (score=0; score<(int)str.length(); score++) { 00428 if (str[score]!='_') { 00429 break; 00430 } 00431 } 00432 if (str.substr(score,str.length())=="NULL") { 00433 str = std::string("_") + str; 00434 } 00435 return str; 00436 } 00437 return "NULL"; 00438 } 00439 00440 bool SheetPatcher::markChanges(const RowChange& change, int r,int width, 00441 std::vector<int>& active_val, 00442 std::vector<SheetCell>& val, 00443 std::vector<SheetCell>& cval, 00444 std::vector<SheetCell>& pval) { 00445 PolySheet sheet = getSheet(); 00446 00447 00448 // Check if there is at least one change that is not just an 00449 // addition 00450 00451 bool have_mod = false; 00452 bool have_add = false; 00453 for (int c=0; c<width; c++) { 00454 if (active_val[c]) { 00455 string key = statusCol.cellString(c,0); 00456 if (key!="+++"&&key!="---") { 00457 have_mod = true; 00458 } 00459 if (key=="+++") { 00460 have_add = true; 00461 } 00462 } 00463 } 00464 string row_key = sheet.cellString(0,r); 00465 00466 string separator = ""; 00467 string conflict_separator = ""; 00468 for (int c=0; c<width; c++) { 00469 if (active_val[c]) { 00470 string key = statusCol.cellString(c,0); 00471 bool this_mod = (key!="+++"); 00472 if (descriptive) { 00473 if (have_mod) { 00474 if (separator=="") { 00475 separator = "->"; 00476 bool more = true; 00477 while (more) { 00478 more = false; 00479 for (int i=0; i<width; i++) { 00480 SheetCell prev = sheet.cellSummary(i,r); 00481 if (prev.text.find(separator)!=string::npos) { 00482 separator = string("-") + separator; 00483 more = true; 00484 break; 00485 } 00486 } 00487 } 00488 } 00489 } else if (have_add) { 00490 separator = "+"; 00491 } 00492 } 00493 string init = separator; 00494 if (change.conflicted) { 00495 if (conflict_separator=="") { 00496 conflict_separator = string("!"); 00497 bool more = true; 00498 // this is actually far too conservative 00499 while (more) { 00500 more = false; 00501 for (int i=0; i<width; i++) { 00502 SheetCell prev = sheet.cellSummary(i,r); 00503 if (prev.text.find(conflict_separator)!=string::npos) { 00504 conflict_separator = string("-") + conflict_separator; 00505 more = true; 00506 break; 00507 } 00508 } 00509 } 00510 } 00511 init = conflict_separator + separator; 00512 } 00513 if (init.length()>activeRow.cellString(0,r).length()) { 00514 activeRow.cellString(0,r,init); 00515 } 00516 if (change.conflicted) { 00517 if (descriptive) { 00518 Poly<Appearance> appear = sheet.getCellAppearance(0,r); 00519 if (appear.isValid()) { 00520 appear->begin(); 00521 appear->setBackgroundRgb16(FULL_COLOR, 00522 0, 00523 0, 00524 AppearanceRange::full()); 00525 appear->end(); 00526 } 00527 } 00528 } 00529 if (descriptive) { 00530 SheetCell prev = sheet.cellSummary(c,r); 00531 string from = prev.toString(); 00532 if (prev.escaped) from = ""; 00533 //string to = val[c].toString(); 00534 SheetCell to = val[c]; 00535 bool conflicted = false; 00536 if (val[c]!=cval[c]) { 00537 to = cval[c]; 00538 conflicted = true; 00539 } 00540 SheetStyle style; 00541 if (this_mod||conflicted) { 00542 if (conflicted&&!pval[c].escaped) { 00543 sheet.cellString(c,r, 00544 colorEncode(pval[c]) + 00545 (conflicted?init:separator) + 00546 colorEncode(prev) + 00547 (conflicted?init:separator) + 00548 colorEncode(to)); 00549 } else { 00550 sheet.cellString(c,r,colorEncode(prev) + 00551 (conflicted?init:separator) + 00552 colorEncode(to)); 00553 } 00554 } else { 00555 /* 00556 if (row_key=="---"&&key=="+++") { 00557 // no point adding anything here 00558 } else if (row_key=="+++"&&key=="---") { 00559 // no point adding anything here 00560 } else { 00561 */ 00562 sheet.cellSummary(c,r,to); 00563 } 00564 if (this_mod||conflicted) { 00565 Poly<Appearance> appear = sheet.getCellAppearance(c,r); 00566 if (appear.isValid()) { 00567 appear->begin(); 00568 if (conflicted) { 00569 appear->setBackgroundRgb16(FULL_COLOR, 00570 0, 00571 0, 00572 AppearanceRange::full()); 00573 } else { 00574 appear->setBackgroundRgb16(HALF_COLOR, 00575 HALF_COLOR, 00576 FULL_COLOR, 00577 AppearanceRange::full()); 00578 } 00579 appear->setWeightBold(true,AppearanceRange::full()); 00580 appear->end(); 00581 } 00582 } 00583 } else { 00584 if (change.conflicted) { 00585 sheet.cellSummary(conflictColumn,r,SheetCell("CONFLICT",false)); 00586 //printf("AT %d %d\n", conflictColumn,r); 00587 00588 Poly<Appearance> appear = sheet.getCellAppearance(conflictColumn,r); 00589 if (appear.isValid()) { 00590 appear->begin(); 00591 appear->setBackgroundRgb16(HALF_COLOR, 00592 HALF_COLOR, 00593 FULL_COLOR, 00594 AppearanceRange::full()); 00595 appear->end(); 00596 } 00597 00598 SheetCell from = pval[c]; 00599 SheetCell to = val[c]; 00600 SheetCell alt = cval[c]; 00601 if (to!=alt) { 00602 string ctxt = 00603 "((( " + from.toString() + " ))) " + 00604 to.toString() + " /// " + alt.toString(); 00605 sheet.cellSummary(c,r,SheetCell(ctxt,false)); 00606 Poly<Appearance> appear = sheet.getCellAppearance(c,r); 00607 if (appear.isValid()) { 00608 appear->begin(); 00609 appear->setBackgroundRgb16(FULL_COLOR, 00610 HALF_COLOR, 00611 HALF_COLOR, 00612 AppearanceRange::full()); 00613 appear->end(); 00614 } 00615 } 00616 } else { 00617 sheet.cellSummary(c,r,val[c]); 00618 } 00619 } 00620 } 00621 } 00622 return true; 00623 } 00624 00625 00626 class Invention { 00627 public: 00628 SheetCell origin; 00629 int offset; 00630 PoolColumnLink link; 00631 }; 00632 00633 bool SheetPatcher::handleConflicts() { 00634 if (readyForConflicts) return true; 00635 if (descriptive) return true; 00636 if (!merging) return false; 00637 for (int i=0; i<(int)column_names.size(); i++) { 00638 if (column_names[i]=="_MERGE_") { 00639 readyForConflicts = true; 00640 conflictColumn = i; 00641 return true; 00642 } 00643 } 00644 PolySheet sheet = getSheet(); 00645 if (!sheet.isValid()) return false; 00646 sheet.mustHaveSchema(); 00647 00648 //printf("SCHEMA %s\n", sheet.getSchema()->toString().c_str()); 00649 //printf("SCHEMA %s\n", active_sheet.getSchema()->toString().c_str()); 00650 //printf("sheet before %s\n", sheet.toString().c_str()); 00651 ColumnInfo con("_MERGE_"); 00652 con.setType("text","sqlite"); 00653 ColumnRef at = active_sheet.insertColumn(ColumnRef(-1),con); 00654 activeCol.insertColumn(ColumnRef(-1),con); 00655 statusCol.insertColumn(ColumnRef(-1),con); 00656 readyForConflicts = at.isValid(); 00657 conflictColumn = at.getIndex(); 00658 00659 //printf("sheet after %s\n", sheet.toString().c_str()); 00660 //printf("SCHEMA AFTER %s\n", active_sheet.getSchema()->toString().c_str()); 00661 00662 return readyForConflicts; 00663 } 00664 00665 bool SheetPatcher::changeRow(const RowChange& change) { 00666 sheetUpdateNeeded = true; 00667 00668 PolySheet sheet = getSheet(); 00669 if (!sheet.isValid()) { 00670 fprintf(stderr,"No sheet available to patch.\n"); 00671 return false; 00672 } 00673 00674 if (!declaredNames) { 00675 if (chain) chain->declareNames(change.allNames,false); 00676 declareNames(change.allNames,false); 00677 } 00678 00679 if (change.conflicted) { 00680 if (!handleConflicts()) { 00681 fprintf(stderr,"Cannot handle conflicts.\n"); 00682 return false; 00683 } 00684 } 00685 00686 if (activeRow.height()!=sheet.height() || activeRow.width()!=1) { 00687 activeRow.resize(1,sheet.height()); 00688 } 00689 00690 dbg_printf("\n======================\nRow cursor in: %d\n", rowCursor); 00691 if (coopy_is_verbose()) { 00692 RowChange c; 00693 c = change; 00694 c.show(); 00695 } 00696 00697 if (!change.sequential) rowCursor = -1; 00698 //map<string,int> dir; 00699 vector<int> active_cond; 00700 vector<string> active_name; 00701 int active_conds = 0; 00702 vector<SheetCell> cond; 00703 vector<int> active_val; 00704 vector<SheetCell> val; 00705 vector<SheetCell> cval; 00706 vector<SheetCell> pval; 00707 vector<string> allNames = change.allNames; 00708 int width = sheet.width(); //(int)change.allNames.size(); 00709 /* 00710 if (width==0) { 00711 if (column_names.size()==0) { 00712 NameSniffer sniffer(sheet); 00713 column_names = sniffer.suggestNames(); 00714 } 00715 allNames = column_names; 00716 width = (int)allNames.size(); 00717 } 00718 */ 00719 for (int i=0; i<width; i++) { 00720 //dir[allNames[i]] = i; 00721 active_cond.push_back(0); 00722 active_name.push_back(""); 00723 cond.push_back(SheetCell()); 00724 active_val.push_back(0); 00725 val.push_back(SheetCell()); 00726 cval.push_back(SheetCell()); 00727 pval.push_back(SheetCell()); 00728 } 00729 for (RowChange::txt2cell::const_iterator it = change.cond.begin(); 00730 it!=change.cond.end(); it++) { 00731 if (name2col.find(it->first)!=name2col.end()) { 00732 int idx = name2col[it->first]; //dir[it->first]; 00733 //printf(" [cond] %d %s -> %s\n", idx, it->first.c_str(), it->second.toString().c_str()); 00734 active_cond[idx] = 1; 00735 active_conds++; 00736 cond[idx] = it->second; 00737 active_name[idx] = it->first; 00738 } 00739 } 00740 for (RowChange::txt2cell::const_iterator it = change.val.begin(); 00741 it!=change.val.end(); it++) { 00742 if (name2col.find(it->first)!=name2col.end()) { 00743 int idx = name2col[it->first]; //dir[it->first]; 00744 //printf(" [val] %d %s -> %s\n", idx, it->first.c_str(), it->second.toString().c_str()); 00745 active_val[idx] = 1; 00746 val[idx] = it->second; 00747 cval[idx] = it->second; 00748 pval[idx] = it->second; 00749 } else { 00750 if (std::find(change.names.begin(),change.names.end(),it->first)!= 00751 change.names.end()) { 00752 fprintf(stderr,"Unknown column %s\n", it->first.c_str()); 00753 } 00754 } 00755 } 00756 for (RowChange::txt2cell::const_iterator it = change.conflictingVal.begin(); 00757 it!=change.conflictingVal.end(); it++) { 00758 if (name2col.find(it->first)!=name2col.end()) { 00759 int idx = name2col[it->first]; 00760 cval[idx] = it->second; 00761 } 00762 } 00763 for (RowChange::txt2cell::const_iterator it = change.conflictingParentVal.begin(); 00764 it!=change.conflictingParentVal.end(); it++) { 00765 if (name2col.find(it->first)!=name2col.end()) { 00766 int idx = name2col[it->first]; 00767 pval[idx] = it->second; 00768 } 00769 } 00770 00771 00772 bool result = false; 00773 bool defer = false; 00774 00775 switch (change.mode) { 00776 case ROW_CHANGE_INSERT: 00777 { 00778 RowRef tail(rowCursor); 00779 Poly<SheetRow> inserter; 00780 if (sheet.isSequential()) { 00781 inserter = sheet.insertRowOrdered(tail); 00782 } else { 00783 inserter = sheet.insertRow(); 00784 } 00785 vector<Invention> inventions; 00786 for (int c=0; c<width; c++) { 00787 if (active_val[c]) { 00788 PoolColumnLink link; 00789 if (flags.foreign_pool) { 00790 std::map<std::string,coopy::store::PoolColumnLink>::iterator it = 00791 name2pool.find(col2name[c]); 00792 if (it!=name2pool.end()) { 00793 link = it->second; 00794 } 00795 } 00796 if (link.isValid()&&!descriptive) { 00797 // Skip if inventor 00798 // But should try to recover id to place in pool 00799 00800 if (!link.isInventor()) { 00801 bool found = false; 00802 PoolRecord v = link.getColumn().lookup(val[c],found); 00803 if (found) { 00804 if (!v.linked) found = false; 00805 } else { 00806 if (flags.pool) { 00807 if (flags.pool->isScanned()) { 00808 // if prescanned, then unmarked records can pass through 00809 found = true; 00810 v.cell = val[c]; 00811 } 00812 } 00813 } 00814 if (!found) { 00815 /* 00816 fprintf(stderr, "Problem translating a local value to a remote value. Diagnostics:\n"); 00817 fprintf(stderr, " * Table: %s\n", link.getTableName().c_str()); 00818 fprintf(stderr, " * Column: %s\n", link.getColumnName().c_str()); 00819 fprintf(stderr, " * Value: %s\n", val[c].toString().c_str()); 00820 fprintf(stderr, "This value is not (yet) present in the '%s' pool. Solutions:\n", link.getPoolName().c_str()); 00821 fprintf(stderr, " * Use --table A --table B ... to control order in which tables are updated.\n"); 00822 fprintf(stderr, " Try to do more fundamental inserts first, then actions that reference them\n"); 00823 fprintf(stderr, " * Or rerun with --native flag if no translation is needed.\n"); 00824 //fprintf(stderr, "Exit, fatal error.\n"); 00825 //exit(1); 00826 */ 00827 00828 result = false; 00829 defer = true; 00830 inserter->undo(); 00831 break; 00832 00833 } else { 00834 inserter->setCell(c,v.cell); 00835 } 00836 } else { 00837 inserter->invent(c); 00838 Invention invent = { val[c], c, link }; 00839 inventions.push_back(invent); 00840 } 00841 } else { 00842 inserter->setCell(c,val[c]); 00843 } 00844 } 00845 } 00846 if (defer) { 00847 break; 00848 } 00849 inserter->flush(); 00850 if (inventions.size()>0) { 00851 int r = inserter->getRowAfterFlush().getIndex(); 00852 for (int i=0; i<(int)inventions.size(); i++) { 00853 Invention& inv = inventions[i]; 00854 SheetCell dest = sheet.cellSummary(inv.offset,r); 00855 inv.link.getColumn().put(inv.origin,dest); 00856 dbg_printf("Link %s -> %s\n", inv.origin.toString().c_str(), 00857 dest.toString().c_str()); 00858 } 00859 } 00860 if (sheet.isSequential()) { 00861 int r = inserter->getRowAfterFlush().getIndex(); 00862 activeRow.insertRowOrdered(tail); 00863 activeRow.cellString(0,r,"+++"); 00864 if (descriptive) { 00865 Poly<Appearance> appear = sheet.getRowAppearance(r); 00866 if (appear.isValid()) { 00867 appear->begin(); 00868 appear->setBackgroundRgb16(HALF_COLOR, 00869 FULL_COLOR, 00870 HALF_COLOR, 00871 AppearanceRange::full()); 00872 appear->setWeightBold(true,AppearanceRange::full()); 00873 appear->end(); 00874 } 00875 } 00876 r++; 00877 if (r>=sheet.height()) { 00878 r = -1; 00879 } 00880 rowCursor = r; 00881 } 00882 } 00883 break; 00884 case ROW_CHANGE_DELETE: 00885 { 00886 int r = matchRow(active_cond,active_name,cond,width); 00887 if (r<0) { 00888 result = false; 00889 break; 00890 } 00891 RowRef row(r); 00892 rowCursor = r; 00893 if (!descriptive) { 00894 sheet.deleteRow(row); 00895 activeRow.deleteRow(row); 00896 } else { 00897 Poly<Appearance> appear = sheet.getRowAppearance(r); 00898 if (appear.isValid()) { 00899 appear->begin(); 00900 appear->setBackgroundRgb16(FULL_COLOR, 00901 HALF_COLOR, 00902 HALF_COLOR, 00903 AppearanceRange::full()); 00904 //appear->setWeightBold(true,AppearanceRange::full()); 00905 appear->setStrikethrough(true,AppearanceRange::full()); 00906 appear->end(); 00907 } 00908 activeRow.cellString(0,r,"---"); 00909 } 00910 if (rowCursor>=sheet.height()) { 00911 rowCursor = -1; 00912 } 00913 result = true; 00914 } 00915 break; 00916 case ROW_CHANGE_CONTEXT: 00917 { 00918 if (active_conds>0) { 00919 int r = matchRow(active_cond,active_name,cond,width); 00920 if (r!=-2) { 00921 if (r<0) { 00922 result = false; 00923 break; 00924 } 00925 r++; 00926 } else { 00927 r = 0; 00928 } 00929 if (r>=sheet.height()) { 00930 r = -1; 00931 } 00932 RowRef row(r); 00933 rowCursor = r; 00934 } else { 00935 rowCursor = 0; 00936 checkHeader(); 00937 } 00938 result = true; 00939 } 00940 break; 00941 case ROW_CHANGE_MOVE: 00942 { 00943 bool success = false; 00944 int r = matchRow(active_cond,active_name,cond,width); 00945 if (r<0) { 00946 result = false; 00947 break; 00948 } 00949 RowRef from(r); 00950 RowRef to(rowCursor); 00951 dbg_printf("Moving %d to %d in sheet of length %d\n", from.getIndex(), to.getIndex(), sheet.height()); 00952 RowRef result = sheet.moveRow(from,to); 00953 if (result.getIndex() == -1) { 00954 fprintf(stderr,"Row move failed in sheet of type %s\n", 00955 sheet.desc().c_str()); 00956 } else { 00957 activeRow.moveRow(from,to); 00958 } 00959 r = result.getIndex(); 00960 dbg_printf("Move result was %d\n", r); 00961 for (int y=0; y<sheet.height(); y++) { 00962 dbg_printf("%d %s / ", y, sheet.cellString(0,y).c_str()); 00963 } 00964 dbg_printf("\n"); 00965 markChanges(change,r,width,active_val,val,cval,pval); 00966 r++; 00967 if (r>=sheet.height()) { 00968 r = -1; 00969 } 00970 rowCursor = r; 00971 result = true; 00972 } 00973 break; 00974 case ROW_CHANGE_UPDATE: 00975 { 00976 bool success = false; 00977 int r = matchRow(active_cond,active_name,cond,width); 00978 if (r<0) { 00979 rowCursor = -1; 00980 result = false; 00981 break; 00982 } 00983 dbg_printf("Match for assignment\n"); 00984 markChanges(change,r,width,active_val,val,cval,pval); 00985 r++; 00986 if (r>=sheet.height()) { 00987 r = -1; 00988 } 00989 rowCursor = r; 00990 dbg_printf("Cursor moved to %d\n", r); 00991 result = true; 00992 } 00993 break; 00994 default: 00995 fprintf(stderr,"* ERROR: unsupported row operation\n"); 00996 result = false; 00997 break; 00998 } 00999 01000 if (defer) { 01001 deferred_rows.push_back(RowUnit(sheetName,change)); 01002 dbg_printf("DEFERRED a row\n"); 01003 } 01004 01005 if (result) { 01006 changeCount++; 01007 if (chain) chain->changeRow(change); 01008 } 01009 01010 return result; 01011 } 01012 01013 bool SheetPatcher::declareNames(const std::vector<std::string>& names, 01014 bool final) { 01015 bool first = !declaredNames; 01016 declaredNames = true; 01017 PolySheet sheet = getSheet(); 01018 if (!sheet.isValid()) return false; 01019 if (chain) chain->declareNames(names,final); 01020 if (config.trustNames==false) { 01021 if (!descriptive) { 01022 if ((int)names.size()!=sheet.width()) { 01023 //fprintf(stderr,"* ERROR: name mismatch\n"); 01024 return false; 01025 } 01026 } 01027 if (first) { 01028 for (int i=0; i<(int)names.size(); i++) { 01029 if (names[i]!=activeCol.cellString(i,0)) { 01030 //if (names[i][0]=='[') { 01031 syn2name[names[i]] = activeCol.cellString(i,0); 01032 name2syn[activeCol.cellString(i,0)] = names[i]; 01033 //} 01034 } 01035 } 01036 updateCols(); 01037 } else { 01038 if (!descriptive) { 01039 for (int i=0; i<(int)names.size(); i++) { 01040 if (names[i]!=activeCol.cellString(i,0)) { 01041 if (names[i][0]!='[') { 01042 int idx = matchCol(names[i]); 01043 int idx1 = matchCol(activeCol.cellString(i,0)); 01044 moveColumn(idx,idx1); 01045 } 01046 } 01047 } 01048 updateCols(); 01049 } 01050 } 01051 } else { 01052 for (int i=0; i<(int)names.size(); i++) { 01053 printf("Checking %s\n", names[i].c_str()); 01054 } 01055 fprintf(stderr,"Named columns not implemented yet\n"); 01056 exit(1); 01057 } 01058 original_index.clear(); 01059 for (int i=0; i<(int)names.size(); i++) { 01060 original_index[names[i]] = i; 01061 } 01062 return true; 01063 } 01064 01065 bool SheetPatcher::setSheet(const char *name) { 01066 checkedHeader = false; 01067 updateSheet(); 01068 sheetUpdateNeeded = true; 01069 01070 if (chain) chain->setSheet(name); 01071 01072 TextBook *book = getBook(); 01073 if (book==NULL) return false; 01074 01075 //reset 01076 config = ConfigChange(); 01077 columns.clear(); 01078 column_names.clear(); 01079 rowCursor = -1; 01080 declaredNames = false; 01081 01082 // load 01083 PolySheet psheet; 01084 attachSheet(psheet); 01085 psheet = book->readSheet(name); 01086 if (!psheet.isValid()) { 01087 if (!book->namedSheets()) { 01088 psheet = book->readSheetByIndex(0); 01089 } 01090 } 01091 if (!psheet.isValid()) { 01092 if (string(name)=="sheet") { // default name 01093 if (book->namedSheets()) { 01094 if (book->getNames().size() == 1) { 01095 psheet = book->readSheetByIndex(0); 01096 } 01097 } 01098 } 01099 } 01100 if (!psheet.isValid()) { 01101 fprintf(stderr,"Cannot find patch\n", name); 01102 return false; 01103 } 01104 dbg_printf("Moved to sheet %s\n", name); 01105 attachSheet(psheet); 01106 //sheet = &psheet; 01107 01108 if (sheetName!=name) { 01109 sheetName = name; 01110 sheetChange = true; 01111 } 01112 01113 setNames(); 01114 01115 addPoolsFromFlags(psheet); 01116 01117 return true; 01118 } 01119 01120 01121 bool SheetPatcher::mergeStart() { 01122 sheetChange = true; 01123 sheetName = ""; 01124 killNeutral = false; 01125 activeRow.resize(1,0); 01126 setNames(); 01127 if (chain) chain->mergeStart(); 01128 return true; 01129 } 01130 01131 static string clean(const string& s) { 01132 return (s!="NULL")?s:""; 01133 } 01134 01135 bool SheetPatcher::mergeAllDone() { 01136 dbg_printf("SheetPatcher::mergeAllDone\n"); 01137 if (chain) chain->mergeAllDone(); 01138 updateSheet(); 01139 return true; 01140 } 01141 01142 bool SheetPatcher::mergeDone() { 01143 int len = -1; 01144 int prev_len = -2; 01145 do { 01146 prev_len = len; 01147 len = deferred_rows.size(); 01148 if (len>0) { 01149 dbg_printf("Dealing with deferred rows\n"); 01150 std::list<RowUnit> cp = deferred_rows; 01151 deferred_rows.clear(); 01152 for (std::list<RowUnit>::iterator it = cp.begin(); 01153 it != cp.end(); it++) { 01154 RowUnit& unit = *it; 01155 if (unit.sheet_name!=sheetName) { 01156 setSheet(unit.sheet_name.c_str()); 01157 } 01158 changeRow(unit.change); 01159 } 01160 } 01161 } while (len>0 && len!=prev_len); 01162 if (len>0) { 01163 fprintf(stderr,"Cyclic dependency, cannot apply deferred rows\n"); 01164 exit(1); 01165 } 01166 01167 if (chain) chain->mergeDone(); 01168 return true; 01169 } 01170 01171 01172 bool SheetPatcher::setNames(bool forceSheetChange) { 01173 if (forceSheetChange) { 01174 sheetChange = true; 01175 } 01176 PolySheet sheet = getSheet(); 01177 if (!sheet.isValid()) return false; 01178 if (sheetChange) { 01179 dbg_printf("SheetPatcher Working on sheet %ld\n", 01180 (long int)(&sheet.tail_const())); 01181 sheetChange = false; 01182 clearNames(); 01183 activeCol.resize(0,1); 01184 statusCol.resize(0,1); 01185 sniffer = new NameSniffer(sheet,getFlags()); 01186 COOPY_ASSERT(sniffer); 01187 sniffedSheet = &sheet; 01188 activeCol.resize(sheet.width(),1); 01189 statusCol.resize(sheet.width(),1); 01190 for (int i=0; i<sheet.width(); i++) { 01191 string name = sniffer->suggestColumnName(i); 01192 activeCol.cellString(i,0,name); 01193 } 01194 updateCols(); 01195 updatePool(); 01196 } 01197 return true; 01198 } 01199 01200 01201 void SheetPatcher::updatePool() { 01202 PolySheet sheet = getSheet(); 01203 if (!sheet.isValid()) return; 01204 setNames(); 01205 if (descriptive) return; 01206 if (!sniffer) return; 01207 if (sheetName=="") return; 01208 name2pool.clear(); 01209 bool addedAuto = false; 01210 for (int i=0; i<sheet.width(); i++) { 01211 string name = sniffer->suggestColumnName(i); 01212 const CompareFlags& flags = getFlags(); 01213 if (!flags.pool) continue; 01214 //printf("Looking up %s %s\n", sheetName.c_str(), name.c_str()); 01215 PoolColumnLink pc = flags.pool->lookup(sheetName,name); 01216 if (!pc.isValid()) { 01217 ColumnType t = sniffer->suggestColumnType(i); 01218 if (t.autoIncrement&&!addedAuto) { 01219 //printf("Found autoinc'er\n"); 01220 flags.pool->create(sheetName,sheetName,name,true); 01221 pc = flags.pool->lookup(sheetName,name); 01222 addedAuto = true; 01223 } 01224 if (t.foreignKey!="") { 01225 //printf("Found xref\n"); 01226 flags.pool->create(t.foreignTable,sheetName,name,false); 01227 pc = flags.pool->lookup(sheetName,name); 01228 } 01229 } 01230 if (!pc.isValid()) continue; 01231 //printf("Found a pool at %s %s\n", sheetName.c_str(), name.c_str()); 01232 name2pool[name] = pc; 01233 CompareFlags& flags2 = getMutableFlags(); 01234 if (!flags2.foreign_pool_set) { 01235 flags2.foreign_pool = true; 01236 flags2.foreign_pool_set = true; 01237 } 01238 } 01239 } 01240 01241 01242 bool SheetPatcher::updateSheet() { 01243 if (!sheetUpdateNeeded) return false; 01244 01245 if (descriptive) { 01246 PolySheet sheet = getSheet(); 01247 if (!sheet.isValid()) return false; 01248 if (sheet.height()==0) return true; 01249 sheet.insertColumn(ColumnRef(0)); //,ColumnInfo("_ACTION_")); 01250 dbg_printf("Added description column\n"); 01251 //sheet.insertRow(RowRef(0)); 01252 for (int i=0; i<sheet.height()&&i<activeRow.height(); i++) { 01253 dbg_printf(" row %d\n", i); 01254 string txt = activeRow.cellString(0,i); 01255 if (txt==""&&killNeutral) { 01256 txt = "---"; 01257 } 01258 if (txt!="") { 01259 sheet.cellString(0,i,txt); 01260 Poly<Appearance> appear = sheet.getCellAppearance(0,i); 01261 if (appear.isValid()) { 01262 appear->begin(); 01263 appear->setWeightBold(true,AppearanceRange::full()); 01264 appear->setStrikethrough(false,AppearanceRange::full()); 01265 appear->end(); 01266 } 01267 } 01268 } 01269 COOPY_ASSERT(sniffer); 01270 int r = -1; 01271 r = sniffer->getHeaderHeight()-1; 01272 if (r>=0 && r<sheet.height()) { 01273 string key = clean(sheet.cellString(0,r)); 01274 if (key == "" || key == "->") { 01275 for (int i=0; i<=r; i++) { 01276 sheet.cellString(0,i,string("@@")+clean(sheet.cellString(0,i))); 01277 } 01278 } else { 01279 r = -1; 01280 } 01281 } 01282 int yoff = 0; 01283 if (r<0) { 01284 sheet.insertRow(RowRef(0)); 01285 sheet.cellString(0,yoff,"@@"); 01286 r = 0; 01287 } else { 01288 yoff = r; 01289 } 01290 for (int i=1; i<sheet.width(); i++) { 01291 dbg_printf("Header clobber %s\n", sheet.cellString(i,yoff).c_str()); 01292 sheet.cellString(i,yoff,activeCol.cellString(i-1,0)); 01293 } 01294 bool colAction = false; 01295 for (int i=1; i<sheet.width(); i++) { 01296 if (statusCol.cellString(i-1,0)!="") { 01297 colAction = true; 01298 break; 01299 } 01300 } 01301 if (colAction) { 01302 sheet.insertRow(RowRef(0)); 01303 sheet.cellString(0,0,"!"); 01304 for (int i=1; i<sheet.width(); i++) { 01305 sheet.cellString(i,0,statusCol.cellString(i-1,0)); 01306 } 01307 yoff++; 01308 } 01309 Poly<Appearance> appear = sheet.getRowAppearance(r+yoff); 01310 if (appear.isValid()) { 01311 appear->begin(); 01312 appear->setWeightBold(true,AppearanceRange::full()); 01313 appear->end(); 01314 } 01315 01316 int lines = getFlags().context_lines; 01317 if (lines==-2) lines = 2; 01318 if (lines>=0) { 01319 vector<int> show; 01320 show.resize(sheet.height(),0); 01321 for (int i=0; i<sheet.height(); i++) { 01322 bool present = clean(sheet.cellString(0,i))!=""; 01323 if (present) { 01324 for (int j=-lines; j<=lines; j++) { 01325 int k = i+j; 01326 if (k>=0 && k<sheet.height()) { 01327 show[k] = 1; 01328 dbg_printf("* Marking %d...\n", k); 01329 } 01330 } 01331 } 01332 } 01333 int offset = 0; 01334 bool addedBreak = false; 01335 int start_delete = -1; 01336 int count_delete = 0; 01337 for (int i=0; i<(int)show.size(); i++) { 01338 dbg_printf("Checking %d (%d)...\n", i, show[i]); 01339 if (show[i]==0) { 01340 int k = i+offset; 01341 if (addedBreak) { 01342 dbg_printf("Deleting row %d\n", k); 01343 if (start_delete==-1) { 01344 start_delete = k; 01345 count_delete = 0; 01346 } 01347 count_delete++; 01348 //sheet.deleteRow(RowRef(k)); 01349 offset--; 01350 } else { 01351 if (start_delete!=-1) { 01352 dbg_printf("Implementing deletion of %d rows starting at %d\n", 01353 count_delete, 01354 start_delete); 01355 01356 sheet.deleteRows(RowRef(start_delete), 01357 RowRef(start_delete+count_delete-1)); 01358 start_delete = -1; 01359 } 01360 dbg_printf("Adding ... marks to %d\n", k); 01361 sheet.cellString(0,k,"..."); 01362 for (int j=1; j<sheet.width(); j++) { 01363 sheet.cellString(j,k,"..."); 01364 } 01365 addedBreak = true; 01366 } 01367 } else { 01368 addedBreak = false; 01369 } 01370 } 01371 if (start_delete!=-1) { 01372 dbg_printf("Implementing deletion of %d rows starting at %d\n", 01373 count_delete, 01374 start_delete); 01375 sheet.deleteRows(RowRef(start_delete), 01376 RowRef(start_delete+count_delete-1)); 01377 start_delete = -1; 01378 } 01379 } 01380 } 01381 01382 if (forReview) { 01383 PolySheet sheet = getSheet(); 01384 if (!sheet.isValid()) return false; 01385 sheet.insertColumn(ColumnRef(0)); 01386 sheet.insertRow(RowRef(0)); 01387 sheet.cellString(0,0,"Allow? (yes/no)"); 01388 } 01389 01390 sheetUpdateNeeded = false; 01391 01392 return true; 01393 } 01394 01395 /* 01396 coopy::store::PolySheet SheetPatcher::getSheet() { 01397 PolySheet sheet = Patcher::getSheet(); 01398 if (!sheet.isValid()) return sheet; 01399 if (!active_sheet.isValid()) { 01400 active_sheet = sheet; 01401 } 01402 if (&sheet.tail()!=&active_sheet.tail()) { 01403 active_sheet = sheet; 01404 } 01405 if (!getFlags().assume_header) { 01406 active_sheet.forbidSchema(); 01407 } 01408 return active_sheet; 01409 } 01410 */ 01411 01412 01413 01414 01415 bool SheetPatcher::changeName(const NameChange& change) { 01416 if (change.strong) { 01417 PolySheet sheet = getSheet(); 01418 setNames(); 01419 map<string,int> includes; 01420 for (int i=0; i<(int)change.names.size(); i++) { 01421 includes[change.names[i]] = 1; 01422 } 01423 int at = 0; 01424 int mods = 0; 01425 for (int i=0; i<activeCol.width(); i++) { 01426 string name = activeCol.cellString(i,0); 01427 bool included = includes.find(name) != includes.end(); 01428 if (!included) { 01429 ColumnRef col(at); 01430 sheet.deleteColumn(at); 01431 mods++; 01432 } else { 01433 at++; 01434 } 01435 } 01436 if (mods) setNames(true); 01437 } 01438 if (chain) chain->changeName(change); 01439 bool ok = Patcher::changeName(change); 01440 if (!ok) return ok; 01441 return ok; 01442 }