Tekkotsu Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

plistCollections.cc

Go to the documentation of this file.
00001 #include "plistCollections.h"
00002 #include "string_util.h"
00003 #include <libxml/xmlmemory.h>
00004 #include <libxml/parser.h>
00005 #include <iomanip>
00006 
00007 //better to put this here instead of the header
00008 using namespace std; 
00009 
00010 namespace plist {
00011   template class DictionaryOf<ObjectBase>;
00012   template class ArrayOf<ObjectBase>;
00013   template class ArrayOf<Dictionary>;
00014   template class ArrayOf<Primitive<float> >;
00015   template class ArrayOf<Primitive<double> >;
00016 
00017   // This specialization allows use of the generic Array(n) constructor, but everything will be NULL: user must be sure to initialize all of the elements!
00018   template<> ArrayOf<ObjectBase, ObjectBase::conversion_policy<ArrayBase,ObjectBase>::value_conversion>::ArrayOf(storage_t::size_type n)
00019     : ArrayBase(false), ObjectBase::conversion_policy<ArrayBase,ObjectBase>::value_conversion()
00020   {
00021     arr.resize(n);
00022   }
00023 
00024   template<> ObjectBase* loadXML(xmlNode* node) {
00025     ObjectBase * obj=plist::loadXML(node);
00026     if(obj==NULL)
00027       throw XMLLoadSave::bad_format(node,"plist::loadXML encountered an unknown value type");
00028     return obj;
00029   }
00030   
00031   template<> PrimitiveBase* loadXML(xmlNode* node) {
00032     ObjectBase * obj=plist::loadXML(node);
00033     if(obj==NULL)
00034       throw XMLLoadSave::bad_format(node,"plist::loadXML encountered an unknown value type");
00035     PrimitiveBase * cobj = dynamic_cast<PrimitiveBase*>(obj);
00036     if(cobj==NULL) {
00037       delete obj;
00038       throw XMLLoadSave::bad_format(node,"plist::loadXML<PrimitiveBase*> attempted to load non-primitive (e.g. collection?)");
00039     }
00040     return cobj;
00041   }
00042   template<> Collection* loadXML(xmlNode* node) {
00043     ObjectBase * obj=plist::loadXML(node);
00044     if(obj==NULL)
00045       throw XMLLoadSave::bad_format(node,"plist::loadXML encountered an unknown value type");
00046     Collection * cobj = dynamic_cast<Collection*>(obj);
00047     if(cobj==NULL) {
00048       delete obj;
00049       throw XMLLoadSave::bad_format(node,"plist::loadXML<Collection*> attempted to load non-collection (e.g. primitive?)");
00050     }
00051     return cobj;
00052   }
00053   
00054   Collection::~Collection() {
00055     delete collectionListeners;
00056     collectionListeners=NULL;
00057   }
00058   
00059   bool Collection::resolveAssignment(const std::string& arg) {
00060     std::stringstream errstream;
00061     return resolveAssignment(arg,errstream);
00062   }
00063   
00064   bool Collection::resolveAssignment(const std::string& arg, std::ostream& errstream) {
00065     string::size_type eqpos = arg.find("=");
00066     string value=string_util::trim(arg.substr(eqpos+1));
00067     bool isAppend = (arg[eqpos-1]=='+');
00068     string key=string_util::trim(arg.substr(0,isAppend?eqpos-1:eqpos));
00069     plist::ObjectBase* ob=resolveEntry(key);
00070     if(ob==NULL) {
00071       if(isAppend) {
00072         errstream << "Cannot append to unknown array '" << key << '\'' << endl;
00073       } else {
00074         string::size_type dotpos = key.rfind(".");
00075         string parent = key.substr(0,dotpos);
00076         string entry = key.substr(dotpos+1);
00077         ob=resolveEntry(parent);
00078         if(ob==NULL) {
00079           errstream << "'" << key << "' is unknown" << endl;
00080         } else if(plist::Collection* col=dynamic_cast<plist::Collection*>(ob)) {
00081           if((col->getLoadPolicy() & plist::Collection::ADDITIONS) != plist::Collection::ADDITIONS) {
00082             errstream << "Collection '" << parent << "' is not dynamically resizeable, cannot add entry for '" << entry << "'" << endl;
00083           } else if(plist::ArrayBase::StringConversion* arr=dynamic_cast<plist::ArrayBase::StringConversion*>(ob)) {
00084             try {
00085               size_t i=atoi(entry.c_str());
00086               arr->addValue(i,value);
00087               return true;
00088             } catch(const XMLLoadSave::bad_format& bf) {
00089               errstream << "'" << value << "' is a bad value for '" << key << "'" << endl;
00090               errstream << bf.what() << endl;
00091             } catch(const std::exception& e) {
00092               errstream << "An exception occured: " << e.what() << endl;
00093             }
00094           } else if(plist::DictionaryBase::StringConversion* dict=dynamic_cast<plist::DictionaryBase::StringConversion*>(ob)) {
00095             try {
00096               dict->addValue(entry,value);
00097               return true;
00098             } catch(const XMLLoadSave::bad_format& bf) {
00099               errstream << "'" << value << "' is a bad value for '" << key << "'" << endl;
00100               errstream << bf.what() << endl;
00101             } catch(const std::exception& e) {
00102               errstream << "An exception occured: " << e.what() << endl;
00103             }
00104           } else {
00105             errstream << "Unknown collection type or conversion policy for adding new entry '" << entry <<"' to '" << parent << "' from string value '" << value << '\'' << endl;
00106           }
00107         } else {
00108           errstream << "Cannot add subentries to non-collection '" << parent << "'" << endl;
00109         }
00110       }
00111     } else if(isAppend) {
00112       if(dynamic_cast<plist::Collection*>(ob)) {
00113         if(plist::ArrayBase* arr=dynamic_cast<plist::ArrayBase*>(ob)) {
00114           if((arr->getLoadPolicy() & plist::Collection::ADDITIONS) != plist::Collection::ADDITIONS) {
00115             errstream << "Array '" << key << "' is not dynamically resizeable, cannot append entry for '" << value << "'" << endl;
00116           } else if(plist::ArrayBase::StringConversion* strarr=dynamic_cast<plist::ArrayBase::StringConversion*>(ob)) {
00117             try {
00118               strarr->addValue(value);
00119               return true;
00120             } catch(const XMLLoadSave::bad_format& bf) {
00121               errstream << "'" << value << "' is a bad value for '" << key << "'" << endl;
00122               errstream << bf.what() << endl;
00123             } catch(const std::exception& e) {
00124               errstream << "An exception occured: " << e.what() << endl;
00125             }
00126           } else {
00127             errstream << "Internal error: don't know how to convert string value '" << value << "' into new entry for '" << key << "'" << endl;
00128           }
00129         } else { // probably a dictionary
00130           errstream << "Cannot append unnamed new entries to non-array, specify new entry name and use '='" << endl;
00131         }
00132       } else { // probably a primitive
00133         errstream << "Cannot add subentries to non-array '" << key << "'" << endl;
00134       }
00135     } else {
00136       if(plist::PrimitiveBase* pbp=dynamic_cast<plist::PrimitiveBase*>(ob)) {
00137         try {
00138           pbp->set(value);
00139           return true;
00140         } catch(const XMLLoadSave::bad_format& bf) {
00141           errstream << "'" << value << "' is a bad value for '" << key << "'" << endl;
00142           errstream << bf.what() << endl;
00143         } catch(const std::exception& e) {
00144           errstream << "An exception occured: " << e.what() << endl;
00145         }
00146       } else {
00147         errstream << "Cannot assign to a collection" << endl;
00148       }
00149     }
00150     return false;
00151   }
00152   
00153   void Collection::addCollectionListener(CollectionListener* l) const {
00154     if(l!=NULL) {
00155       if(collectionListeners==NULL)
00156         collectionListeners=new std::set<CollectionListener*>;
00157       collectionListeners->insert(l);
00158     }
00159   }
00160     
00161   void Collection::removeCollectionListener(CollectionListener* l) const {
00162     if(collectionListeners==NULL)
00163       return;
00164     std::set<CollectionListener*>::iterator it=collectionListeners->find(l);
00165     if(it!=collectionListeners->end()) {
00166       collectionListeners->erase(it);
00167       if(collectionListeners->empty()) {
00168         delete collectionListeners;
00169         collectionListeners=NULL;
00170       }
00171     }
00172   }
00173   
00174   bool Collection::isCollectionListener(CollectionListener* l) const {
00175     if(l==NULL)
00176       return false;
00177     if(collectionListeners==NULL)
00178       return false;
00179     std::set<CollectionListener*>::iterator it=collectionListeners->find(l);
00180     return it!=collectionListeners->end();
00181   }
00182     
00183   long Collection::toLong() const { throw std::runtime_error("Unable to cast collection to integer value"); }
00184   double Collection::toDouble() const { throw std::runtime_error("Unable to cast collection to floating point value"); }
00185 
00186   void Collection::fireEntryAdded(ObjectBase& val) {
00187     if(collectionListeners==NULL)
00188       return;
00189     // copy primitive listeners so we aren't screwed if a listener is removed during processing, particularly if it's the *last* listener
00190     std::set<CollectionListener*> pls=*collectionListeners;
00191     for(std::set<CollectionListener*>::const_iterator it=pls.begin(); collectionListeners!=NULL && it!=pls.end(); ++it) {
00192       // make sure current listener hasn't been removed
00193       if(collectionListeners->count(*it)>0)
00194         (*it)->plistCollectionEntryAdded(*this,val);
00195     }
00196   }
00197   
00198   void Collection::fireEntryRemoved(ObjectBase& val) {
00199     if(collectionListeners==NULL)
00200       return;
00201     // copy primitive listeners so we aren't screwed if a listener is removed during processing, particularly if it's the *last* listener
00202     std::set<CollectionListener*> pls=*collectionListeners;
00203     for(std::set<CollectionListener*>::const_iterator it=pls.begin(); collectionListeners!=NULL && it!=pls.end(); ++it) {
00204       // make sure current listener hasn't been removed
00205       if(collectionListeners->count(*it)>0)
00206         (*it)->plistCollectionEntryRemoved(*this,val);
00207     }
00208   }
00209   
00210   void Collection::fireEntriesChanged() {
00211     if(collectionListeners==NULL)
00212       return;
00213     // copy primitive listeners so we aren't screwed if a listener is removed during processing, particularly if it's the *last* listener
00214     std::set<CollectionListener*> pls=*collectionListeners;
00215     for(std::set<CollectionListener*>::const_iterator it=pls.begin(); collectionListeners!=NULL && it!=pls.end(); ++it) {
00216       // make sure current listener hasn't been removed
00217       if(collectionListeners->count(*it)>0)
00218         (*it)->plistCollectionEntriesChanged(*this);
00219     }
00220   }
00221   
00222   std::string Collection::getIndentationPrefix(xmlNode* node) {
00223     std::string indentStr;
00224     for(xmlNode* cur=node->parent; cur!=NULL; cur=cur->parent) {
00225       if((void*)cur==(void*)node->doc) { //if we hit the document node, discount it and we're done
00226         if(indentStr.size()>0)
00227           indentStr=indentStr.substr(0,indentStr.size()-perIndent().size());
00228         break;
00229       }
00230       indentStr+=perIndent();
00231     }
00232     return indentStr;
00233   }
00234   
00235   size_t Collection::getIndex(const std::string& name) {
00236     char * endp=0;
00237     long index=strtol(name.c_str(),&endp,0);
00238     if(index<0)
00239       return (size_t)-1;
00240     //throw bad_format(NULL,"Collection::getIndex passed negative index encoded in string: "+name);
00241     if(*endp!='\0')
00242       return (size_t)-1;
00243     //throw bad_format(NULL,"Collection::getIndex was called with a non-numeric value");
00244     return index;
00245   }
00246   
00247   
00248   void AutoCollectionListener::deactivate() {
00249     src.removeCollectionListener(this);
00250     if(const ArrayBase * arr=dynamic_cast<const ArrayBase*>(&src)) {
00251       for(plist::ArrayBase::const_iterator it=arr->begin(); it!=arr->end(); ++it)
00252         if(PrimitiveBase * pb = dynamic_cast<PrimitiveBase*>(*it))
00253           pb->removePrimitiveListener(this);
00254     } else if(const DictionaryBase * dict=dynamic_cast<const DictionaryBase*>(&src)) {
00255       for(plist::DictionaryBase::const_iterator it=dict->begin(); it!=dict->end(); ++it)
00256         if(PrimitiveBase * pb = dynamic_cast<PrimitiveBase*>(it->second))
00257           pb->removePrimitiveListener(this);
00258     } else {
00259       std::cerr << "plist::AutoCollectionListener could not unsubscribe from source entries because source is not a known Collection type" << std::endl;
00260     }
00261   }
00262   void AutoCollectionListener::plistCollectionEntryAdded(plist::Collection& /*col*/, ObjectBase& primitive) {
00263     if(plist::PrimitiveBase * pb = dynamic_cast<plist::PrimitiveBase*>(&primitive)) {
00264       pb->addPrimitiveListener(this);
00265       if(updateOnNew)
00266         plistValueChanged(*pb);
00267     } else
00268       plistSubCollectionAdded(dynamic_cast<plist::Collection&>(primitive));
00269   }
00270   void AutoCollectionListener::plistCollectionEntryRemoved(plist::Collection& /*col*/, ObjectBase& primitive) {
00271     if(plist::PrimitiveBase * pb = dynamic_cast<plist::PrimitiveBase*>(&primitive))
00272       pb->removePrimitiveListener(this); 
00273     else
00274       plistSubCollectionRemoved(dynamic_cast<plist::Collection&>(primitive));
00275   }
00276   void AutoCollectionListener::plistCollectionEntriesChanged(plist::Collection& /*col*/) {
00277     if(const ArrayBase * arr=dynamic_cast<const ArrayBase*>(&src)) {
00278       for(plist::ArrayBase::const_iterator it=arr->begin(); it!=arr->end(); ++it)
00279         if(PrimitiveBase * pb = dynamic_cast<PrimitiveBase*>(*it))
00280           pb->addPrimitiveListener(this);
00281     } else if(const DictionaryBase * dict=dynamic_cast<const DictionaryBase*>(&src)) {
00282       for(plist::DictionaryBase::const_iterator it=dict->begin(); it!=dict->end(); ++it)
00283         if(PrimitiveBase * pb = dynamic_cast<PrimitiveBase*>(it->second))
00284           pb->addPrimitiveListener(this);
00285     } else {
00286       std::cerr << "plist::AutoCollectionListener could not unsubscribe from source entries because source is not a known Collection type" << std::endl;
00287     }
00288   }
00289   
00290   
00291   bool DictionaryBase::removeEntry(const std::string& name) {
00292     storage_t::iterator it=dict.find(name);
00293     if(it==dict.end())
00294       return false;
00295     //still here, then we found exact name match
00296     ObjectBase* obj=it->second;
00297     dict.erase(it);
00298     comments.erase(name);
00299     fireEntryRemoved(*obj);
00300     return true;
00301   }
00302   
00303   bool DictionaryBase::renameEntry(const std::string& oldname, const std::string& newname) {
00304     storage_t::iterator oit=dict.find(oldname);
00305     if(oit==dict.end())
00306       return false;
00307     
00308     // check for previous inhabitant of the new name
00309     storage_t::iterator nit=dict.find(newname);
00310     if(nit!=dict.end()) {
00311       // we found exact name match on the new name -- remove previous entry
00312       ObjectBase* obj=nit->second;
00313       dict.erase(nit);
00314       comments.erase(newname);
00315       fireEntryRemoved(*obj);
00316     }
00317     
00318     ObjectBase* val=oit->second;
00319     dict.erase(oit);
00320     dict[newname]=val;
00321     
00322     // now move comment along too
00323     comments_t::iterator cit=comments.find(oldname);
00324     if(cit==comments.end()) { // no comment for item being moved...
00325       // any comments by a previous resident of the new name?
00326       cit = comments.find(newname);
00327       if(cit!=comments.end()) // if so, remove them
00328         comments.erase(cit);
00329     } else {
00330       // item being moved has a comment, bring it along...
00331       string com = cit->second;
00332       comments.erase(cit);
00333       comments[newname]=com;
00334     }
00335     fireEntriesChanged();
00336     return true;
00337   }
00338   
00339   bool DictionaryBase::swapEntry(const std::string& a, const std::string& b) {
00340     storage_t::iterator ait = dict.find(a);
00341     storage_t::iterator bit = dict.find(b);
00342     if(ait==dict.end() && bit==dict.end())
00343       return false;
00344     else if(ait==dict.end())
00345       return renameEntry(b,a);
00346     else if(bit==dict.end())
00347       return renameEntry(a,b);
00348     
00349     swap(ait->second,bit->second);
00350     
00351     // swap comments too
00352     comments_t::iterator acit = comments.find(a);
00353     comments_t::iterator bcit = comments.find(b);
00354     if(acit != comments.end()) {
00355       if(bcit != comments.end()) {
00356         // have comments for both
00357         swap(acit->second,bcit->second);
00358       } else {
00359         // only have a comment for a
00360         string com = acit->second;
00361         comments.erase(acit);
00362         comments[b]=com;
00363       }
00364     } else if(bcit != comments.end()) {
00365       // only have a comment for b
00366       string com = bcit->second;
00367       comments.erase(bcit);
00368       comments[a]=com;
00369     }
00370     fireEntriesChanged();
00371     return true;
00372   }
00373 
00374   ObjectBase* DictionaryBase::resolveEntry(const std::string& path) const {
00375     //do we have a key with this name?
00376     const_iterator it=dict.find(path);
00377     if(it!=dict.end())
00378       return it->second; //yes, return it
00379     
00380     //perhaps there's a sub-dictionary
00381     string::size_type p;
00382     it=getSubEntry(path,p);
00383     if(it==dict.end()) {
00384       // got noth'n
00385       return NULL;
00386     }
00387     
00388     //found a matching sub-collection, have it find the rest recursively
00389     const Collection* d=dynamic_cast<const Collection*>(it->second);
00390     return d->resolveEntry(path.substr(p+1));
00391   }
00392   
00393   void DictionaryBase::setComment(const std::string& name, const std::string& comment) {
00394     if(comment.size()==0)
00395       comments.erase(name);
00396     else if(comment.find("--")!=std::string::npos)
00397       throw std::runtime_error("per XML spec, comment string ('"+comment+"' cannot contain '--')");
00398     else
00399       comments[name]=comment;
00400   }
00401 
00402   const std::string& DictionaryBase::getComment(const std::string& name) const {
00403     storage_t::const_iterator it=dict.find(name);
00404     if(it==dict.end())
00405       return emptyStr();
00406     //found exact name match
00407     comments_t::const_iterator cit=comments.find(name);
00408     return (cit!=comments.end()) ? cit->second : emptyStr();
00409   }
00410   
00411   void DictionaryBase::loadXML(xmlNode* node) {
00412     //check if our node has been set to NULL (invalid or not found)
00413     if(node==NULL)
00414       return;
00415     if(!xNodeHasName(node,"dict"))
00416       throw bad_format(node,"Dictionary::loadXML expected <dict> value, got "+std::string((const char*)xNodeGetName(node)));
00417     
00418     LoadSavePolicy origLoadPolicy=loadPolicy;
00419     xmlChar * att = xmlGetProp(node,(const xmlChar*)"load");
00420     if(att!=NULL) {
00421       if(xmlStrcasecmp(att, (const xmlChar*)"fixed")==0) {
00422         setLoadPolicy(Collection::FIXED);
00423       } else if(xmlStrcasecmp(att, (const xmlChar*)"union")==0) {
00424         setLoadPolicy(Collection::UNION);
00425       } else if(xmlStrcasecmp(att, (const xmlChar*)"intersect")==0) {
00426         setLoadPolicy(Collection::INTERSECT);
00427       } else if(xmlStrcasecmp(att, (const xmlChar*)"sync")==0) {
00428         setLoadPolicy(Collection::SYNC);
00429       } else {
00430         std::cerr << "WARNING: unknown plist::Dictionary load mode '" << (char*)att << "', ignoring..." << std::endl;
00431         std::string file;
00432         xmlChar* uri = xmlNodeGetBase(node->doc,node);
00433         if(uri!=NULL && uri[0]!='\0')
00434           file = std::string(" of ") + (char*)uri + std::string(":");
00435         else
00436           file = " at line ";
00437         xmlFree(uri);
00438         std::cerr << "  (from" << file << xmlGetLineNo(node) << ")" << std::endl;
00439       }
00440     }
00441     xmlFree(att); att=NULL;
00442     
00443     try {
00444       std::string comment;
00445       std::set<std::string> seen;
00446       //process children nodes
00447       for(xmlNode* cur = skipToElement(node->children,comment); cur!=NULL; cur = skipToElement(cur->next,comment)) {
00448               
00449         //find the next key node
00450         xmlNode * k=cur;
00451         if(xmlStrcmp(k->name, (const xmlChar *)"key"))
00452           throw bad_format(k,"Dictionary format error: expect data in pairs of key and value (two values found in a row)");
00453         cur=skipToElement(cur->next);
00454         
00455         //find the following value (non-key) node
00456         xmlNode * v=cur;
00457         if(v==NULL)
00458           throw bad_format(cur,"Dictionary format error: expect data in pairs of key and value (dictionary ended with hanging key)");
00459         if(!xmlStrcmp(v->name, (const xmlChar *)"key"))
00460           throw bad_format(v,"Dictionary format error: expect data in pairs of key and value (two keys found in a row)");
00461         
00462         //find corresponding entry
00463         xmlChar* cont=xmlNodeGetContent(k);
00464         string key=(const char*)cont;
00465         xmlFree(cont);
00466         seen.insert(key);
00467         loadXMLNode(key,v,comment);
00468       }
00469       if((loadPolicy&REMOVALS) && seen.size()!=size()) {
00470         std::set<std::string> rem;
00471         for(const_iterator it=begin(); it!=end(); ++it) {
00472           if(seen.find(it->first)==seen.end())
00473             rem.insert(it->first);
00474         }
00475         for(std::set<std::string>::const_iterator it=rem.begin(); it!=rem.end(); ++it)
00476           removeEntry(*it);
00477       }
00478     } catch(...) {
00479       setLoadPolicy(origLoadPolicy);
00480       throw;
00481     }
00482     setLoadPolicy(origLoadPolicy);
00483   }
00484   
00485   void DictionaryBase::saveXML(xmlNode* node, bool onlyOverwrite, std::set<std::string>& seen) const {
00486     //check if our node has been set to NULL (invalid or not found)
00487     if(node==NULL)
00488       return;
00489     
00490     //set the type of the current node
00491     xmlNodeSetName(node,(const xmlChar*)"dict");
00492     
00493     //find the depth of the target node in the xml tree to maintain proper indentation
00494     std::string indentStr=getIndentationPrefix(node);
00495     
00496     //This will hold any comments found between elements -- if no comment is found, a new one may be added
00497     std::string comment;
00498 
00499     //process children nodes
00500     xmlNode* prev=node->children;
00501     for(xmlNode* cur = skipToElement(node->children,comment); cur!=NULL; cur = skipToElement(cur,comment)) {
00502       
00503       //find the next key node
00504       xmlNode * k=cur;
00505       if(xmlStrcmp(k->name, (const xmlChar *)"key")) {
00506         cur = k->next;
00507         xmlUnlinkNode(k);
00508         xmlFreeNode(k);
00509         continue;
00510       }
00511       cur=skipToElement(cur->next);
00512       
00513       //find the following value (non-key) node
00514       xmlNode * v=cur;
00515       if(v==NULL) {
00516         xmlUnlinkNode(k);
00517         xmlFreeNode(k);
00518         break;
00519       }
00520       if(!xmlStrcmp(v->name, (const xmlChar *)"key"))
00521         throw bad_format(v,"Dictionary format error: expect data in pairs of key and value (two keys found in a row)");
00522       
00523       xmlChar* cont=xmlNodeGetContent(k);
00524       std::string key=(const char*)cont;
00525       xmlFree(cont);
00526       if(!saveOverXMLNode(k,v,key,comment,indentStr,seen)) {
00527         cur=xNodeGetNextNode(cur);
00528         if(savePolicy&REMOVALS) {
00529           while(prev!=cur) {
00530             xmlNode* n=prev;
00531             prev=xNodeGetNextNode(prev);
00532             xmlUnlinkNode(n);
00533             xmlFreeNode(n);
00534           }
00535         } else {
00536           if(warnUnused && savePolicy==FIXED)
00537             cerr << "Warning: saving over existing plist dictionary, key '" << key << "' does not match a registered variable.  Ignoring..." << endl;
00538         }
00539         prev=cur;
00540       }
00541       prev=cur=xNodeGetNextNode(cur);
00542     }
00543 
00544     if(!onlyOverwrite && seen.size()!=dict.size()) {
00545       // clear text nodes from end of dictionary back to last entry
00546       for(xmlNode* cur=node->last; cur!=NULL && cur->type==XML_TEXT_NODE; cur=node->last) {
00547         xmlUnlinkNode(cur);
00548         xmlFreeNode(cur);
00549       }
00550       size_t longestKeyLen = getLongestKeyLen(NULL,1);
00551       // the main dictionary has entries that weren't seen... find which ones
00552       // if needed, this could be made faster (O(n) vs. current O(n lg n)) by assuming the maps
00553       // are sorted and moving two iterators through together instead of repeated find()'s
00554       for(storage_t::const_iterator it=dict.begin(); it!=dict.end(); ++it) {
00555         if(seen.find(it->first)==seen.end()) {
00556           //we didn't see this node in the existing xml tree, have to add a new node pair for it
00557           saveXMLNode(node,it->first,it->second,indentStr,longestKeyLen);
00558         }
00559       }
00560       std::string parentIndent;
00561       if(indentStr.size()>=perIndent().size())
00562         parentIndent=indentStr.substr(perIndent().size());
00563       xmlAddChild(node,xmlNewText((const xmlChar*)("\n"+parentIndent).c_str()));
00564     }
00565   }
00566 
00567   std::string DictionaryBase::toString() const {
00568     stringstream s;
00569     s << *this;
00570     return s.str();
00571   }
00572   
00573   unsigned int DictionaryBase::getLongestKeyLen(const regex_t* reg/*=NULL*/, unsigned int depth/*=-1*/) const {
00574     if(depth==0)
00575       return 0;
00576     size_t longest=0;
00577     size_t seplen=subCollectionSep().size();
00578     for(DictionaryBase::const_iterator it=begin(); it!=end(); ++it) {
00579       if(reg!=NULL && regexec(reg,it->first.c_str(),0,NULL,0)!=0)
00580         continue;
00581       size_t cur=it->first.size();
00582       if(Collection* dp=dynamic_cast<Collection*>(it->second))
00583         cur+=dp->getLongestKeyLen(reg,depth-1)+seplen;
00584       longest=std::max(longest,cur);
00585     }
00586     return longest;
00587   }
00588   
00589   DictionaryBase::iterator DictionaryBase::getSubEntry(const std::string& name, std::string::size_type& seppos) {
00590     seppos=name.find(subCollectionSep());
00591     if(seppos==string::npos)
00592       return dict.end(); //no '.'s found -- go away
00593     iterator it=dict.find(name.substr(0,seppos));
00594     if(it==dict.end())
00595       return dict.end(); //no entry matching prefix -- go away
00596     const Collection* d=dynamic_cast<const Collection*>(it->second);
00597     if(d==NULL)
00598       return dict.end(); //matching prefix is not a collection -- go away
00599     return it;
00600   }
00601   DictionaryBase::const_iterator DictionaryBase::getSubEntry(const std::string& name, std::string::size_type& seppos) const {
00602     seppos=name.find(subCollectionSep());
00603     if(seppos==string::npos)
00604       return dict.end(); //no '.'s found -- go away
00605     const_iterator it=dict.find(name.substr(0,seppos));
00606     if(it==dict.end())
00607       return dict.end(); //no entry matching prefix -- go away
00608     const Collection* d=dynamic_cast<const Collection*>(it->second);
00609     if(d==NULL)
00610       return dict.end(); //matching prefix is not a collection -- go away
00611     return it;
00612   }
00613     
00614   void DictionaryBase::clear() {
00615     storage_t::size_type s=dict.size();
00616     // this bit of trickiness is to handle element destructors doing things to the list while it's being cleared
00617     std::set<ObjectBase*> refs=myRef;
00618     dict.clear();
00619     myRef.clear();
00620     comments.clear();
00621     if(s>0) //only fire if we had entries to begin with
00622       fireEntriesChanged();
00623     for(std::set<ObjectBase*>::iterator it=refs.begin(); it!=refs.end(); ++it)
00624       delete *it;
00625   }
00626   
00627   void DictionaryBase::takeObject(const std::string& /*name*/, ObjectBase* obj) {
00628     myRef.insert(obj);
00629   }
00630 
00631   void DictionaryBase::fireEntryRemoved(ObjectBase& val) {
00632     Collection::fireEntryRemoved(val);
00633     std::set<ObjectBase*>::iterator it=myRef.find(&val);
00634     if(it!=myRef.end()) {
00635       myRef.erase(it);
00636       delete &val;
00637     }
00638   }
00639   
00640   void DictionaryBase::cloneMyRef() {
00641     for(iterator dit=dict.begin(); dit!=dict.end(); ++dit) {
00642       std::set<ObjectBase*>::iterator rit=myRef.find(dit->second);
00643       if(rit!=myRef.end()) {
00644         myRef.erase(rit);
00645         myRef.insert(dit->second=dynamic_cast<ObjectBase*>((dit->second)->clone()));
00646       }
00647     }
00648     
00649     //slower implementation, but can handle multiple pointers to the same instance (which we don't support elsewhere, so no point in doing it)
00650     /*
00651      std::set<ObjectBase*> ns;
00652     for(std::set<ObjectBase*>::iterator it=myRef.begin(); it!=myRef.end(); ++it) {
00653       ObjectBase* n=dynamic_cast<ObjectBase*>((*it)->clone());
00654       bool used=false;
00655       for(iterator dit=dict.begin(); dit!=dict.end(); ++dit) {
00656         if(*it==dit->second) {
00657           dit->second=n;
00658           used=true;
00659         }
00660       }
00661       if(!used) {
00662         cerr << "Warning: dictionary claims control over pointer not found in dictionary" << endl;
00663         delete n;
00664       } else
00665         ns.insert(n);
00666     }
00667     myRef=ns;
00668     */
00669   }
00670   
00671   bool DictionaryBase::saveOverXMLNode(xmlNode* k, xmlNode* val, const std::string& key, std::string comment, const std::string& indentStr, std::set<std::string>& seen) const {
00672     //find corresponding entry
00673     storage_t::const_iterator it=findEntry(key);
00674     if(it==dict.end())
00675       return false;
00676     if(comment.size()==0) {
00677       bool isSub=dynamic_cast<const Collection*>(it->second);
00678       if(isSub)
00679         if(const ArrayBase* arr=dynamic_cast<const ArrayBase*>(it->second))
00680           isSub = !arr->getSaveInlineStyle();
00681       bool isFirst=true;
00682       const std::string indentedNewline="\n"+indentStr;
00683       comments_t::const_iterator cit=comments.find(key);
00684       if(cit!=comments.end()) {
00685         while(k->prev!=NULL && xNodeIsText(k->prev)) {
00686           xmlNode* n=k->prev;
00687           xmlUnlinkNode(n);
00688           xmlFreeNode(n);
00689         }
00690       }
00691       if(!saveCondensed) {
00692         const std::string headline=("======== "+it->first+" ========");
00693         if(isSub && static_cast<const Collection*>(it->second)->size()>0) {
00694           isFirst=(skipToElement(k->parent->children)==k);
00695           xmlAddPrevSibling(k,xmlNewText((const xmlChar*)(isFirst ? indentedNewline : indentedNewline+indentedNewline).c_str()));
00696           xmlAddPrevSibling(k,xmlNewComment((const xmlChar*)headline.c_str()));
00697         }
00698       }
00699       if(cit!=comments.end()) {
00700         if(isSub || cit->second.find(key)<KEY_IN_COMMENT_MAX_POS)
00701           comment=cit->second;
00702         else //if not a sub-dictionary, and comment doesn't already start with entry name, prepend entry name
00703           comment=key+": "+cit->second;
00704         string::size_type pos=comment.rfind('\n');
00705         while(pos!=string::npos) {
00706           if(comment.compare(pos+1,indentStr.size(),indentStr)!=0)
00707             comment.insert(pos+1,indentStr);
00708           if(pos==0)
00709             break;
00710           pos = comment.rfind('\n',pos-1);
00711         }
00712         if(!isSub)
00713           isFirst=(skipToElement(k->parent->children)==k);
00714         xmlAddPrevSibling(k,xmlNewText((const xmlChar*)(isFirst ? indentedNewline : indentedNewline+indentedNewline).c_str()));
00715         xmlAddPrevSibling(k,xmlNewComment((const xmlChar*)comment.c_str()));
00716         xmlAddPrevSibling(k,xmlNewText((const xmlChar*)indentedNewline.c_str()));
00717       }
00718     }
00719     it->second->saveXML(val);
00720     if(seen.find(key)!=seen.end()) {
00721       std::cerr << "WARNING: plist::Dictionary found duplicate key " << key << " during save" << std::endl;
00722     } else {
00723       seen.insert(key);
00724     }
00725     return true;
00726   }
00727   
00728   void DictionaryBase::saveXMLNode(xmlNode* node, const std::string& key, const ObjectBase* val, const std::string& indentStr, size_t longestKeyLen) const {
00729     bool isSub=dynamic_cast<const Collection*>(val);
00730     if(isSub)
00731       if(const ArrayBase* arr=dynamic_cast<const ArrayBase*>(val))
00732         isSub = !arr->getSaveInlineStyle();
00733     bool isFirst=(node->children==NULL);
00734     const std::string indentedNewline="\n"+indentStr;
00735     if(!saveCondensed) {
00736       const std::string headline=("======== "+key+" ========");
00737       if(isSub && static_cast<const Collection*>(val)->size()>0) {
00738         xmlAddChild(node,xmlNewText((const xmlChar*)(isFirst ? indentedNewline : indentedNewline+indentedNewline).c_str()));
00739         xmlAddChild(node,xmlNewComment((const xmlChar*)headline.c_str()));
00740       }
00741     }
00742     std::string comment;
00743     comments_t::const_iterator cit=comments.find(key);
00744     if(cit!=comments.end()) {
00745       if(isSub || cit->second.find(key)<KEY_IN_COMMENT_MAX_POS)
00746         comment=cit->second;
00747       else
00748         comment=key+": "+cit->second;
00749       string::size_type pos=comment.rfind('\n');
00750       while(pos!=string::npos) {
00751         if(comment.compare(pos+1,indentStr.size(),indentStr)!=0)
00752           comment.insert(pos+1,indentStr);
00753         if(pos==0)
00754           break;
00755         pos = comment.rfind('\n',pos-1);
00756       }
00757       xmlAddChild(node,xmlNewText((const xmlChar*)(isSub || isFirst ? indentedNewline : indentedNewline+indentedNewline).c_str()));
00758       xmlAddChild(node,xmlNewComment((const xmlChar*)comment.c_str()));
00759     }
00760     xmlAddChild(node,xmlNewText((const xmlChar*)indentedNewline.c_str()));
00761     xmlNode* k=xmlNewChild(node,NULL,(const xmlChar*)"key",(const xmlChar*)key.c_str());
00762     if(k==NULL)
00763       throw bad_format(node,"Error: plist Dictionary xml error on saving key");
00764     string space(longestKeyLen-string_util::utf8len(key)+1,' ');
00765     xmlAddChild(node,xmlNewText((const xmlChar*)space.c_str()));
00766     xmlNode* v=xmlNewChild(node,NULL,(const xmlChar*)"",NULL);
00767     if(v==NULL)
00768       throw bad_format(node,"Error: plist Dictionary xml error on saving value");
00769     val->saveXML(v);
00770   }
00771   
00772   
00773   bool ArrayBase::removeEntry(size_t index) {
00774     if(index>=arr.size())
00775        return false;
00776     storage_t::iterator it=arr.begin();
00777     advance(it,index);
00778     ObjectBase* obj=*it;
00779     arr.erase(it);
00780     comments.erase(index);
00781     fireEntryRemoved(*obj);
00782     return true;
00783   }
00784 
00785   ObjectBase* ArrayBase::resolveEntry(const std::string& path) const {
00786     size_t index=getIndex(path);
00787     if(index<size())
00788       return &getEntry(index);
00789     std::string::size_type p;
00790     const_iterator it=getSubEntry(path,p);
00791     if(it==arr.end())
00792       return NULL;
00793     const Collection * d=dynamic_cast<const Collection*>(*it);
00794     return d->resolveEntry(path.substr(p+1));
00795   }
00796     
00797   void ArrayBase::clear() {
00798     storage_t::size_type s=arr.size();
00799     // this bit of trickiness is to handle element destructors doing things to the list while it's being cleared
00800     std::set<ObjectBase*> refs=myRef;
00801     arr.clear();
00802     comments.clear();
00803     myRef.clear();
00804     if(s>0) //only fire if we had entries to begin with
00805       fireEntriesChanged();
00806     for(std::set<ObjectBase*>::iterator it=refs.begin(); it!=refs.end(); ++it)
00807       delete *it;
00808   }
00809   
00810   void ArrayBase::setComment(size_t index, const std::string& comment) {
00811     if(comment.size()==0)
00812       comments.erase(index);
00813     else if(comment.find("--")!=std::string::npos)
00814       throw std::runtime_error("per XML spec, comment string ('"+comment+"' cannot contain '--')");
00815     else
00816       comments[index]=comment;
00817   }
00818   
00819   const std::string& ArrayBase::getComment(size_t index) const {
00820     comments_t::const_iterator it=comments.find(index);
00821     if(it==comments.end())
00822       return emptyStr();
00823     else
00824       return it->second;
00825   }
00826   
00827   void ArrayBase::loadXML(xmlNode* node) {
00828     //check if our node has been set to NULL (invalid or not found)
00829     if(node==NULL)
00830       return;
00831     if(!xNodeHasName(node,"array"))
00832       throw bad_format(node,"Array::loadXML expected <array> value, got "+std::string((const char*)xNodeGetName(node)));
00833     
00834     std::string comment;
00835     unsigned int i=0;
00836     for(xmlNode* cur = skipToElement(xNodeGetChildren(node),comment); cur!=NULL; cur = skipToElement(xNodeGetNextNode(cur),comment)) {
00837       if(!loadXMLNode(i++, cur, comment))
00838          break;
00839     }
00840     if(loadPolicy&REMOVALS) {
00841       while(i<size())
00842         removeEntry(size()-1);
00843     } 
00844   }
00845   
00846   void ArrayBase::saveXML(xmlNode* node) const {
00847     //check if our node has been set to NULL (invalid or not found)
00848     if(node==NULL)
00849       return;
00850     
00851     //set the type of the current node
00852     xmlNodeSetName(node,(const xmlChar*)"array");
00853     
00854     //find the depth of the target node in the xml tree to maintain proper indentation
00855     std::string indentStr=getIndentationPrefix(node);
00856     std::string parentIndent;
00857     if(indentStr.size()>=perIndent().size())
00858       parentIndent=indentStr.substr(perIndent().size());
00859     
00860     //This will hold any comments found between elements -- if no comment is found, a new one may be added
00861     std::string comment;
00862     
00863     //This will be the index of the item we're loading next
00864     unsigned int i=0;
00865     
00866     //process children nodes
00867     xmlNode * prev=xNodeGetChildren(node);
00868     for(xmlNode* cur = skipToElement(prev,comment); cur!=NULL; cur = skipToElement(cur,comment)) {
00869       
00870       if(i==arr.size()) {
00871         if(savePolicy&REMOVALS) {
00872           while(prev!=NULL) {
00873             xmlNode* n=prev;
00874             prev=xNodeGetNextNode(prev);
00875             xmlUnlinkNode(n);
00876             xmlFreeNode(n);
00877           }
00878         } else {
00879           if(warnUnused && savePolicy==FIXED)
00880             std::cerr << "Warning: plist::Array ignoring extraneous items in destination during save..." << std::endl;
00881         }
00882         break;
00883       }
00884       if(comment.size()==0) {
00885         comments_t::const_iterator cit=comments.find(i);
00886         if(cit!=comments.end()) {
00887           std::stringstream buf;
00888           buf << i;
00889           if(/*isSub ||*/ cit->second.compare(0,buf.str().size(),buf.str())==0)
00890             comment=cit->second;
00891           else { //if not a sub-dictionary, and comment doesn't already start with entry name, prepend entry name
00892             comment=buf.str();
00893             comment+=": "+cit->second;
00894           }
00895           xmlAddPrevSibling(cur,xmlNewText((const xmlChar*)("\n"+indentStr).c_str()));
00896           std::string::size_type pos=comment.rfind('\n');
00897           while(pos!=std::string::npos) {
00898             if(comment.compare(pos+1,indentStr.size(),indentStr)!=0)
00899               comment.insert(pos+1,indentStr);
00900             if(pos==0)
00901               break;
00902             pos = comment.rfind('\n',pos-1);
00903           }
00904           xmlAddPrevSibling(cur,xmlNewComment((const xmlChar*)comment.c_str()));
00905           xmlAddPrevSibling(cur,xmlNewText((const xmlChar*)("\n"+indentStr).c_str()));
00906         }
00907       }
00908       arr[i++]->saveXML(cur);
00909       prev=cur=xNodeGetNextNode(cur);
00910     }
00911     
00912     if(!(savePolicy&ADDITIONS))
00913       return;
00914     
00915     bool hadUnsaved = (i<arr.size());
00916     for(; i<arr.size(); ++i) {
00917       comments_t::const_iterator cit=comments.find(i);
00918       if(cit!=comments.end()) {
00919         std::stringstream buf;
00920         buf << i;
00921         if(/*isSub ||*/ cit->second.compare(0,buf.str().size(),buf.str())==0)
00922           comment=cit->second;
00923         else { //if not a sub-dictionary, and comment doesn't already start with entry name, prepend entry name
00924           comment=buf.str();
00925           comment+=": "+cit->second;
00926         }
00927         xmlAddChild(node,xmlNewText((const xmlChar*)("\n"+indentStr).c_str()));
00928         std::string::size_type pos=comment.rfind('\n');
00929         while(pos!=std::string::npos) {
00930           if(comment.compare(pos+1,indentStr.size(),indentStr)!=0)
00931             comment.insert(pos+1,indentStr);
00932           if(pos==0)
00933             break;
00934           pos = comment.rfind('\n',pos-1);
00935         }
00936         xmlAddChild(node,xmlNewComment((const xmlChar*)comment.c_str()));
00937       }
00938       if(saveInlineStyle)
00939         xmlAddChild(node,xmlNewText((const xmlChar*)" "));
00940       else
00941         xmlAddChild(node,xmlNewText((const xmlChar*)("\n"+indentStr).c_str()));
00942       xmlNode* v=xmlNewChild(node,NULL,(const xmlChar*)"",NULL);
00943       if(v==NULL)
00944         throw bad_format(node,"Error: plist Array xml error on saving value");
00945       arr[i]->saveXML(v);
00946     }
00947     if(hadUnsaved) {
00948       if(saveInlineStyle)
00949         xmlAddChild(node,xmlNewText((const xmlChar*)" "));
00950       else
00951         xmlAddChild(node,xmlNewText((const xmlChar*)("\n"+parentIndent).c_str()));
00952     }
00953   }
00954   
00955   std::string ArrayBase::toString() const {
00956     std::stringstream s;
00957     s << *this;
00958     return s.str();
00959   }
00960   
00961   unsigned int ArrayBase::getLongestKeyLen(const regex_t* reg/*=NULL*/, unsigned int depth/*=-1*/) const {
00962     if(depth==0)
00963       return 0;
00964     size_t longest=0;
00965     size_t seplen=subCollectionSep().size();
00966     for(size_t i=0; i<size(); ++i) {
00967       std::stringstream s;
00968       s << i;
00969       if(reg!=NULL && regexec(reg,s.str().c_str(),0,NULL,0)!=0)
00970         continue;
00971       size_t cur=s.str().size();
00972       if(Collection* dp=dynamic_cast<Collection*>(arr[i]))
00973         cur+=dp->getLongestKeyLen(reg,depth-1)+seplen;
00974       longest=std::max(longest,cur);
00975     }
00976     return longest;
00977   }
00978   
00979   void ArrayBase::takeObject(size_t /*index*/, ObjectBase* obj) {
00980     myRef.insert(obj);
00981   }
00982   
00983   void ArrayBase::fireEntryRemoved(ObjectBase& val) {
00984     Collection::fireEntryRemoved(val);
00985     std::set<ObjectBase*>::iterator it=myRef.find(&val);
00986     if(it!=myRef.end()) {
00987       myRef.erase(it);
00988       delete &val;
00989     }
00990   }
00991   
00992   ArrayBase::iterator ArrayBase::getSubEntry(const std::string& name, std::string::size_type& seppos) {
00993     seppos=name.find(subCollectionSep());
00994     if(seppos==std::string::npos)
00995       return arr.end(); //no '.'s found -- go away
00996     size_t index=getIndex(name.substr(0,seppos));
00997     if(index>=size())
00998       return arr.end(); //no entry matching prefix -- go away
00999     iterator it=arr.begin();
01000     advance(it,index);
01001     const Collection* d=dynamic_cast<const Collection*>(*it);
01002     if(d==NULL)
01003       return arr.end(); //matching prefix is not a collection -- go away
01004     return it;
01005   }
01006   ArrayBase::const_iterator ArrayBase::getSubEntry(const std::string& name, std::string::size_type& seppos) const {
01007     seppos=name.find(subCollectionSep());
01008     if(seppos==std::string::npos)
01009       return arr.end(); //no '.'s found -- go away
01010     size_t index=getIndex(name.substr(0,seppos));
01011     if(index>=size())
01012       return arr.end(); //no entry matching prefix -- go away
01013     const_iterator it=arr.begin();
01014     advance(it,index);
01015     const Collection* d=dynamic_cast<const Collection*>(*it);
01016     if(d==NULL)
01017       return arr.end(); //matching prefix is not a collection -- go away
01018     return it;
01019   }
01020   
01021   void ArrayBase::cloneMyRef() {
01022     for(iterator dit=arr.begin(); dit!=arr.end(); ++dit) {
01023       std::set<ObjectBase*>::iterator rit=myRef.find(*dit);
01024       if(rit!=myRef.end()) {
01025         myRef.erase(rit);
01026         myRef.insert(*dit=dynamic_cast<ObjectBase*>((*dit)->clone()));
01027       }
01028     }
01029   }
01030   
01031   std::ostream& filteredDisplay(std::ostream& os, const ObjectBase& c, const std::string& sel, int selType, unsigned int depth) {
01032     if(sel.size()==0)
01033       return filteredDisplay(os,c,NULL,depth);
01034     regex_t r;
01035     if(regcomp(&r,sel.c_str(),selType|REG_NOSUB)==0)
01036       filteredDisplay(os,c,&r,depth);
01037     regfree(&r);
01038     return os;
01039   }
01040     
01041   std::ostream& filteredDisplay(std::ostream& os, const ObjectBase& c, const regex_t* reg, unsigned int depth) {
01042     unsigned int seplen=Collection::subCollectionSep().size();
01043     unsigned int out=0;
01044     
01045     if(const ArrayBase* a=dynamic_cast<const ArrayBase*>(&c)) {
01046       unsigned int longest=std::max(a->getLongestKeyLen(reg,depth),static_cast<unsigned int>(os.width()));
01047       for(unsigned long i=0; i<a->size(); ++i) {
01048         stringstream ns;
01049         ns << i;
01050         if(reg!=NULL && regexec(reg,ns.str().c_str(),0,NULL,0)!=0)
01051           continue;
01052         out++;
01053         if(depth==0)
01054           return os << right << setw(longest) << "" << " = [...]" << endl;
01055         if(Collection* dp=dynamic_cast<Collection*>(&(*a)[i])) {
01056           stringstream ss;
01057           ss << left << std::setw(longest-snprintf(NULL,0,"%lu",i)-seplen);
01058           filteredDisplay(ss,*dp,reg,depth-1);
01059           std::string line;
01060           for(getline(ss,line); ss; std::getline(ss,line))
01061             os << (ns.str() + Collection::subCollectionSep() + line) << std::endl;
01062         } else {
01063           os << std::left << std::setw(longest) << ns.str() << " = " << (*a)[i] << std::endl;
01064         }
01065       }
01066       if(out==0)
01067         return os << right << setw(longest) << "" << " = (empty array)" << endl;
01068       
01069     } else if(const DictionaryBase* d=dynamic_cast<const DictionaryBase*>(&c)) {
01070       unsigned int longest=std::max(d->getLongestKeyLen(reg,depth),static_cast<unsigned int>(os.width()));
01071       for(DictionaryBase::storage_t::const_iterator it=d->begin(); it!=d->end(); ++it) {
01072         if(reg!=NULL && regexec(reg,it->first.c_str(),0,NULL,0)!=0)
01073           continue;
01074         out++;
01075         if(depth==0)
01076           return os << right << setw(longest) << "" << " = [...]" << endl;
01077         if(Collection* dp=dynamic_cast<Collection*>(it->second)) {
01078           stringstream ss;
01079           ss << left << setw(longest-it->first.size()-seplen);
01080           filteredDisplay(ss,*dp,reg,depth-1);
01081           string line;
01082           for(getline(ss,line); ss; getline(ss,line))
01083             os << (it->first + Collection::subCollectionSep() + line) << endl;
01084         } else {
01085           os << left << setw(longest) << it->first << " = " << *it->second << endl;
01086         }
01087       }
01088       if(out==0)
01089         return os << right << setw(longest) << "" << " = (empty dictionary)" << endl;
01090       
01091     } else {
01092       os << c.toString();
01093     }
01094     return os;
01095   }
01096   
01097 } //namespace plist
01098 
01099 
01100 /*! @file
01101  * @brief 
01102  * @author Ethan Tira-Thompson (ejt) (Creator)
01103  */

Tekkotsu v5.1CVS
Generated Mon May 9 04:58:47 2016 by Doxygen 1.6.3