1 /* 2 Copyright (c) 2006 - 2021 3 CLST - Radboud University 4 ILK - Tilburg University 5 6 This file is part of ticcutils 7 8 ticcutils is free software; you can redistribute it and/or modify 9 it under the terms of the GNU General Public License as published by 10 the Free Software Foundation; either version 3 of the License, or 11 (at your option) any later version. 12 13 ticcutils is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program; if not, see <http://www.gnu.org/licenses/>. 20 21 For questions and suggestions, see: 22 https://github.com/LanguageMachines/ticcutils/issues 23 or send mail to: 24 lamasoftware (at ) science.ru.nl 25 26 */ 27 28 #include "ticcutils/Configuration.h" 29 30 #include <string> 31 #include <map> 32 #include <set> 33 #include <fstream> 34 #include <stdexcept> 35 #include <iostream> 36 #include "ticcutils/StringOps.h" 37 38 using namespace std; 39 40 namespace TiCC { 41 Configuration()42 Configuration::Configuration(){ 43 /// set up a Configuration structure 44 myMap["global"] = ssMap(); 45 } 46 fixControl(const string & s,char c)47 string fixControl( const string& s, char c ){ 48 /// replace special characters 49 /*! 50 \param s the inputstring 51 \param c special 'symbol' to replace 52 \return a new string where all occurences of "\\c" aee replaced by "\c" 53 */ 54 string sString; 55 string rString; 56 switch ( c ){ 57 case 't': 58 sString = "\\t"; 59 rString = "\t"; 60 break; 61 case 'r': 62 sString = "\\r"; 63 rString = "\r"; 64 break; 65 case 'n': 66 sString = "\\n"; 67 rString = "\n"; 68 break; 69 default: 70 throw logic_error("invalid char for fixControl" ); 71 } 72 string::size_type pos1 = s.find( sString ); 73 if ( pos1 == string::npos ){ 74 return s; 75 } 76 else { 77 string result = s.substr( 0, pos1 ); 78 result += rString; 79 string::size_type pos2 = s.find( sString, pos1+1 ); 80 while ( pos2 != string::npos ){ 81 result += s.substr( pos1+2, pos2-pos1-2 ); 82 result += rString; 83 pos1 = pos2; 84 pos2 = s.find( sString, pos1+1 ); 85 } 86 result += s.substr( pos1+2 ); 87 return result; 88 } 89 } 90 fixControls(const string & s)91 string fixControls( const string& s ){ 92 /// replace all "\\n", "\\r", "\\t" sequences by "\n", "\r" and "\t" 93 string result = s; 94 result = fixControl( result, 'n' ); 95 result = fixControl( result, 'r' ); 96 result = fixControl( result, 't' ); 97 return result; 98 } 99 get_att_val(const string & line,const string & section)100 bool Configuration::get_att_val( const string& line, const string& section ){ 101 /// parse a string into an attribute/value pair and insert in a section 102 /*! 103 \param line the line to parse 104 \param section the section to insert in. 105 \return true if we have a result 106 */ 107 string::size_type pos = line.find("="); 108 if ( pos != string::npos ){ 109 string att = line.substr(0,pos); 110 att = TiCC::trim(att); 111 string val = line.substr(pos+1); 112 val = TiCC::trim(val); 113 if ( val[0] == '"' && val[val.length()-1] == '"' ){ 114 val = val.substr(1, val.length()-2); 115 } 116 val = fixControls( val ); 117 myMap[section][att] = val; 118 return true; 119 } 120 return false; 121 } 122 fill(const string & fileName)123 bool Configuration::fill( const string& fileName ){ 124 /// fill a Configuration structure from a file 125 /*! 126 \param fileName the name of the input file 127 \return true on succes 128 */ 129 ifstream is( fileName ); 130 if ( !is ){ 131 cerr << "unable to read configuration from " << fileName << endl; 132 return false; 133 } 134 string cdir = dirname( fileName ); 135 if ( cdir == "." ){ 136 cdir = ""; 137 } 138 else { 139 cdir += "/"; 140 } 141 // cerr << "dirname= " << cdir << endl; 142 myMap["global"]["configDir"] = cdir; // can be overidden below 143 string inLine; 144 string section = "global"; 145 while ( getline( is, inLine ) ){ 146 string line = TiCC::trim(inLine); 147 if ( line.empty() || line[0] == '#' ){ 148 continue; 149 } 150 if ( match_front( line, "[[" ) ){ 151 if ( line[line.length()-1] == ']' && 152 line[line.length()-2] == ']' ){ 153 section = line.substr(2,line.length()-4); 154 // cerr << "GOT section = " << section << endl; 155 } 156 else { 157 cerr << "invalid section: in line '" << line << "'" << endl; 158 return false; 159 } 160 } 161 else if ( !get_att_val ( line, section ) ){ 162 cerr << "invalid attribute value pair in line '" 163 << line << "'" << endl; 164 return false; 165 } 166 } 167 return true; 168 } 169 fill(const string & fileName,const string & insect)170 bool Configuration::fill( const string& fileName, const string& insect ){ 171 /// fill a Configuration structure from a file for a certain section 172 /*! 173 \param fileName the name of the input file 174 \param insect the section to find and fill 175 \return true on succes 176 */ 177 ifstream is( fileName ); 178 if ( !is ){ 179 cerr << "unable to read configuration from " << fileName << endl; 180 return false; 181 } 182 bool found = false; 183 string inLine; 184 string localsection; 185 string section = TiCC::trim(insect); 186 // cerr << "looking for section = " << insection << endl; 187 while ( getline( is, inLine ) ){ 188 string line = TiCC::trim(inLine); 189 if ( line.empty() || line[0] == '#' ){ 190 continue; 191 } 192 if ( match_front( line, "[[" ) ){ 193 if ( line[line.length()-1] == ']' && 194 line[line.length()-2] == ']' ){ 195 localsection = line.substr(2,line.length()-4); 196 localsection = TiCC::trim(localsection); 197 // cerr << "GOT section = " << localsection << endl; 198 } 199 else { 200 cerr << "invalid section: in line '" << line << "'" << endl; 201 return false; 202 } 203 } 204 else if ( localsection == section ){ 205 found = true; 206 if ( !get_att_val( line, section ) ){ 207 cerr << "invalid attribute value pair in line '" 208 << line << "'" << endl; 209 return false; 210 } 211 } 212 } 213 if ( !found ){ 214 cerr << "unable to find a section [[" << section << "]] in file: " 215 << fileName << endl; 216 return false; 217 } 218 return true; 219 } 220 encode_ctrl(const string & in)221 string encode_ctrl( const string& in ){ 222 /// encdode "\n", "\r" and "\t" into "\\n", "\\r" and "\\t" 223 /*! 224 \param in the input string 225 \return an encodes string 226 */ 227 string out; 228 for ( const auto& c : in ){ 229 switch ( c ){ 230 case '\n': out += "\\n"; 231 break; 232 case '\r': out += "\\r"; 233 break; 234 case '\t': out += "\\t"; 235 break; 236 default: 237 out += c; 238 } 239 } 240 return out; 241 } 242 dump(ostream & os) const243 void Configuration::dump( ostream& os ) const { 244 /// dump a Configuration to a stream 245 auto it1 = myMap.find("global"); 246 if ( it1 == myMap.end() ){ 247 os << "empty" << endl; 248 return; 249 } 250 os << "[[global]]" << endl; 251 auto it2 = it1->second.begin(); 252 while ( it2 != it1->second.end() ){ 253 os << it2->first << "=" << it2->second << endl; 254 ++it2; 255 } 256 it1 = myMap.begin(); 257 while ( it1 != myMap.end() ){ 258 if ( it1->first != "global" ){ 259 os << endl << "[[" << it1->first << "]]" << endl; 260 it2 = it1->second.begin(); 261 while ( it2 != it1->second.end() ){ 262 os << it2->first << "=" << encode_ctrl(it2->second) << endl; 263 ++it2; 264 } 265 } 266 ++it1; 267 } 268 } 269 create_configfile(const string & name) const270 void Configuration::create_configfile( const string& name ) const { 271 /// create a named configfile 272 /*! 273 \param name the name of the output file 274 */ 275 ofstream os( name ); 276 if ( !os ){ 277 throw runtime_error( "unable to create outputfile: " + name ); 278 } 279 dump( os ); 280 } 281 setatt(const string & inatt,const string & inval,const string & insect)282 string Configuration::setatt( const string& inatt, 283 const string& inval, 284 const string& insect ){ 285 /// set an attribute/value pair in a section 286 /*! 287 \param inatt the attribute to add 288 \param inval the value to insert 289 \param insect the section to insert in. When empty, use the "global" one. 290 \return the old value if \e inatt was already set. 291 */ 292 string oldVal; 293 string att = TiCC::trim(inatt); 294 string val = TiCC::trim(inval); 295 string sect = TiCC::trim(insect); 296 if ( sect.empty() ){ 297 sect = "global"; 298 } 299 auto it1 = myMap.find( sect ); 300 if ( it1 != myMap.end() ){ 301 auto const& it2 = it1->second.find( att ); 302 if ( it2 != it1->second.end() ){ 303 oldVal = it2->second; 304 } 305 it1->second[att] = val; 306 } 307 else { 308 myMap[sect].insert( make_pair( att, val ) ); 309 } 310 return oldVal; 311 } 312 clearatt(const string & inatt,const string & insect)313 string Configuration::clearatt( const string& inatt, 314 const string& insect ){ 315 /// remove an attribute (and its value) from a section 316 /*! 317 \param inatt the attribute to remove 318 \param insect the section to search, When empty, use the "global" one. 319 \return the value of the removed attribute 320 */ 321 // cerr << "clear att: '" << inatt << "' in [" << insect << "]" << endl; 322 string oldVal; 323 string sect = TiCC::trim(insect); 324 string att = TiCC::trim(inatt); 325 if ( sect.empty() ){ 326 sect = "global"; 327 } 328 // cerr << "clear sect [" << sect << "]" << endl; 329 auto it1 = myMap.find( sect ); 330 if ( it1 != myMap.end() ){ 331 // cerr << "found sect [" << sect << "]" << endl; 332 auto const& it2 = it1->second.find( att ); 333 if ( it2 != it1->second.end() ){ 334 // cerr << "found att [" << att << "]" << endl; 335 oldVal = it2->second; 336 } 337 else { 338 // cerr << "missed att [" << att << "]" << endl; 339 } 340 it1->second.erase( att ); 341 } 342 else { 343 // cerr << "MISSED sect [" << sect << "]" << endl; 344 } 345 return oldVal; 346 } 347 erasesection(const string & insect)348 void Configuration::erasesection( const string& insect ){ 349 /// remove all attributes from a section and removes the section 350 /*! 351 \param insect the section to remove. 352 */ 353 myMap.erase( insect ); 354 } 355 getatt(const string & inatt,const string & insect) const356 string Configuration::getatt( const string& inatt, 357 const string& insect ) const { 358 /// return the value for an attribute 359 /*! 360 \param inatt the attribute to remove 361 \param insect the section to search, When empty, use the "global" one. 362 \return the value of the attribute 363 \note when the attribute is NOT FOUND in the given section, we try to 364 retrieve it from the 'global' one. Which serves as a fallback then 365 */ 366 string sect = TiCC::trim(insect); 367 string att = TiCC::trim(inatt); 368 string key = sect; 369 if ( key.empty() ){ 370 key = "global"; 371 } 372 auto const& it1 = myMap.find( key ); 373 if ( it1 == myMap.end() ){ 374 return ""; 375 } 376 else { 377 auto const& it2 = it1->second.find( att ); 378 if ( it2 == it1->second.end() ){ 379 if ( sect.empty() || sect == "global" ){ 380 return ""; 381 } 382 else { 383 return getatt( att, "global" ); 384 } 385 } 386 else { 387 return it2->second; 388 } 389 } 390 } 391 lookUpAll(const string & insect) const392 map<string,string> Configuration::lookUpAll( const string& insect ) const { 393 /// return all attribute/value values in a given section 394 /*! 395 \param insect the section to search. When empty use 'global' 396 \return a map of all attribute/value pairs 397 */ 398 map<string,string> result; 399 string sect = TiCC::trim(insect); 400 if ( sect.empty() ){ 401 sect = "global"; 402 } 403 auto const& it1 = myMap.find( sect ); 404 if ( it1 != myMap.end() ){ 405 auto it2 = it1->second.begin(); 406 while ( it2 != it1->second.end() ){ 407 result[it2->first] = it2->second; 408 ++it2; 409 } 410 } 411 return result; 412 } 413 lookUpSections() const414 set<string> Configuration::lookUpSections() const { 415 /// return the names of all sections 416 set<string> result; 417 result.insert("global"); 418 auto it = myMap.begin(); 419 while ( it != myMap.end() ){ 420 result.insert( it->first ); 421 ++it; 422 } 423 return result; 424 } 425 hasSection(const string & insect) const426 bool Configuration::hasSection( const string& insect ) const { 427 /// check the presence of a section 428 string sect = TiCC::trim( insect ); 429 if ( !sect.empty() ){ 430 auto const it = myMap.find( sect ); 431 if ( it != myMap.end() ){ 432 return true; 433 } 434 } 435 return false; 436 } 437 merge(const Configuration & in,bool override)438 void Configuration::merge( const Configuration& in, bool override ) { 439 /// merge two Configuration objects 440 /*! 441 \param in the Configuration to add 442 \param override when true, override allready present attributes 443 */ 444 // get al sections from in; 445 set<string> sections = in.lookUpSections(); 446 for ( const auto& s : sections ){ 447 // for every section, get all at-val pairs 448 auto avs = in.lookUpAll( s ); 449 for ( const auto& av : avs ){ 450 // merge every at-val in the wanted section 451 if ( !override 452 && myMap[s].find( av.first ) != myMap[s].end() ){ 453 continue; 454 } 455 setatt( av.first, av.second, s ); 456 } 457 } 458 } 459 460 } 461