aboutsummaryrefslogtreecommitdiff
path: root/src/core/kconfigini.cpp
diff options
context:
space:
mode:
authorJenkins CI <null@kde.org>2013-12-18 00:45:18 +0000
committerJenkins CI <null@kde.org>2013-12-18 00:45:18 +0000
commit867e7a50e6396338ab4fe9aa22ad141e4cd344d2 (patch)
tree1d6f8d6c912fa04dc268b5580bcfe696fa538743 /src/core/kconfigini.cpp
parentc38b88497a833e482e6892b72c8f52adec6de857 (diff)
downloadkconfig-867e7a50e6396338ab4fe9aa22ad141e4cd344d2.tar.gz
kconfig-867e7a50e6396338ab4fe9aa22ad141e4cd344d2.tar.bz2
Move kconfig code to the root directory.
Diffstat (limited to 'src/core/kconfigini.cpp')
-rw-r--r--src/core/kconfigini.cpp770
1 files changed, 770 insertions, 0 deletions
diff --git a/src/core/kconfigini.cpp b/src/core/kconfigini.cpp
new file mode 100644
index 00000000..f44b2c39
--- /dev/null
+++ b/src/core/kconfigini.cpp
@@ -0,0 +1,770 @@
+/*
+ This file is part of the KDE libraries
+ Copyright (c) 2006, 2007 Thomas Braxton <kde.braxton@gmail.com>
+ Copyright (c) 1999 Preston Brown <pbrown@kde.org>
+ Copyright (C) 1997-1999 Matthias Kalle Dalheimer (kalle@kde.org)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kconfigini_p.h"
+
+#include "kconfig.h"
+#include "kconfigbackend.h"
+#include "bufferfragment_p.h"
+#include "kconfigdata.h"
+
+#include <qsavefile.h>
+#include <qlockfile.h>
+#include <qdatetime.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qdebug.h>
+#include <qplatformdefs.h>
+
+#ifndef Q_OS_WIN
+#include <unistd.h> // getuid, close
+#endif
+#include <sys/types.h> // uid_t
+#include <fcntl.h> // open
+
+KCONFIGCORE_EXPORT bool kde_kiosk_exception = false; // flag to disable kiosk restrictions
+
+QString KConfigIniBackend::warningProlog(const QFile &file, int line)
+{
+ return QString::fromLatin1("KConfigIni: In file %2, line %1: ")
+ .arg(line).arg(file.fileName());
+}
+
+KConfigIniBackend::KConfigIniBackend()
+ : KConfigBackend(), lockFile(NULL)
+{
+}
+
+KConfigIniBackend::~KConfigIniBackend()
+{
+}
+
+KConfigBackend::ParseInfo
+ KConfigIniBackend::parseConfig(const QByteArray& currentLocale, KEntryMap& entryMap,
+ ParseOptions options)
+{
+ return parseConfig(currentLocale, entryMap, options, false);
+}
+
+// merging==true is the merging that happens at the beginning of writeConfig:
+// merge changes in the on-disk file with the changes in the KConfig object.
+KConfigBackend::ParseInfo
+KConfigIniBackend::parseConfig(const QByteArray& currentLocale, KEntryMap& entryMap,
+ ParseOptions options, bool merging)
+{
+ if (filePath().isEmpty() || !QFile::exists(filePath()))
+ return ParseOk;
+
+ bool bDefault = options&ParseDefaults;
+ bool allowExecutableValues = options&ParseExpansions;
+
+ QByteArray currentGroup("<default>");
+
+ QFile file(filePath());
+ if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
+ return ParseOpenError;
+
+ QList<QByteArray> immutableGroups;
+
+ bool fileOptionImmutable = false;
+ bool groupOptionImmutable = false;
+ bool groupSkip = false;
+
+ int lineNo = 0;
+ // on systems using \r\n as end of line, \r will be taken care of by
+ // trim() below
+ QByteArray buffer = file.readAll();
+ BufferFragment contents(buffer.data(), buffer.size());
+ unsigned int len = contents.length();
+ unsigned int startOfLine = 0;
+
+ while (startOfLine < len) {
+ BufferFragment line = contents.split('\n', &startOfLine);
+ line.trim();
+ lineNo++;
+
+ // skip empty lines and lines beginning with '#'
+ if (line.isEmpty() || line.at(0) == '#')
+ continue;
+
+ if (line.at(0) == '[') { // found a group
+ groupOptionImmutable = fileOptionImmutable;
+
+ QByteArray newGroup;
+ int start = 1, end;
+ do {
+ end = start;
+ for (;;) {
+ if (end == line.length()) {
+ qWarning() << warningProlog(file, lineNo) << "Invalid group header.";
+ // XXX maybe reset the current group here?
+ goto next_line;
+ }
+ if (line.at(end) == ']')
+ break;
+ end++;
+ }
+ if (end + 1 == line.length() && start + 2 == end &&
+ line.at(start) == '$' && line.at(start + 1) == 'i')
+ {
+ if (newGroup.isEmpty())
+ fileOptionImmutable = !kde_kiosk_exception;
+ else
+ groupOptionImmutable = !kde_kiosk_exception;
+ }
+ else {
+ if (!newGroup.isEmpty())
+ newGroup += '\x1d';
+ BufferFragment namePart=line.mid(start, end - start);
+ printableToString(&namePart, file, lineNo);
+ newGroup += namePart.toByteArray();
+ }
+ } while ((start = end + 2) <= line.length() && line.at(end + 1) == '[');
+ currentGroup = newGroup;
+
+ groupSkip = entryMap.getEntryOption(currentGroup, 0, 0, KEntryMap::EntryImmutable);
+
+ if (groupSkip && !bDefault)
+ continue;
+
+ if (groupOptionImmutable)
+ // Do not make the groups immutable until the entries from
+ // this file have been added.
+ immutableGroups.append(currentGroup);
+ } else {
+ if (groupSkip && !bDefault)
+ continue; // skip entry
+
+ BufferFragment aKey;
+ int eqpos = line.indexOf('=');
+ if (eqpos < 0) {
+ aKey = line;
+ line.clear();
+ } else {
+ BufferFragment temp = line.left(eqpos);
+ temp.trim();
+ aKey = temp;
+ line.truncateLeft(eqpos + 1);
+ }
+ if (aKey.isEmpty()) {
+ qWarning() << warningProlog(file, lineNo) << "Invalid entry (empty key)";
+ continue;
+ }
+
+ KEntryMap::EntryOptions entryOptions=0;
+ if (groupOptionImmutable)
+ entryOptions |= KEntryMap::EntryImmutable;
+
+ BufferFragment locale;
+ int start;
+ while ((start = aKey.lastIndexOf('[')) >= 0) {
+ int end = aKey.indexOf(']', start);
+ if (end < 0) {
+ qWarning() << warningProlog(file, lineNo)
+ << "Invalid entry (missing ']')";
+ goto next_line;
+ } else if (end > start + 1 && aKey.at(start + 1) == '$') { // found option(s)
+ int i = start + 2;
+ while (i < end) {
+ switch (aKey.at(i)) {
+ case 'i':
+ if (!kde_kiosk_exception)
+ entryOptions |= KEntryMap::EntryImmutable;
+ break;
+ case 'e':
+ if (allowExecutableValues)
+ entryOptions |= KEntryMap::EntryExpansion;
+ break;
+ case 'd':
+ entryOptions |= KEntryMap::EntryDeleted;
+ aKey = aKey.left(start);
+ printableToString(&aKey, file, lineNo);
+ entryMap.setEntry(currentGroup, aKey.toByteArray(), QByteArray(), entryOptions);
+ goto next_line;
+ default:
+ break;
+ }
+ i++;
+ }
+ } else { // found a locale
+ if (!locale.isNull()) {
+ qWarning() << warningProlog(file, lineNo)
+ << "Invalid entry (second locale!?)";
+ goto next_line;
+ }
+
+ locale = aKey.mid(start + 1,end - start - 1);
+ }
+ aKey.truncate(start);
+ }
+ if (eqpos < 0) { // Do this here after [$d] was checked
+ qWarning() << warningProlog(file, lineNo) << "Invalid entry (missing '=')";
+ continue;
+ }
+ printableToString(&aKey, file, lineNo);
+ if (!locale.isEmpty()) {
+ if (locale != currentLocale) {
+ // backward compatibility. C == en_US
+ if (locale.at(0) != 'C' || currentLocale != "en_US") {
+ if (merging)
+ entryOptions |= KEntryMap::EntryRawKey;
+ else
+ goto next_line; // skip this entry if we're not merging
+ }
+ }
+ }
+
+ if (!(entryOptions & KEntryMap::EntryRawKey))
+ printableToString(&aKey, file, lineNo);
+
+ if (options&ParseGlobal)
+ entryOptions |= KEntryMap::EntryGlobal;
+ if (bDefault)
+ entryOptions |= KEntryMap::EntryDefault;
+ if (!locale.isNull())
+ entryOptions |= KEntryMap::EntryLocalized;
+ printableToString(&line, file, lineNo);
+ if (entryOptions & KEntryMap::EntryRawKey) {
+ QByteArray rawKey;
+ rawKey.reserve(aKey.length() + locale.length() + 2);
+ rawKey.append(aKey.toVolatileByteArray());
+ rawKey.append('[').append(locale.toVolatileByteArray()).append(']');
+ entryMap.setEntry(currentGroup, rawKey, line.toByteArray(), entryOptions);
+ } else {
+ entryMap.setEntry(currentGroup, aKey.toByteArray(), line.toByteArray(), entryOptions);
+ }
+ }
+next_line:
+ continue;
+ }
+
+ // now make sure immutable groups are marked immutable
+ Q_FOREACH(const QByteArray& group, immutableGroups) {
+ entryMap.setEntry(group, QByteArray(), QByteArray(), KEntryMap::EntryImmutable);
+ }
+
+ return fileOptionImmutable ? ParseImmutable : ParseOk;
+}
+
+void KConfigIniBackend::writeEntries(const QByteArray& locale, QIODevice& file,
+ const KEntryMap& map, bool defaultGroup, bool &firstEntry)
+{
+ QByteArray currentGroup;
+ bool groupIsImmutable = false;
+ const KEntryMapConstIterator end = map.constEnd();
+ for (KEntryMapConstIterator it = map.constBegin(); it != end; ++it) {
+ const KEntryKey& key = it.key();
+
+ // Either process the default group or all others
+ if ((key.mGroup != "<default>") == defaultGroup)
+ continue; // skip
+
+ // the only thing we care about groups is, is it immutable?
+ if (key.mKey.isNull()) {
+ groupIsImmutable = it->bImmutable;
+ continue; // skip
+ }
+
+ const KEntry& currentEntry = *it;
+ if (!defaultGroup && currentGroup != key.mGroup) {
+ if (!firstEntry)
+ file.putChar('\n');
+ currentGroup = key.mGroup;
+ for (int start = 0, end;; start = end + 1) {
+ file.putChar('[');
+ end = currentGroup.indexOf('\x1d', start);
+ if (end < 0) {
+ int cgl = currentGroup.length();
+ if (currentGroup.at(start) == '$' && cgl - start <= 10) {
+ for (int i = start + 1; i < cgl; i++) {
+ char c = currentGroup.at(i);
+ if (c < 'a' || c > 'z')
+ goto nope;
+ }
+ file.write("\\x24");
+ start++;
+ }
+ nope:
+ file.write(stringToPrintable(currentGroup.mid(start), GroupString));
+ file.putChar(']');
+ if (groupIsImmutable) {
+ file.write("[$i]", 4);
+ }
+ file.putChar('\n');
+ break;
+ } else {
+ file.write(stringToPrintable(currentGroup.mid(start, end - start), GroupString));
+ file.putChar(']');
+ }
+ }
+ }
+
+ firstEntry = false;
+ // it is data for a group
+
+ if (key.bRaw) // unprocessed key with attached locale from merge
+ file.write(key.mKey);
+ else {
+ file.write(stringToPrintable(key.mKey, KeyString)); // Key
+ if (key.bLocal && locale != "C") { // 'C' locale == untranslated
+ file.putChar('[');
+ file.write(locale); // locale tag
+ file.putChar(']');
+ }
+ }
+ if (currentEntry.bDeleted) {
+ if (currentEntry.bImmutable)
+ file.write("[$di]", 5); // Deleted + immutable
+ else
+ file.write("[$d]", 4); // Deleted
+ } else {
+ if (currentEntry.bImmutable || currentEntry.bExpand) {
+ file.write("[$", 2);
+ if (currentEntry.bImmutable)
+ file.putChar('i');
+ if (currentEntry.bExpand)
+ file.putChar('e');
+ file.putChar(']');
+ }
+ file.putChar('=');
+ file.write(stringToPrintable(currentEntry.mValue, ValueString));
+ }
+ file.putChar('\n');
+ }
+}
+
+void KConfigIniBackend::writeEntries(const QByteArray& locale, QIODevice& file, const KEntryMap& map)
+{
+ bool firstEntry = true;
+
+ // write default group
+ writeEntries(locale, file, map, true, firstEntry);
+
+ // write all other groups
+ writeEntries(locale, file, map, false, firstEntry);
+}
+
+bool KConfigIniBackend::writeConfig(const QByteArray& locale, KEntryMap& entryMap,
+ WriteOptions options)
+{
+ Q_ASSERT(!filePath().isEmpty());
+
+ KEntryMap writeMap;
+ const bool bGlobal = options & WriteGlobal;
+
+ // First, reparse the file on disk, to merge our changes with the ones done by other apps
+ // Store the result into writeMap.
+ {
+ ParseOptions opts = ParseExpansions;
+ if (bGlobal)
+ opts |= ParseGlobal;
+ ParseInfo info = parseConfig(locale, writeMap, opts, true);
+ if (info != ParseOk) // either there was an error or the file became immutable
+ return false;
+ }
+
+ const KEntryMapIterator end = entryMap.end();
+ for (KEntryMapIterator it=entryMap.begin(); it != end; ++it) {
+ if (!it.key().mKey.isEmpty() && !it->bDirty) // not dirty, doesn't overwrite entry in writeMap. skips default entries, too.
+ continue;
+
+ const KEntryKey& key = it.key();
+
+ // only write entries that have the same "globality" as the file
+ if (it->bGlobal == bGlobal) {
+ if (it->bReverted) {
+ writeMap.remove(key);
+ } else if (!it->bDeleted) {
+ writeMap[key] = *it;
+ } else {
+ KEntryKey defaultKey = key;
+ defaultKey.bDefault = true;
+ if (!entryMap.contains(defaultKey)) {
+ writeMap.remove(key); // remove the deleted entry if there is no default
+ //qDebug() << "Detected as deleted=>removed:" << key.mGroup << key.mKey << "global=" << bGlobal;
+ } else {
+ writeMap[key] = *it; // otherwise write an explicitly deleted entry
+ //qDebug() << "Detected as deleted=>[$d]:" << key.mGroup << key.mKey << "global=" << bGlobal;
+ }
+ }
+ it->bDirty = false;
+ }
+ }
+
+ // now writeMap should contain only entries to be written
+ // so write it out to disk
+
+ // check if file exists
+ QFile::Permissions fileMode = QFile::ReadUser | QFile::WriteUser;
+ bool createNew = true;
+
+ QFileInfo fi(filePath());
+ if (fi.exists())
+ {
+#ifdef Q_OS_WIN
+ //TODO: getuid does not exist on windows, use GetSecurityInfo and GetTokenInformation instead
+ createNew = false;
+#else
+ if (fi.ownerId() == ::getuid())
+ {
+ // Preserve file mode if file exists and is owned by user.
+ fileMode = fi.permissions();
+ }
+ else
+ {
+ // File is not owned by user:
+ // Don't create new file but write to existing file instead.
+ createNew = false;
+ }
+#endif
+ }
+
+ if (createNew) {
+ QSaveFile file(filePath());
+ if (!file.open(QIODevice::WriteOnly)) {
+ return false;
+ }
+
+ file.setTextModeEnabled(true); // to get eol translation
+ writeEntries(locale, file, writeMap);
+
+ if (!file.size() && (fileMode == (QFile::ReadUser | QFile::WriteUser))) {
+ // File is empty and doesn't have special permissions: delete it.
+ file.cancelWriting();
+
+ if (fi.exists()) {
+ // also remove the old file in case it existed. this can happen
+ // when we delete all the entries in an existing config file.
+ // if we don't do this, then deletions and revertToDefault's
+ // will mysteriously fail
+ QFile::remove(filePath());
+ }
+ } else {
+ // Normal case: Close the file
+ if (file.commit()) {
+ QFile::setPermissions(filePath(), fileMode);
+ return true;
+ }
+ // Couldn't write. Disk full?
+ qWarning() << "Couldn't write" << filePath() << ". Disk full?";
+ return false;
+ }
+ } else {
+ // Open existing file. *DON'T* create it if it suddenly does not exist!
+#ifdef Q_OS_UNIX
+ int fd = QT_OPEN(QFile::encodeName(filePath()).constData(), O_WRONLY | O_TRUNC);
+ if (fd < 0) {
+ return false;
+ }
+ FILE *fp = ::fdopen(fd, "w");
+ if (!fp) {
+ QT_CLOSE(fd);
+ return false;
+ }
+ QFile f;
+ if (!f.open(fp, QIODevice::WriteOnly)) {
+ fclose(fp);
+ return false;
+ }
+ writeEntries(locale, f, writeMap);
+ f.close();
+ fclose(fp);
+#else
+ QFile f( filePath() );
+ // XXX This is broken - it DOES create the file if it is suddenly gone.
+ if (!f.open( QIODevice::WriteOnly | QIODevice::Truncate )) {
+ return false;
+ }
+ f.setTextModeEnabled(true);
+ writeEntries(locale, f, writeMap);
+#endif
+ }
+ return true;
+}
+
+
+bool KConfigIniBackend::isWritable() const
+{
+ const QString filePath = this->filePath();
+ if (!filePath.isEmpty()) {
+ QFileInfo file(filePath);
+ if (!file.exists()) {
+ // If the file does not exist, check if the deepest
+ // existing dir is writable.
+ QFileInfo dir(file.absolutePath());
+ while (!dir.exists()) {
+ QString parent = dir.absolutePath(); // Go up. Can't use cdUp() on non-existing dirs.
+ if (parent == dir.filePath()) {
+ // no parent
+ return false;
+ }
+ dir.setFile(parent);
+ }
+ return dir.isDir() && dir.isWritable();
+ } else {
+ return file.isWritable();
+ }
+ }
+
+ return false;
+}
+
+QString KConfigIniBackend::nonWritableErrorMessage() const
+{
+ return tr("Configuration file \"%1\" not writable.\n").arg(filePath());
+}
+
+void KConfigIniBackend::createEnclosing()
+{
+ const QString file = filePath();
+ if (file.isEmpty())
+ return; // nothing to do
+
+ // Create the containing dir, maybe it wasn't there
+ QDir dir;
+ dir.mkpath(QFileInfo(file).absolutePath());
+}
+
+void KConfigIniBackend::setFilePath(const QString& file)
+{
+ if (file.isEmpty())
+ return;
+
+ Q_ASSERT(QDir::isAbsolutePath(file));
+
+ const QFileInfo info(file);
+ if (info.exists()) {
+ setLocalFilePath(info.canonicalFilePath());
+ setLastModified(info.lastModified());
+ setSize(info.size());
+ } else {
+ setLocalFilePath(file);
+ setSize(0);
+ QDateTime dummy;
+ dummy.setTime_t(0);
+ setLastModified(dummy);
+ }
+}
+
+KConfigBase::AccessMode KConfigIniBackend::accessMode() const
+{
+ if (filePath().isEmpty())
+ return KConfigBase::NoAccess;
+
+ if (isWritable())
+ return KConfigBase::ReadWrite;
+
+ return KConfigBase::ReadOnly;
+}
+
+bool KConfigIniBackend::lock()
+{
+ Q_ASSERT(!filePath().isEmpty());
+
+ if (!lockFile) {
+ lockFile = new QLockFile(filePath() + QLatin1String(".lock"));
+ }
+
+ // This is a workaround for current QLockFilePrivate::tryLock_sys
+ // which might crash calling qAppName() if sync() is called after
+ // the QCoreApplication instance is gone. It might be the case with
+ // KSharedConfig instances cleanup.
+ if (!lockFile->tryLock(lockFile->staleLockTime())) {
+ lockFile->removeStaleLockFile();
+ lockFile->lock();
+ }
+ return lockFile->isLocked();
+}
+
+void KConfigIniBackend::unlock()
+{
+ lockFile->unlock();
+ delete lockFile;
+ lockFile = NULL;
+}
+
+bool KConfigIniBackend::isLocked() const
+{
+ return lockFile && lockFile->isLocked();
+}
+
+QByteArray KConfigIniBackend::stringToPrintable(const QByteArray& aString, StringType type)
+{
+ static const char nibbleLookup[] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ if (aString.isEmpty())
+ return aString;
+ const int l = aString.length();
+
+ QByteArray result; // Guesstimated that it's good to avoid data() initialization for a length of l*4
+ result.resize(l * 4); // Maximum 4x as long as source string due to \x<ab> escape sequences
+ register const char *s = aString.constData();
+ int i = 0;
+ char *data = result.data();
+ char *start = data;
+
+ // Protect leading space
+ if (s[0] == ' ' && type != GroupString) {
+ *data++ = '\\';
+ *data++ = 's';
+ i++;
+ }
+
+ for (; i < l; ++i/*, r++*/) {
+ switch (s[i]) {
+ default:
+ // The \n, \t, \r cases (all < 32) are handled below; we can ignore them here
+ if (((unsigned char)s[i]) < 32)
+ goto doEscape;
+ *data++ = s[i];
+ break;
+ case '\n':
+ *data++ = '\\';
+ *data++ = 'n';
+ break;
+ case '\t':
+ *data++ = '\\';
+ *data++ = 't';
+ break;
+ case '\r':
+ *data++ = '\\';
+ *data++ = 'r';
+ break;
+ case '\\':
+ *data++ = '\\';
+ *data++ = '\\';
+ break;
+ case '=':
+ if (type != KeyString) {
+ *data++ = s[i];
+ break;
+ }
+ goto doEscape;
+ case '[':
+ case ']':
+ // Above chars are OK to put in *value* strings as plaintext
+ if (type == ValueString) {
+ *data++ = s[i];
+ break;
+ }
+ doEscape:
+ *data++ = '\\';
+ *data++ = 'x';
+ *data++ = nibbleLookup[((unsigned char)s[i]) >> 4];
+ *data++ = nibbleLookup[((unsigned char)s[i]) & 0x0f];
+ break;
+ }
+ }
+ *data = 0;
+ result.resize(data - start);
+
+ // Protect trailing space
+ if (result.endsWith(' ') && type != GroupString) {
+ result.replace(result.length() - 1, 1, "\\s");
+ }
+ result.squeeze();
+
+ return result;
+}
+
+char KConfigIniBackend::charFromHex(const char *str, const QFile& file, int line)
+{
+ unsigned char ret = 0;
+ for (int i = 0; i < 2; i++) {
+ ret <<= 4;
+ quint8 c = quint8(str[i]);
+
+ if (c >= '0' && c <= '9') {
+ ret |= c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ ret |= c - 'a' + 0x0a;
+ } else if (c >= 'A' && c <= 'F') {
+ ret |= c - 'A' + 0x0a;
+ } else {
+ QByteArray e(str, 2);
+ e.prepend("\\x");
+ qWarning() << warningProlog(file, line) << "Invalid hex character " << c
+ << " in \\x<nn>-type escape sequence \"" << e.constData() << "\".";
+ return 'x';
+ }
+ }
+ return char(ret);
+}
+
+void KConfigIniBackend::printableToString(BufferFragment* aString, const QFile& file, int line)
+{
+ if (aString->isEmpty() || aString->indexOf('\\')==-1)
+ return;
+ aString->trim();
+ int l = aString->length();
+ char *r = aString->data();
+ char *str=r;
+
+ for(int i = 0; i < l; i++, r++) {
+ if (str[i]!= '\\') {
+ *r=str[i];
+ } else {
+ // Probable escape sequence
+ i++;
+ if (i >= l) { // Line ends after backslash - stop.
+ *r = '\\';
+ break;
+ }
+
+ switch(str[i]) {
+ case 's':
+ *r = ' ';
+ break;
+ case 't':
+ *r = '\t';
+ break;
+ case 'n':
+ *r = '\n';
+ break;
+ case 'r':
+ *r = '\r';
+ break;
+ case '\\':
+ *r = '\\';
+ break;
+ case 'x':
+ if (i + 2 < l) {
+ *r = charFromHex(str + i + 1, file, line);
+ i += 2;
+ } else {
+ *r = 'x';
+ i = l - 1;
+ }
+ break;
+ default:
+ *r = '\\';
+ qWarning() << warningProlog(file, line)
+ << QString::fromLatin1("Invalid escape sequence \"\\%1\".").arg(str[i]);
+ }
+ }
+ }
+ aString->truncate(r - aString->constData());
+}