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