Project

General

Profile

Statistics
| Branch: | Revision:

root / src / envir / inifilereader.cc @ 636cbdd5

History | View | Annotate | Download (9.91 KB)

1
//==========================================================================
2
//  INIFILEREADER.CC - part of
3
//                     OMNeT++/OMNEST
4
//            Discrete System Simulation in C++
5
//
6
//  Author: Andras Varga
7
//
8
//==========================================================================
9

    
10
/*--------------------------------------------------------------*
11
  Copyright (C) 1992-2008 Andras Varga
12
  Copyright (C) 2006-2008 OpenSim Ltd.
13

14
  This file is distributed WITHOUT ANY WARRANTY. See the file
15
  `license' for details on this and other legal matters.
16
*--------------------------------------------------------------*/
17

    
18
#include <string.h>
19
#include <stdio.h>
20
#include <errno.h>
21
#include <set>
22
#include "opp_ctype.h"
23
#include "fileutil.h"  // directoryOf
24
#include "inifilereader.h"
25
#include "fsutils.h"
26
#include "cexception.h"
27

    
28
USING_NAMESPACE
29

    
30

    
31
#define ERRPREFIX  "Error at `%s' line %d: "
32

    
33

    
34
InifileReader::InifileReader()
35
{
36
}
37

    
38
InifileReader::~InifileReader()
39
{
40
}
41

    
42
void InifileReader::initializeFrom(cConfiguration *bootConfig)
43
{
44
    throw cRuntimeError("InifileReader: initializeFrom() method not supported");
45
}
46

    
47
const char *InifileReader::getFileName() const
48
{
49
    return rootfilename.c_str();
50
}
51

    
52
const char *InifileReader::getDefaultBaseDirectory() const
53
{
54
    return defaultbasedir.c_str();
55
}
56

    
57
int InifileReader::getNumSections() const
58
{
59
    return sections.size();
60
}
61

    
62
const char *InifileReader::getSectionName(int sectionId) const
63
{
64
    const Section& section = getSection(sectionId);
65
    return section.name.c_str();
66
}
67

    
68
int InifileReader::getNumEntries(int sectionId) const
69
{
70
    const Section& section = getSection(sectionId);
71
    return section.entries.size();
72
}
73

    
74
const InifileReader::KeyValue& InifileReader::getEntry(int sectionId, int entryId) const
75
{
76
    const Section& section = getSection(sectionId);
77
    if (entryId<0 || entryId>=(int)section.entries.size())
78
        throw cRuntimeError("InifileReader: entry index %d out of bounds", entryId);
79
    return section.entries[entryId];
80
}
81

    
82
const InifileReader::Section& InifileReader::getSection(int sectionId) const
83
{
84
    if (sectionId<0 || sectionId>=(int)sections.size())
85
        throw cRuntimeError("InifileReader: section index %d out of bounds", sectionId);
86
    return sections[sectionId];
87
}
88

    
89
const InifileReader::Section& InifileReader::getOrCreateSection(const char *sectionName)
90
{
91
    for (int i=0; i<(int)sections.size(); i++)
92
        if (!strcmp(sections[i].name.c_str(), sectionName))
93
            return sections[i];
94
    Section section;
95
    section.name = sectionName;
96
    sections.push_back(section);
97
    return sections.back();
98
}
99

    
100
void InifileReader::readFile(const char *filename)
101
{
102
    rootfilename = filename;
103

    
104
    // use the first ini file's location as default base dir
105
    if (defaultbasedir.empty())
106
        defaultbasedir = directoryOf(rootfilename.c_str());
107

    
108
    internalReadFile(filename, NULL);
109
}
110

    
111
void InifileReader::internalReadFile(const char *filename, Section *currentSection)
112
{
113
    // create an entry for this file, checking against circular inclusion
114
    std::string tmpfname = tidyFilename(toAbsolutePath(filename).c_str(),true);
115
    std::string tmpdir = directoryOf(tmpfname.c_str());
116
    if (filenames.find(tmpfname)!=filenames.end())
117
        throw cRuntimeError("Ini file `%s' includes itself, directly or indirectly", filename);
118
    filenames.insert(tmpfname);
119
    if (basedirs.find(tmpdir)==basedirs.end())
120
        basedirs.insert(tmpdir);
121

    
122
    // get a reference to the string instance in filenames[], we'll refer to it in KeyValue
123
    const std::string *filenameRef = &(*filenames.find(tmpfname));
124
    const std::string *basedirRef = &(*basedirs.find(tmpdir));
125

    
126
    // open and read this file
127
    FILE *file = fopen(filename,"r");
128
    if (!file)
129
        throw cRuntimeError("Cannot open ini file `%s'", filename);
130

    
131
    int lineNumber = 0;
132
    std::set<std::string> sectionsInFile; // we'll check for uniqueness
133

    
134
    std::string rawLine;
135
    while (readLineInto(rawLine, file))
136
    {
137
        ASSERT(rawLine.empty() || (*(rawLine.end()-1)!='\r' && *(rawLine.end()-1)!='\n'));
138

    
139
        // join continued lines
140
        lineNumber++;
141
        std::string lineBuf = rawLine;
142
        if (!rawLine.empty() && *(rawLine.end()-1) == '\\')
143
        {
144
            while (true)
145
            {
146
                readLineInto(rawLine, file);
147
                lineNumber++;
148
                lineBuf.resize(lineBuf.size()-1); // cut off backslash from previous line
149
                lineBuf += rawLine;
150
                if (rawLine.empty() || *(rawLine.end()-1) != '\\')
151
                    break;
152
            }
153
        }
154

    
155
        // process the line
156
        const char *line = lineBuf.c_str();
157
        while (opp_isspace(*line)) line++;
158

    
159
        if (!strncmp(line, "#% old-wildcards", 16))
160
        {
161
            // in old ini files, "*" matched dots as well, like "**" does now;
162
            // we don't support this backward compatibility feature any longer
163
            throw cRuntimeError(ERRPREFIX "old-wildcards mode (#%% old-wildcards syntax) no longer supported", filename, lineNumber);
164
        }
165
        else if (!*line || *line=='#')
166
        {
167
            // blank or comment line, ignore
168
        }
169
        else if (*line==';')
170
        {
171
            // obsolete comment line
172
            throw cRuntimeError(ERRPREFIX "semicolon is no longer a comment start character, please use hashmark ('#')", filename, lineNumber);
173
        }
174
        else if (*line=='i' && strncmp(line, "include", 7)==0 && opp_isspace(line[7]))
175
        {
176
            // include directive
177
            const char *rest = line+7;
178
            const char *endPos = findEndContent(rest, filename, lineNumber);
179
            std::string includeFilename = trim(rest, endPos);
180

    
181
            // filenames should be relative to the current ini file we're processing,
182
            // so cd into its directory before opening included file
183
            PushDir d(directoryOf(filename).c_str());
184

    
185
            // process included inifile
186
            internalReadFile(includeFilename.c_str(), currentSection);
187
        }
188
        else if (*line=='[')
189
        {
190
            // section heading
191
            const char *endPos = findEndContent(line, filename, lineNumber);
192
            if (*(endPos-1) != ']')
193
                throw cRuntimeError(ERRPREFIX "syntax error in section heading", filename, lineNumber);
194
            std::string sectionName = trim(line+1, endPos-1);
195
            if (sectionName.empty())
196
                throw cRuntimeError(ERRPREFIX "section name cannot be empty", filename, lineNumber);
197

    
198
            // section name must be unique within file (to reduce risk of copy/paste errors)
199
            if (sectionsInFile.find(sectionName) != sectionsInFile.end())
200
                throw cRuntimeError(ERRPREFIX "section name not unique within file", filename, lineNumber);
201
            sectionsInFile.insert(sectionName);
202

    
203
            // add section of not yet seen (in another file)
204
            currentSection = (Section *)&getOrCreateSection(sectionName.c_str());
205
        }
206
        else
207
        {
208
            // key = value
209
            if (currentSection==NULL)
210
                currentSection = (Section *)&getOrCreateSection("General");
211
            const char *endPos = findEndContent(line, filename, lineNumber);
212
            const char *equalSignPos = strchr(line, '=');
213
            if (equalSignPos==NULL || equalSignPos > endPos)
214
                throw cRuntimeError(ERRPREFIX "line must be in the form key=value", filename, lineNumber);
215
            std::string key = trim(line, equalSignPos);
216
            std::string value = trim(equalSignPos+1, endPos);
217
            if (key.empty())
218
                throw cRuntimeError(ERRPREFIX "line must be in the form key=value", filename, lineNumber);
219

    
220
            currentSection->entries.push_back(KeyValue1(basedirRef, filenameRef, lineNumber, key.c_str(), value.c_str()));
221
        }
222
    }
223
}
224

    
225
bool InifileReader::readLineInto(std::string& line, FILE *file)
226
{
227
    line = "";
228
    if (feof(file))
229
        return false;
230

    
231
    char buffer[512+1];
232
    while (fgets(buffer, 512, file)!=NULL)
233
    {
234
        const char *endBuffer = buffer + strlen(buffer);
235
        if (buffer==endBuffer) break; // should not happen
236
        bool eolReached = *(endBuffer-1)=='\n' || *(endBuffer-1)=='\r';
237
        while (endBuffer>buffer && opp_isspace(*(endBuffer-1))) endBuffer--;
238
        line.append(buffer, endBuffer - buffer);
239
        if (eolReached)
240
            break;
241
    }
242
    if (ferror(file))
243
        throw cRuntimeError("Cannot read ini file: %s", strerror(errno));
244
    return true;
245
}
246

    
247
/**
248
 * Returns the position of the comment on the given line (i.e. the position of the
249
 * # character), or line.length() if no comment is found. string literals
250
 * are recognized and skipped properly.
251
 */
