/* PIP - Platform Independent Primitives Config parser Copyright (C) 2014 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "piconfig.h" /*! \class PIConfig * \brief Configuration file * \details This class provide handle access to configuration file. * * \section PIConfig_sec0 Synopsis * PIConfig reads configuration file and create internal dendritic * representation of all entries of this file. You can easily read * some values and write new. * \image html piconfig.png * * %PIConfig supports also INI-style files with sections "[section]". * In this case line with section name interpret as prefix to the next * lines. For example, these configs are equal: * \code * ser.device = /dev/ttyS0 * ser.speed = 115200 * debug = true * \endcode * \code * [ser] * device = /dev/ttyS0 * speed = 115200 * [] * debug = true * \endcode * * \section PIConfig_sec1 Concepts * Each node of internal tree has type PIConfig::Entry. %PIConfig * has one root element \a rootEntry(). Any entry of configuration file is a * child of this element. * */ /*! \class PIConfig::Entry * \brief %Entry of configuration file * \details This class is node of internal PIConfig tree. * %Entry provide access to elements of PIConfig. Each entry has * children or next properties: * * name * * value * * type * * comment * * Each property is a PIString. These properties forms from text line with * format: \code{.cpp} = # \endcode * Type and comment are optional fields. Type is a single letter immediately * after comment symbol "#". \n \n * %Entry has many implicit convertions to common types: boolean, integers, * float, double, PIString, PIStringList. \n \n * Generally there is no need to create instance of %PIConfig::Entry manually, * it returns by functions \a getValue() of \a PIConfig, \a PIConfig::Entry or * \a PIConfig::Branch. If there is no suitable entry to return, reference to * internal instance of %PIConfig::Entry with "default" value will be returned. * \snippet piconfig.cpp PIConfig::Entry * */ /*! \class PIConfig::Branch * \brief %Branch is a list of entries of configuration file * \details %Branch provides some features to get entries lists. * \snippet piconfig.cpp PIConfig::Branch * */ PIConfig::Entry PIConfig::Branch::_empty; PIConfig::Entry PIConfig::Entry::_empty; PIConfig::Branch PIConfig::Branch::allLeaves() { Branch b; b.delim = delim; piForeach (Entry * i, *this) { if (i->isLeaf()) b << i; else allLeaves(b, i); } return b; } PIConfig::Entry & PIConfig::Branch::getValue(const PIString & vname, const PIString & def, bool * exist) { if (vname.isEmpty()) { _empty.clear(); _empty.delim = delim; if (exist != 0) *exist = false; return _empty; } PIStringList tree = vname.split(delim); PIString name = tree.front(); tree.pop_front(); Entry * ce = 0; piForeach (Entry * i, *this) if (i->_name == name) { ce = i; break; } if (ce == 0) { _empty._name = vname; _empty._value = def; _empty.delim = delim; if (exist != 0) *exist = false; return _empty; } piForeach (PIString & i, tree) { ce = ce->findChild(i); if (ce == 0) { _empty._name = vname; _empty._value = def; _empty.delim = delim; if (exist != 0) *exist = false; return _empty; } } if (exist != 0) *exist = true; return *ce; } PIConfig::Branch PIConfig::Branch::getValues(const PIString & name) { Branch b; b.delim = delim; piForeach (Entry * i, *this) { if (i->isLeaf()) { if (i->_name.find(name) >= 0) b << i; } else { piForeach (Entry * j, i->_children) if (j->_name.find(name) >= 0) b << j; } } return b; } PIConfig::Branch PIConfig::Branch::getLeaves() { Branch b; b.delim = delim; piForeach (Entry * i, *this) if (i->isLeaf()) b << i; return b; } PIConfig::Branch PIConfig::Branch::getBranches() { Branch b; b.delim = delim; piForeach (Entry * i, *this) if (!i->isLeaf()) b << i; return b; } PIConfig::Branch & PIConfig::Branch::filter(const PIString & f) { for (int i = 0; i < size_s(); ++i) { if (at(i)->_name.find(f) < 0) { remove(i); --i; } } return *this; } bool PIConfig::Branch::entryExists(const Entry * e, const PIString & name) const { if (e->_children.isEmpty()) { if (e->_name == name) return true; else return false; } piForeachC (Entry * i, e->_children) if (entryExists(i, name)) return true; return false; } PIConfig::Entry & PIConfig::Entry::getValue(const PIString & vname, const PIString & def, bool * exist) { PIStringList tree = vname.split(delim); Entry * ce = this; piForeach (PIString & i, tree) { ce = ce->findChild(i); if (ce == 0) { _empty._name = vname; _empty._value = def; _empty.delim = delim; if (exist != 0) *exist = false; return _empty; } } if (exist != 0) *exist = true; return *ce; } PIConfig::Branch PIConfig::Entry::getValues(const PIString & vname) { Branch b; b.delim = delim; piForeach (Entry * i, _children) if (i->_name.find(vname) >= 0) b << i; return b; }; bool PIConfig::Entry::entryExists(const Entry * e, const PIString & name) const { if (e->_children.isEmpty()) { if (e->_name == name) return true; else return false; } piForeachC (Entry * i, e->_children) if (entryExists(i, name)) return true; return false; } PIConfig::PIConfig(const PIString & path, PIIODevice::DeviceMode mode): PIFile(path, mode) { delim = "."; root.delim = delim; empty.delim = delim; empty._parent = 0; if (!isOpened()) open(path, mode); parse(); } PIConfig::Entry & PIConfig::getValue(const PIString & vname, const PIString & def, bool * exist) { PIStringList tree = vname.split(delim); Entry * ce = &root; piForeach (PIString & i, tree) { ce = ce->findChild(i); if (ce == 0) { if (exist != 0) *exist = false; empty._name = vname; empty._value = def; empty.delim = delim; return empty; } } if (exist != 0) *exist = true; return *ce; } PIConfig::Branch PIConfig::getValues(const PIString & vname) { Branch b; b.delim = delim; piForeach (Entry * i, root._children) if (i->_name.find(vname) >= 0) b << i; return b; }; void PIConfig::addEntry(const PIString & name, const PIString & value, const PIString & type, bool write) { if (getValue(name)._parent != 0) return; bool toRoot = false; PIStringList tree = name.split(delim); PIString ename = tree.back(); tree.pop_back(); Entry * te, * ce, * entry = &root; if (tree.isEmpty()) toRoot = true; piForeach (PIString & i, tree) { te = entry->findChild(i); if (te == 0) { ce = new Entry(); ce->delim = delim; ce->_tab = entry->_tab; ce->_line = entry->_line; ce->_name = i; ce->_parent = entry; entry->_children << ce; entry = ce; } else entry = te; } PIConfig::Branch ch = entry->_children; ch.sort(PIConfig::Entry::compare); te = (entry->isLeaf() ? 0 : ch.back()); ce = new Entry(); ce->delim = delim; ce->_name = ename; ce->_value = value; ce->_type = type; if (te == 0) { ce->_tab = entry->_tab; if (toRoot) ce->_line = other.size_s() - 1; else ce->_line = entry->_line; } else { ce->_tab = te->_tab; if (toRoot) ce->_line = other.size_s() - 1; else { ch = entry->_parent->_children; ch.sort(PIConfig::Entry::compare); ce->_line = ch.back()->_line + 1; } } ce->_parent = entry; entry->_children << ce; other.insert(ce->_line, ""); Branch b = allLeaves(); bool found = false; for (int i = 0; i < b.size_s(); ++i) { if (found) { b[i]->_line++; continue; } if (b[i] == ce) { found = true; if (i > 0) if (b[i - 1]->_line == b[i]->_line) b[i - 1]->_line++; } } if (write) writeAll(); } void PIConfig::setValue(const PIString & name, const PIString & value, const PIString & type, bool write) { Entry & e(getValue(name)); if (&e == &empty) { addEntry(name, value, type, write); return; } e._value = value; e._type = type; if (write) writeAll(); } int PIConfig::entryIndex(const PIString & name) { PIStringList tree = name.split(delim); Entry * ce = &root; piForeach (PIString & i, tree) { ce = ce->findChild(i); if (ce == 0) return -1; } Branch b = allLeaves(); return allLeaves().indexOf(ce); } void PIConfig::setValue(uint number, const PIString & value, bool write) { Entry & e(entryByIndex(number)); if (&e == &empty) return; e._value = value; if (write) writeAll(); } void PIConfig::setName(uint number, const PIString & name, bool write) { Entry & e(entryByIndex(number)); if (&e == &empty) return; e._name = name; if (write) writeAll(); } void PIConfig::setType(uint number, const PIString & type, bool write) { Entry & e(entryByIndex(number)); if (&e == &empty) return; e._type = type; if (write) writeAll(); } void PIConfig::setComment(uint number, const PIString & comment, bool write) { Entry & e(entryByIndex(number)); if (&e == &empty) return; e._comment = comment; if (write) writeAll(); } void PIConfig::removeEntry(const PIString & name, bool write) { Entry & e(getValue(name)); if (&e == &empty) return; Branch b = allLeaves(); removeEntry(b, &e); if (write) writeAll(); } void PIConfig::removeEntry(uint number, bool write) { Entry & e(entryByIndex(number)); if (&e == &empty) return; Branch b = allLeaves(); removeEntry(b, &e); if (write) writeAll(); } void PIConfig::removeEntry(Branch & b, PIConfig::Entry * e) { bool leaf = true; if (e->isLeaf()) other.remove(e->_line); if (!e->isLeaf() && !e->_value.isEmpty()) { e->_value.clear(); leaf = false; } else { int cc = e->_children.size_s(); piForTimes (cc) removeEntry(b, e->_children.back()); } bool found = false; for (int i = 0; i < b.size_s(); ++i) { if (found) { b[i]->_line--; continue; } if (b[i] == e) found = true; } if (!leaf) return; e->_parent->_children.removeOne(e); b.removeOne(e); delete e; } PIString PIConfig::getPrefixFromLine(PIString line, bool * exists) { line.trim(); if (line.left(1) == "#") {if (exists) *exists = false; return PIString();} int ci = line.find("#"); if (ci >= 0) line.cutRight(line.size() - ci); if (line.find("=") >= 0) {if (exists) *exists = false; return PIString();} if (line.find("[") >= 0 && line.find("]") >= 0) { if (exists) *exists = true; return line.takeRange('[', ']').trim(); } if (exists) *exists = false; return PIString(); } void PIConfig::writeAll() { //cout << this << " write < " << size() << endl; PIFile::clear(); //*this << "1234567894132456798\n"; return; //writeEntry(&root); buildFullNames(&root); Branch b = allLeaves(); PIString prefix, tprefix; bool isPrefix; //for (int i = 0; i < b.size_s(); ++i) // cout << b[i]->_name << " = " << b[i]->_value << endl; int j = 0; for (int i = 0; i < other.size_s(); ++i) { //cout << j << endl; if (j >= 0 && j < b.size_s()) { if (b[j]->_line == i) { b[j]->buildLine(); *this << (b[j]->_all).cutLeft(prefix.size()) << '\n'; //cout << this << " " << b[j]->_all << endl; ++j; } else { *this << other[i]; tprefix = getPrefixFromLine(other[i], &isPrefix); if (isPrefix) { prefix = tprefix; if (!prefix.isEmpty()) prefix += delim; } if (i < other.size_s() - 1) *this << '\n'; //cout << this << " " << other[i] << endl; } } else { *this << other[i]; tprefix = getPrefixFromLine(other[i], &isPrefix); if (isPrefix) { prefix = tprefix; if (!prefix.isEmpty()) prefix += delim; } if (i < other.size_s() - 1) *this << '\n'; //cout << this << " " << other[i] << endl; } } flush(); readAll(); //cout << this << " write > " << size() << endl; } void PIConfig::readAll() { root.deleteBranch(); root.clear(); parse(); } bool PIConfig::entryExists(const Entry * e, const PIString & name) const { if (e->_children.isEmpty()) { if (e->_name == name) return true; else return false; } piForeachC (Entry * i, e->_children) if (entryExists(i, name)) return true; return false; } void PIConfig::parse() { PIString src, str, tab, comm, all, name, type, prefix, tprefix; PIStringList tree; Entry * entry, * te, * ce; int ind, sind; bool isNew, isPrefix; if (!isOpened()) return; seekToBegin(); other.clear(); lines = centry = 0; while (!isEnd()) { other.push_back(PIString()); src = str = readLine(); tprefix = getPrefixFromLine(src, &isPrefix); if (isPrefix) { prefix = tprefix; if (!prefix.isEmpty()) prefix += delim; } tab = str.left(str.find(str.trimmed().left(1))); str.trim(); //cout << endl << str << endl << endl; all = str; ind = str.find('='); if ((ind > 0) && !(str[0] == '#')) { sind = str.find('#'); if (sind > 0) { comm = str.right(str.length() - sind - 1).trimmed(); if (comm.length() > 0) type = comm[0]; else type = "s"; comm = comm.right(comm.length() - 1).trimmed(); str = str.left(sind); } else { type = "s"; comm = ""; } //name = str.left(ind).trimmed(); tree = (prefix + str.left(ind).trimmed()).split(delim); name = tree.back(); tree.pop_back(); entry = &root; piForeachC (PIString & i, tree) { te = entry->findChild(i); if (te == 0) { ce = new Entry(); ce->delim = delim; ce->_tab = tab; ce->_line = lines; ce->_name = i; ce->_parent = entry; entry->_children << ce; entry = ce; } else entry = te; } isNew = false; ce = entry->findChild(name); if (ce == 0) { ce = new Entry(); isNew = true; } ce->delim = delim; ce->_tab = tab; ce->_name = name; ce->_value = str.right(str.length() - ind - 1).trimmed(); ce->_type = type; ce->_comment = comm; ce->_line = lines; ce->_all = all; if (isNew) { ce->_parent = entry; entry->_children << ce; } } else other.back() = src; lines++; } setEntryDelim(&root, delim); buildFullNames(&root); }