ALib C++ Library
Library Version: 2412 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
inifile.cpp
1// #################################################################################################
2// ALib C++ Library
3//
4// Copyright 2013-2024 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6// #################################################################################################
8
9#if !DOXYGEN
16# include <fstream>
17#endif // !DOXYGEN
18
19using namespace alib::lang::system;
20
21namespace alib { namespace config {
22
23// #################################################################################################
24// helpers
25// #################################################################################################
27{
28 auto c= subs.CharAtStart();
29 return c == '#'
30 || c == ';'
31 || ( c == '/' && subs.Length() > 1 && subs.CharAt(1) == '/' );
32}
33
34
35
36// #################################################################################################
37// Constructor/Destructor
38// #################################################################################################
40: Allocator ( ALIB_DBG("IniFile",) 8 )
41, entryTable( Allocator )
42, Sections ( Allocator )
43, LinesWithReadErrors( Allocator )
44{}
45
47{
48 FileComments = nullptr;
49 FileName = nullptr;
50 entryTable .Reset();
51 Sections .Reset();
52 LinesWithReadErrors.Reset();
53 Allocator .Reset();
54}
55
57{
58 auto& section= Sections.EmplaceBack(Allocator);
59 section.Name.Allocate(Allocator, name );
60 return &section;
61}
62
64{
65 // search section
66 for(auto secIt= Sections.begin() ; secIt != Sections.end(); ++secIt )
67 {
68 // found?
69 if( secIt->Name.Equals(name))
70 {
71 auto* section= &*secIt;
72 // delete hashtable entries
73 for(auto entryIt= entryTable.begin() ; entryIt != entryTable.end(); ++entryIt )
74 {
75 if( entryIt->second.first == section )
76 {
77 entryIt= entryTable.Erase( entryIt );
78 continue;
79 }
80 }
81
82 (void) Sections.Erase(secIt);
83 return section;
84 }
85 }
86 return nullptr;
87}
88
90{
91 // search entry
92 for(auto entryIt= section->Entries.begin() ; entryIt != section->Entries.end(); ++entryIt )
93 {
94 // found?
95 if( entryIt->Name.Equals(name))
96 {
97 auto* entry= &*entryIt;
98 ALIB_ASSERT_RESULT_GREATER_THAN( entryTable.Erase( EntryKey(section->Name, name)), 0 )
99 return entry;
100 }
101 }
102 return nullptr;
103}
104
106{
107 ALIB_ASSERT_ERROR( pName.IsNotEmpty(), "CONFIG", "Empty Ini-File variable name given.")
108 Entry& newEntry= section->Entries.PushBack(Entry());
109 newEntry.Name.Allocate(Allocator, pName);
110 entryTable.EmplaceUnique( EntryKey(section->Name, newEntry.Name),
111 std::make_pair(section, &newEntry) );
112 return &newEntry;
113}
114
115std::pair<IniFile::Section*, IniFile::Entry*> IniFile::SearchEntry (const String& sectionName, const String& name )
116{
117 ALIB_ASSERT_ERROR( sectionName.IsNotNull(), "CONFIG", "Nulled section name given.")
118 auto it= entryTable.Find( EntryKey(sectionName, name ) );
119 if( it != entryTable.end() )
120 return it.Mapped();
121 return std::pair<Section*, Entry*>(nullptr, nullptr);
122}
123
125{
126 ALIB_ASSERT_ERROR(sectionName.IsNotNull(), "CONFIG", "Nulled section name given.")
127 auto sIt= Sections.begin();
128 while( sIt != Sections.end() )
129 {
130 if( sIt->Name.Equals<CHK, lang::Case::Ignore>( sectionName ) )
131 return &*sIt;
132 ++sIt;
133 }
134 return nullptr;
135}
136
137std::pair<IniFile::Section*, bool>
139{
140 Section* s= SearchSection( sectionName );
141 if ( s != nullptr )
142 return std::make_pair( s, false );
143
144 return std::make_pair( CreateSection( sectionName ), true );
145}
146
147void IniFile::AddComments ( String& dest, const String& comments, const String& prefix )
148{
149 String2K buf;
150 Tokenizer tknzr( comments, '\n' );
151 while( tknzr.HasNext() )
152 {
153 auto tok= tknzr.Next().TrimEnd();
154 tok.ConsumeCharFromEnd('\r');
155 if( !startsWithCommentSymbol(tok) )
156 buf << prefix;
157 buf << tok << NEW_LINE;
158 }
159 dest.Allocate(Allocator, buf);
160}
161
163{
165
166 //------------- open file -------------
167 ALIB_STRINGS_TO_NARROW(path, nPath, 256)
168 std::ifstream file( nPath );
169
170 if ( !file.is_open() )
171 {
172 int errNo= errno;
173
174 // file does not exist ?
175 if ( errNo == UnderlyingIntegral(SystemErrors::enoent) )
176 return -1;
177
178 // other errors: throw
181 A_CHAR("INI-"), path );
182 throw e;
183 }
184
185
186 if( Sections.IsEmpty() )
188
189 String256 actLine; ALIB_DBG( actLine .DbgDisableBufferReplacementWarning(); )
190 String4K actComments; ALIB_DBG( actComments.DbgDisableBufferReplacementWarning(); )
191 Section* actSection= &Sections.Front();
192
193 String8 equalSignOrWhitespace('=');
194 equalSignOrWhitespace << DEFAULT_WHITESPACES;
195
196 StringReader reader;
197 reader.SetStream( &file );
198
199 auto writeBackAttribute= alib::CONFIG.GetResource("INIWB");
200
201
202 //------------- read loop -------------
203 bool writebackFlag = false;
204 int qtyEntriesRead = 0;
205 int lineNo = 0;
206 bool fileHeaderRead = false;
207 while( !reader.IsEOF() )
208 {
209 reader.Read( actLine ); ++lineNo;
210
211 Substring lineTrimmed( actLine );
212 lineTrimmed.Trim();
213
214 // end of file header?
215 if ( !fileHeaderRead && lineTrimmed.IsEmpty() )
216 {
217 fileHeaderRead= true;
218 FileComments.Allocate(Allocator, actComments);
219 actComments.Reset();
220 }
221
222 // continued comment or empty section?
223 if ( lineTrimmed.IsEmpty() || startsWithCommentSymbol( lineTrimmed ) )
224 {
225 actComments._(actLine).NewLine();
226 continue;
227 }
228
229 // writeback flag?
230 if( lineTrimmed.Equals<NC,lang::Case::Ignore>(writeBackAttribute) )
231 {
232 writebackFlag= true;
233 continue;
234 }
235
236 // section line?
237 if ( lineTrimmed.ConsumeChar('[') )
238 {
239 fileHeaderRead= true;
240
241 if( !lineTrimmed.ConsumeCharFromEnd( ']' ) )
242 {
243 LinesWithReadErrors.EmplaceBack( lineNo );
244 // continue; // we tolerate missing section ends, but still mark this as a read error
245 }
246 lineTrimmed.TrimEnd();
247 actSection= SearchOrCreateSection( lineTrimmed ).first;
248 if( actSection->Comments.IsEmpty())
249 actSection->Comments.Allocate(Allocator, actComments);
250 actSection->WriteBack= writebackFlag;
251 writebackFlag= false;
252
253 actComments.Reset();
254 continue;
255 }
256
257 // Variable line(s)
258 {
259 String128 actName;
260 String4K actRawValue;
261
262 integer idx= lineTrimmed.IndexOfAny<lang::Inclusion::Include>( equalSignOrWhitespace );
263 if( idx < 0 )
264 {
265 actName << lineTrimmed;
266 lineTrimmed.Clear();
267 }
268 else
269 {
270 actName << lineTrimmed.Substring( 0, idx );
271 actName.TrimEnd();
272 lineTrimmed.ConsumeChars( idx );
273 actRawValue._(lineTrimmed);
274 }
275
276 // read continues as long as lines end with '\' (must not be '\\')
277 while ( lineTrimmed.CharAtEnd() == '\\'
278 && (lineTrimmed.Length() == 1 || lineTrimmed.CharAt<NC>( lineTrimmed.Length() -2 ) != '\\' ) )
279 {
280 actRawValue.NewLine();
281 reader.Read( actLine );
282 if ( reader.IsEOF() )
283 {
284 // last line of the file ended with '\' !
285 lineTrimmed.Clear();
286 break;
287 }
288 actLine.TrimEnd();
289 actRawValue << (actLine);
290 lineTrimmed= actLine;
291 }
292
293 // insert entry with raw and trimmed value
294 {
295 auto* entry= SearchEntry( actSection->Name, actName ).second;
296 if( entry == nullptr)
297 {
298 entry= CreateEntry( actSection, actName );
299 ++qtyEntriesRead;
300 }
301 else
302 {
303 ALIB_WARNING("CONFIG", "Variable was found twice in INI-file. First value will be discarded\n"
304 "on writing. Variable name: ", actName )
305 }
306 entry->Comments.Allocate(Allocator, actComments );
307 entry->RawValue.Allocate(Allocator, actRawValue );
308 entry->WriteBack= writebackFlag;
309 writebackFlag= false;
310
311 // parse trimmed value
312 String4K trimmedValue;
313 Substring parser= actRawValue;
314 parser.Trim().ConsumeChar('=');
315 parser.TrimStart();
316 Tokenizer tknzr(parser, '\n', true);
317 while((parser= tknzr.Next()).IsNotNull())
318 {
319 parser.ConsumeCharFromEnd('\r');
320 if( parser.CharAtEnd() == '\\'
321 && parser.CharAt(parser.Length()-2) != '\\' )
322 parser.ConsumeCharFromEnd<NC>();
323 parser.TrimEnd();
324 if(!startsWithCommentSymbol(parser))
325 trimmedValue << parser;
326 }
327 entry->Value.Allocate(Allocator, trimmedValue );
328 }
329
330 } // variable line(s)
331
332 actComments.Reset();
333 } // !EOF
334 file.close();
335
336 return qtyEntriesRead;
337}
338
339void IniFile::Write(const PathString& pPath)
340{
341 auto writeBackAttribute= alib::CONFIG.GetResource("INIWB");
342
343 NString256 path(pPath);
344 if( path.IsEmpty() )
345 path << FileName;
346 ALIB_ASSERT_ERROR( path.IsNotEmpty(), "CONFIG",
347 "Given Path is empty and no known filename from previous Read() operation available.")
348
349 // open output file
350 std::ofstream outputFileStream( path.Terminate(), std::ios::binary );
351 if ( !outputFileStream.is_open() )
352 {
353 int errNo= errno;
356 throw e;
357 }
358
359 StringWriter writer;
360 writer.SetStream( &outputFileStream );
361
362 // write file header
363 if ( FileComments.IsNotEmpty() )
364 {
365 writer.Write( FileComments );
366 if (FileComments.CharAtEnd() != '\n')
367 writer.Write( NEW_LINE );
368 }
369
370 // loop over all sections
371 for ( Section& section : Sections )
372 {
373 if( section.Name.IsNotEmpty())
374 {
375 // write section comments, write back flag and name
376 if(section.Comments.CharAtStart() !='\n' && section.Comments.CharAtStart() !='\r')
377 writer.Write( NEW_LINE );
378 writer.Write( section.Comments );
379 if(section.WriteBack)
380 {
381 writer.Write( writeBackAttribute );
382 writer.Write( NEW_LINE );
383 }
384 writer.Write( NString256() << '[' << section.Name << ']' << NEW_LINE );
385 }
386
387 // variables
388 integer maxVarLength= 0;
389 for ( auto& entry : section.Entries )
390 maxVarLength= (std::max)( maxVarLength, entry.Name.Length() );
391
392 for ( auto& entry : section.Entries )
393 {
394 // write comments, write-back-flag, name
395 if( entry.Comments.IsNotEmpty())
396 {
397 if (entry.Comments.CharAtStart() != '\n' && entry.Comments.CharAtStart() != '\r')
398 writer.Write( NEW_LINE );
399 writer.Write( entry.Comments );
400 }
401
402 if(entry.WriteBack)
403 {
404 writer.Write( writeBackAttribute );
405 writer.Write( NEW_LINE );
406 }
407 writer.Write( entry.Name );
408
409 // write value
410 if( entry.NewValue.IsNull())
411 {
412 writer.Write( entry.RawValue );
413 if(!entry.NewValue.EndsWith(NEW_LINE))
414 writer.Write( NEW_LINE );
415 }
416 else
417 {
418 writer.Write( "=" );
419 int cntLine= 0;
420 integer maxValLength= 0;
421 Substring rest= entry.NewValue;
422 for(;;)
423 {
424 // write spaces
425 writer.WriteChars(' ', maxVarLength - ( cntLine == 0 ? entry.Name.Length() - 1
426 : -2 ));
427
428 Substring actual= rest.ConsumeToken( '\n' );
429 actual.ConsumeCharFromEnd('\r'); // in win case
430
431 // escape a trailing comment symbol
432 if( actual.CharAtStart() == '#' || actual.CharAtStart() == ';' )
433 writer.Write( "\\" );
434
435 writer.Write( actual );
436 if( rest.IsEmpty())
437 {
438 writer.Write( NEW_LINE );
439 break;
440 }
441
442 if( actual.Length() > maxValLength )
443 maxValLength= actual.Length() + 2;
444 writer.WriteChars(' ', maxValLength - actual.Length() );
445
446 writer.Write( "\\" );
447 writer.Write( NEW_LINE );
448
449 ++cntLine;
450 }
451 }
452 }
453 }
454
455 // close file
456 outputFileStream.close();
457}
458
459
460}} // namespace [alib::config]
lang::system::PathString FileName
The file name.
Definition inifile.hpp:239
ALIB_API void AddComments(String &dest, const String &comments, const String &prefix=A_CHAR("# "))
Definition inifile.cpp:147
List< MonoAllocator, Section > Sections
The list of sections.
Definition inifile.hpp:236
ALIB_API void Reset()
Clears all data, resets the internal mono allocator.
Definition inifile.cpp:46
ALIB_API Section * CreateSection(const String &name)
Definition inifile.cpp:56
ALIB_API std::pair< Section *, bool > SearchOrCreateSection(const String &sectionName)
Definition inifile.cpp:138
String FileComments
The file header which will be written out as a comment lines with "# " prefixes.
Definition inifile.hpp:242
HashMap< MonoAllocator, EntryKey, std::pair< Section *, Entry * >, EntryKey::Hash, EntryKey::EqualTo > entryTable
Definition inifile.hpp:224
ALIB_API void Write(const lang::system::PathString &path=lang::system::NULL_PATH)
Definition inifile.cpp:339
ALIB_API Entry * CreateEntry(Section *section, const String &name)
Definition inifile.cpp:105
ALIB_API Section * DeleteSection(const String &name)
Definition inifile.cpp:63
ALIB_API Section * SearchSection(const String &sectionName)
Definition inifile.cpp:124
ALIB_API Entry * DeleteEntry(Section *section, const String &name)
Definition inifile.cpp:89
List< MonoAllocator, integer > LinesWithReadErrors
Definition inifile.hpp:246
ALIB_API integer Read(const lang::system::CPathString &path)
Definition inifile.cpp:162
ALIB_API IniFile()
Default constructor.
Definition inifile.cpp:39
bool startsWithCommentSymbol(String &subs)
Definition inifile.cpp:26
ALIB_API std::pair< Section *, Entry * > SearchEntry(const String &section, const String &name)
Definition inifile.cpp:115
const String & GetResource(const NString &name)
Exception & Add(const lang::CallerInfo &ci, TEnum type, TArgs &&... args)
constexpr const TChar * Terminate() const
Definition tastring.inl:821
TAString & TrimEnd(const TCString< TChar > &trimChars=TT_CStringConstants< TChar >::DefaultWhitespaces())
TAString & _(const TString< TChar > &src, integer regionStart, integer regionLength=MAX_LEN)
constexpr bool IsEmpty() const
Definition string.hpp:383
TChar CharAt(integer idx) const
Definition string.hpp:444
void Allocate(TAllocator &allocator, const TString< TChar > &copy)
Definition string.hpp:2012
constexpr bool IsNotEmpty() const
Definition string.hpp:389
constexpr integer Length() const
Definition string.hpp:326
TChar CharAtStart() const
Definition string.hpp:466
constexpr bool IsNotNull() const
Definition string.hpp:371
integer IndexOfAny(const TString &needles, integer startIdx=0) const
Definition string.hpp:1090
TString< TChar > Substring(integer regionStart, integer regionLength=MAX_LEN) const
Definition string.hpp:406
bool Equals(const TString< TChar > &rhs) const
Definition string.hpp:580
TChar CharAtEnd() const
Definition string.hpp:488
TSubstring & TrimEnd(const TCString< TChar > &whiteSpaces=TT_CStringConstants< TChar >::DefaultWhitespaces())
integer ConsumeChars(integer regionLength, TSubstring *target=nullptr)
TSubstring & TrimStart(const TCString< TChar > &whiteSpaces=TT_CStringConstants< TChar >::DefaultWhitespaces())
Definition substring.hpp:89
TString< TChar > ConsumeToken(TChar separator=',', lang::Inclusion includeSeparator=lang::Inclusion::Include)
bool ConsumeCharFromEnd(TChar consumable)
TSubstring & Trim(const TCString< TChar > &whiteSpaces=TT_CStringConstants< TChar >::DefaultWhitespaces())
ALIB_API void Write(const NString &src)
ALIB_API void WriteChars(const nchar fillChar, integer count)
ALIB_API TSubstring< TChar > & Next(lang::Whitespaces trimming=lang::Whitespaces::Trim, TChar newDelim='\0')
Definition tokenizer.cpp:16
#define ALIB_WARNING(...)
Definition alib.hpp:1268
#define ALIB_CALLER_NULLED
Definition alib.hpp:1173
#define A_CHAR(STR)
#define ALIB_STRINGS_TO_NARROW( src, dest, bufSize)
#define ALIB_ASSERT_RESULT_GREATER_THAN(func, value)
Definition alib.hpp:1288
#define ALIB_ASSERT_ERROR(cond,...)
Definition alib.hpp:1271
#define ALIB_DBG(...)
Definition alib.hpp:390
@ ErrorWritingFile
An error occurred writing the file .
@ ErrorOpeningFile
File not found when reading.
This is the reference documentation of sub-namespace system of module ALib BaseCamp.
Definition basecamp.cpp:75
Exception CreateSystemException(const CallerInfo &ci, int errNo)
@ Include
Chooses inclusion.
Definition alib.cpp:69
config::ConfigCamp CONFIG
The singleton instance of ALib Camp class ConfigCamp.
constexpr CString DEFAULT_WHITESPACES
A zero-terminated string of default whitespace characters.
Definition cstring.hpp:600
constexpr const String EMPTY_STRING
An empty string of the default character type.
Definition string.hpp:2529
NLocalString< 256 > NString256
Type alias name for TLocalString<nchar,256>.
constexpr CString NEW_LINE
A zero-terminated string containing the new-line character sequence.
Definition cstring.hpp:580
lang::integer integer
Type alias in namespace alib.
Definition integers.hpp:273
See sibling type NC.
Definition alib.hpp:1097
Hash functor for nodes hashed in field entryTable. Ignores letter case.
Definition inifile.hpp:172
An entry in a Section.
Definition inifile.hpp:135
String Name
The entry's name.
Definition inifile.hpp:136
A section of the INI-file.
Definition inifile.hpp:152
String Name
The name of the section.
Definition inifile.hpp:162
List< MonoAllocator, Entry, Recycling::None > Entries
The list of variables of the section.
Definition inifile.hpp:164