252
const char *InifileReader::findEndContent(const char *line, const char *filename, int lineNumber)
253
{
254
    const char *s = line;
255
    while (*s)
256
    {
257
        switch (*s) {
258
        case '"':
259
            // string literal: skip it
260
            s++;
261
            while (*s && *s!='"') {
262
                if (*s=='\\')  // skip \", \\, etc.
263
                    s++;
264
                s++;
265
            }
266
            if (!*s)
267
                throw cRuntimeError(ERRPREFIX "unterminated string constant", filename, lineNumber);
268
            s++;
269
            break;
270
        case '#':
271
            // comment; seek back to last non-space character
272
            while ((s-1)>line && opp_isspace(*(s-1)))
273
                s--;
274
            return s;
275
        default:
276
            s++;
277
        }
278
    }
279
    return s;
280
}
281

    
282
std::string InifileReader::trim(const char *start, const char *end)
283
{
284
    while (start<end && opp_isspace(*start)) start++;
285
    while ((end-1)>start && opp_isspace(*(end-1))) end--;
286
    return std::string(start, end-start);
287
}
288

    
289
void InifileReader::dump() const
290
{
291
    for (int i=0; i<getNumSections(); i++)
292
    {
293
        printf("\n[%s]\n", getSectionName(i));
294
        for (int j=0; j<getNumEntries(i); j++)
295
        {
296
            const KeyValue& e = getEntry(i, j);
297
            printf("%s = %s\t[basedir: %s]\n", e.getKey(), e.getValue(), e.getBaseDirectory());
298
        }
299
    }
300
}
301

    
302