tuchulcha  0.10.1
Graphical Workflow Configuration Editor
QParameterFile.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2011 Jens-Malte Gottfried
2 
3  This file is part of Tuchulcha.
4 
5  Tuchulcha is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  Tuchulcha is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU Lesser General Public License for more details.
14 
15  You should have received a copy of the GNU Lesser General Public License
16  along with Tuchulcha. If not, see <http://www.gnu.org/licenses/>.
17 */
25 #include "QParameterFile.h"
26 #include <QFile>
27 #include <QTextStream>
28 #include <QTextCodec>
29 
31 {
32  if(!fileName.isEmpty())
33  load(fileName);
34 }
35 
36 void QParameterFile::load(QString fileName, QString encoding) {
37  _content.clear();
38  _keys.clear();
39  QFile inFile(fileName);
40  if (inFile.open(QFile::ReadOnly|QIODevice::Text)) {
41  QString fileContent;
42  if (encoding.isEmpty()) {
43  fileContent = QString::fromLocal8Bit(inFile.readAll());
44  }
45  else {
46  QTextCodec* decoder = QTextCodec::codecForName(encoding.toLatin1());
47  if (!decoder) {
48  decoder = QTextCodec::codecForLocale();
49  QTextStream qerr(stderr,QIODevice::WriteOnly);
50  qerr << "Selected encoding: " << decoder->name() << endl;
51  }
52  fileContent = decoder->toUnicode(inFile.readAll());
53  }
54  QTextStream str(&fileContent,QIODevice::ReadOnly);
55  // regexp instances for content analysis
56  QRegExp lb("(.*)\\\\\\s*"); // handle line breaks
57  QRegExp cm("(.*\\S)?\\s*#.*"); // strip comments
58  QRegExp kv("(\\S+)\\s*(\\S.*)?"); // extract key/value
59  // line-based analyis
60  QString line;
61  uint lc = 0;
62  while (!str.atEnd()) {
63  // trim to avoid whitespace at begin/end
64  line = str.readLine().trimmed();
65  lc++;
66  // strip comments
67  if (cm.exactMatch(line)) {
68  line = cm.cap(1);
69  }
70  // allow line breaks with backslash
71  while (lb.exactMatch(line)) {
72  QString next = str.readLine().trimmed();
73  // strip comments
74  if (cm.exactMatch(next)) {
75  next = cm.cap(1);
76  }
77  line = lb.cap(1) + next;
78  lc++;
79  }
80  // skip empty lines
81  if (line.isEmpty()) {
82  continue;
83  }
84  // match key/value pair
85  if (kv.exactMatch(line)) {
86  QString par(kv.cap(1)), val(kv.cap(2));
87  Q_ASSERT(!par.isEmpty());
88  set(par,val);
89  }
90  else {
91  qDebug("%s:%d: malformed line",fileName.toLocal8Bit().constData(),lc);
92  }
93  }
94  }
95 }
96 
97 void QParameterFile::save(QString fileName) const {
98  QFile outFile(fileName);
99  if (outFile.exists()) {
100  QString backupFileName(fileName + "~");
101  QFile::remove(backupFileName);
102  outFile.copy(backupFileName);
103  }
104  if (outFile.open(QFile::WriteOnly|QIODevice::Truncate|QIODevice::Text)) {
105  QTextStream strm(&outFile);
106  strm << toStringList().join("\n") << endl;
107  }
108 }
109 
110 QStringList QParameterFile::toStringList() const {
111  QStringList res;
112  for (QStringList::const_iterator kIter=_keys.constBegin();
113  kIter!=_keys.constEnd(); kIter++) {
114  res << QString("%1\t\t%2")
115  .arg(*kIter).arg(_content.value(kIter->toLower()));
116  }
117  return res;
118 }
119 
120 bool QParameterFile::isSet(QString parameter) const {
121  return _content.contains(parameter.toLower());
122 }
123 
124 QString QParameterFile::get(QString parameter) const {
125  return isSet(parameter) ?
126  _content.value(parameter.toLower()) : QString::null;
127 }
128 
129 void QParameterFile::erase(QString parameter) {
130  // also delete corresponding priority value if existent
131  if (isSet(parameter + ".editorpriority")) {
132  _content.erase(_content.find((parameter + ".editorpriority").toLower()));
133  const int& pos = _keys.indexOf(QRegExp(parameter + ".editorpriority",
134  Qt::CaseInsensitive, QRegExp::FixedString));
135  Q_ASSERT(pos >= 0); Q_ASSERT(pos < _keys.size());
136  _keys.removeAt(pos);
137  }
138  if (isSet(parameter)) {
139  _content.erase(_content.find(parameter.toLower()));
140  const int& pos = _keys.indexOf(
141  QRegExp(parameter,Qt::CaseInsensitive,QRegExp::FixedString));
142  Q_ASSERT(pos >= 0); Q_ASSERT(pos < _keys.size());
143  _keys.removeAt(pos);
144  }
145 }
146 
147 void QParameterFile::set(QString parameter, QString value) {
148  if (parameter.isEmpty()) {
149  qDebug("Empty parameter given in set()");
150  }
151  else {
152  if (!isSet(parameter)) {
153  _keys.append(parameter);
154  }
155  if (value.isNull()) {
156  value = "";
157  }
158  _content.insert(parameter.toLower(),value);
159  }
160 }
161 
162 QStringList QParameterFile::getKeyList(QString beginsWith) const {
163  QStringList ret;
164  if (beginsWith.isEmpty()) {
165  ret = _keys;
166  }
167  else {
168  QStringListIterator ki(_keys);
169  while (ki.hasNext()) {
170  QString cur = ki.next();
171  if (cur.startsWith(beginsWith,Qt::CaseInsensitive)) {
172  ret << cur;
173  }
174  }
175  }
176  return ret;
177 }
178 
180  _content.clear();
181 }
182 
183 const QRegExp QParameterFile::prefixCheck("[a-z_][a-zA-Z0-9_\\-]*",Qt::CaseSensitive);
184 const QRegExp QParameterFile::postfixCheck("(?:\\.[a-zA-Z0-9_\\-]+)*",Qt::CaseSensitive);
185 const QRegExp QParameterFile::paramCheck("[a-z_][a-zA-Z0-9_\\-\\.]*",Qt::CaseSensitive);
186 const QRegExp QParameterFile::paramCheckSloppy("[\\w_\\-\\.@]*",Qt::CaseInsensitive);
187 
188 bool QParameterFile::rename(QString oldPrefix, QString newPrefix) {
189  // some sanity checks on the given parameters
190  oldPrefix=oldPrefix.section(".",0,0);
191  if (oldPrefix.isEmpty() || newPrefix.isEmpty() || oldPrefix == newPrefix) {
192  return false;
193  }
194  QRegExp check(prefixCheck);
195  if (!check.exactMatch(newPrefix)) {
196  qDebug("QParameterFile::rename: new prefix is no valid prefix: %s",
197  newPrefix.toLocal8Bit().constData());
198  return false;
199  }
200  // collect keys to rename
201  QStringList keysToRename;
202  QRegExp hasPrefix(oldPrefix+"(\\.[\\w\\.\\-_]+)?",Qt::CaseInsensitive);
203  for (QStringList::const_iterator kIter = _keys.constBegin();
204  kIter!=_keys.constEnd(); kIter++) {
205  if (hasPrefix.exactMatch(*kIter)) {
206  keysToRename << *kIter;
207  QString newKey = newPrefix+hasPrefix.cap(1);
208  if (_keys.contains(newKey,Qt::CaseInsensitive)
209  || _content.contains(newKey.toLower())) {
210  qDebug("new Key \"%s\" already exists! abort",
211  newKey.toLocal8Bit().constData());
212  return false;
213  }
214  }
215  }
216  if (keysToRename.isEmpty()) {
217  // nothing to do
218  qDebug("no keys found starting with prefix \"%s\"",
219  oldPrefix.toLocal8Bit().constData());
220  return false;
221  }
222  // rename the keys, this modifies _keys and _content,
223  // so it is done after iterating over it
224  for (QStringList::const_iterator rIter = keysToRename.constBegin();
225  rIter!=keysToRename.constEnd(); rIter++) {
226  QString cur = *rIter;
227  hasPrefix.exactMatch(cur);
228  Q_ASSERT(hasPrefix.matchedLength() >= 0);
229  int pos = _keys.indexOf(QRegExp(cur,Qt::CaseInsensitive));
230  Q_ASSERT(pos >= 0);
231  Q_ASSERT(_content.contains(cur.toLower()));
232  QString val = _content.value(cur.toLower());
233  _keys.removeAt(pos); // remove old key
234  Q_ASSERT(!_keys.contains(cur,Qt::CaseInsensitive));
235  _content.remove(cur.toLower());
236  Q_ASSERT(!_content.contains(cur.toLower()));
237  QString newKey = newPrefix+hasPrefix.cap(1);
238  _keys << newKey; // insert renamed one
239  _content.insert(newKey.toLower(),val);
240  }
241  // rename the parameters containing the old prefix,
242  // this keeps _keys and the keys of _content unchanged, so
243  // renaming can be done while iterating over _content
244  QRegExp paramMatch(
245  QString("((?:%1;)*)(%3)(%2)?((?:;%1)*)")
246  .arg(paramCheckSloppy.pattern())
247  .arg(postfixCheck.pattern())
248  .arg(oldPrefix),Qt::CaseInsensitive);
249  for (QHash<QString,QString>::iterator cIter=_content.begin();
250  cIter!=_content.end();cIter++) {
251  while (paramMatch.exactMatch(cIter.value())) {
252  QStringList mod = paramMatch.capturedTexts();
253  mod.removeFirst(); // full capture
254  Q_ASSERT(mod.length() == 4);
255  Q_ASSERT(oldPrefix.compare(mod[1],Qt::CaseInsensitive)==0);
256  Q_ASSERT(mod.join("") == cIter.value());
257  mod.replace(1,newPrefix);
258  cIter.value() = mod.join(QString());
259  }
260  if (cIter.value().contains(QRegExp(QString("^([^\\s]*;)?%1([\\.;][^\\s]*)?$")
261  .arg(oldPrefix),Qt::CaseInsensitive))) {
262  qWarning("line still contains old prefix (%s): %s",
263  oldPrefix.toLocal8Bit().constData(),
264  cIter.value().toLocal8Bit().constData());
265  qWarning("it does not match the pattern: %s",
266  paramMatch.pattern().toLocal8Bit().constData());
267  }
268  }
269  return true;
270 }
QString get(QString parameter) const
get parameter value
QParameterFile(QString fileName="")
default constructor
QStringList _keys
key array, stored in mixed case
bool isSet(QString parameter) const
check if a given parameter has been set
static const QRegExp paramCheckSloppy
full parameter name check (sloppy version, may handle buggy lines)
Declaration of class QParameterFile.
void load(QString fileName, QString encoding=QString())
load parameter file
void set(QString parameter, QString value="")
set parameter value
QHash< QString, QString > _content
value hash table, keys are stored in lowercase
void erase(QString parameter)
remove parameter from file
bool rename(QString oldPrefix, QString newPrefix)
rename prefix
static const QRegExp paramCheck
full parameter name check
void save(QString fileName) const
save to given plain text file
static const QRegExp postfixCheck
postfix check, everything that may follow a prefix (starting with ".")
QStringList getKeyList(QString beginsWith="") const
Look for parameters beginning with a given string.
QStringList toStringList() const
get StringList representation, like file content after save()
void clear()
clear content
static const QRegExp prefixCheck
instance name or prefix check regex