1 // Richard J. Wagner  v2.1  24 May 2004  wagnerr@umich.edu
2 // Modified by Joey Parrish, June 2011 joey.parrish@gmail.com
3 
4 // Copyright (c) 2004 Richard J. Wagner
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to
8 // deal in the Software without restriction, including without limitation the
9 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 // sell copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 // IN THE SOFTWARE.
23 
24 #include "configfile.h"
25 
26 using std::string;
27 
ConfigFile(string filename,string delimiter,string comment,string sentry)28 ConfigFile::ConfigFile( string filename, string delimiter,
29                         string comment, string sentry )
30     : myDelimiter(delimiter), myComment(comment), mySentry(sentry)
31 {
32     // Construct a ConfigFile, getting keys and values from given file
33 
34     std::ifstream in( filename.c_str() );
35 
36     if( !in ) throw file_not_found( filename );
37 
38     in >> (*this);
39 }
40 
41 
ConfigFile()42 ConfigFile::ConfigFile()
43     : myDelimiter( string(1,'=') ), myComment( string(1,'#') )
44 {
45     // Construct a ConfigFile without a file; empty
46 }
47 
48 
remove(const string & key)49 void ConfigFile::remove( const string& key )
50 {
51     // Remove key and its value
52     myContents.erase( key );
53     if (myLineNumbers.find( key ) != myLineNumbers.end()) {
54         myLines[myLineNumbers[key]].erase();
55         myLineNumbers.erase( key );
56     }
57     return;
58 }
59 
60 
keyExists(const string & key) const61 bool ConfigFile::keyExists( const string& key ) const
62 {
63     // Indicate whether key is found
64     mapci p = myContents.find( key );
65     return ( p != myContents.end() );
66 }
67 
68 
69 /* static */
trim(string & s)70 void ConfigFile::trim( string& s )
71 {
72     // Remove leading and trailing whitespace
73     static const char whitespace[] = " \n\t\v\r\f";
74     s.erase( 0, s.find_first_not_of(whitespace) );
75     s.erase( s.find_last_not_of(whitespace) + 1U );
76 }
77 
78 
operator <<(std::ostream & os,const ConfigFile & cf)79 std::ostream& operator<<( std::ostream& os, const ConfigFile& cf )
80 {
81     // Save a ConfigFile to os
82     for( size_t i = 0;
83          i != cf.myLines.size();
84          ++i )
85     {
86         os << cf.myLines[i] << std::endl;
87     }
88     if ( cf.mySentry != "" )
89     {
90         os << cf.mySentry << std::endl;
91     }
92     return os;
93 }
94 
95 
operator >>(std::istream & is,ConfigFile & cf)96 std::istream& operator>>( std::istream& is, ConfigFile& cf )
97 {
98     // Load a ConfigFile from is
99     // Read in keys and values, keeping internal whitespace
100     typedef string::size_type pos;
101     const string& delim  = cf.myDelimiter;  // separator
102     const string& comm   = cf.myComment;    // comment
103     const string& sentry = cf.mySentry;     // end of file sentry
104     const pos skip = delim.length();        // length of separator
105 
106     string nextline = "";  // might need to read ahead to see where value ends
107 
108     while( is || nextline.length() > 0 )
109     {
110         // Read an entire line at a time
111         string line;
112         if( nextline.length() > 0 )
113         {
114             line = nextline;  // we read ahead; use it now
115             nextline = "";
116         }
117         else
118         {
119             std::getline( is, line );
120         }
121         int line_number = cf.myLines.size();
122         cf.myLines.push_back(line);
123 
124         // Ignore comments
125         // TODO: we will loose commented line here, this is bad
126         // if it will be modified
127         line = line.substr( 0, line.find(comm) );
128 
129         // Check for end of file sentry
130         if( sentry != "" && line.find(sentry) != string::npos ) return is;
131 
132         // Parse the line if it contains a delimiter
133         pos delimPos = line.find( delim );
134         if( delimPos != string::npos )
135         {
136             // Extract the key
137             string key = line.substr( 0, delimPos );
138             line.replace( 0, delimPos+skip, "" );
139 
140             // See if value continues on the next line
141             // Stop at blank line, next line with a key, end of stream,
142             // or end of file sentry
143             while( is )
144             {
145                 std::getline( is, nextline );
146 
147                 if ( is.eof() )
148                 {
149                     // don't add an extra blank line to the list.
150                     break;
151                 }
152 
153                 string nlcopy = nextline;
154                 ConfigFile::trim(nlcopy);
155                 if( nlcopy == "" )
156                 {
157                     cf.myLines.push_back(nextline);
158                     break;
159                 }
160 
161                 if (nextline.find(comm) != 0) {
162                     // TODO: we will loose commented line here, this is bad
163                     nextline = nextline.substr( 0, nextline.find(comm) );
164                 } else {
165                     // Comment will be processed later
166                     break;
167                 }
168                 if( nextline.find(delim) != string::npos )
169                 {
170                     // we will process it at next run correctly
171                     break;
172                 }
173                 if( sentry != "" && nextline.find(sentry) != string::npos )
174                 {
175                     cf.myLines.push_back(nextline);
176                     break;
177                 }
178 
179                 nlcopy = nextline;
180                 ConfigFile::trim(nlcopy);
181                 if( nlcopy != "" )
182                 {
183                     cf.myLines[line_number] += "\n";
184                     line += "\n";
185                 }
186                 line += nlcopy;
187                 cf.myLines[line_number] += nlcopy;
188             }
189 
190             // Store key and value
191             ConfigFile::trim(key);
192             ConfigFile::trim(line);
193             cf.myContents[key] = line;  // overwrites if key is repeated
194             cf.myLineNumbers[key] = line_number;
195         }
196     }
197 
198     return is;
199 }
200