KDECore
kconfigini.cpp
Go to the documentation of this file.
00001 /* 00002 This file is part of the KDE libraries 00003 Copyright (c) 2006, 2007 Thomas Braxton <kde.braxton@gmail.com> 00004 Copyright (c) 1999 Preston Brown <pbrown@kde.org> 00005 Copyright (C) 1997-1999 Matthias Kalle Dalheimer (kalle@kde.org) 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Library General Public 00009 License as published by the Free Software Foundation; either 00010 version 2 of the License, or (at your option) any later version. 00011 00012 This library is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 Library General Public License for more details. 00016 00017 You should have received a copy of the GNU Library General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to 00019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00020 Boston, MA 02110-1301, USA. 00021 */ 00022 00023 #include "kconfigini_p.h" 00024 00025 #include <config.h> 00026 00027 #include <fcntl.h> 00028 00029 #include "kconfig.h" 00030 #include "kconfigbackend.h" 00031 #include "bufferfragment_p.h" 00032 #include "kconfigdata.h" 00033 #include <ksavefile.h> 00034 #include <kde_file.h> 00035 #include "kstandarddirs.h" 00036 00037 #include <qdatetime.h> 00038 #include <qdir.h> 00039 #include <qfile.h> 00040 #include <qfileinfo.h> 00041 #include <qdebug.h> 00042 #include <qmetaobject.h> 00043 #include <qregexp.h> 00044 00045 extern bool kde_kiosk_exception; 00046 00047 QString KConfigIniBackend::warningProlog(const QFile &file, int line) 00048 { 00049 return QString::fromLatin1("KConfigIni: In file %2, line %1: ") 00050 .arg(line).arg(file.fileName()); 00051 } 00052 00053 KConfigIniBackend::KConfigIniBackend() 00054 : KConfigBackend() 00055 { 00056 } 00057 00058 KConfigIniBackend::~KConfigIniBackend() 00059 { 00060 } 00061 00062 KConfigBackend::ParseInfo 00063 KConfigIniBackend::parseConfig(const QByteArray& currentLocale, KEntryMap& entryMap, 00064 ParseOptions options) 00065 { 00066 return parseConfig(currentLocale, entryMap, options, false); 00067 } 00068 00069 KConfigBackend::ParseInfo 00070 KConfigIniBackend::parseConfig(const QByteArray& currentLocale, KEntryMap& entryMap, 00071 ParseOptions options, bool merging) 00072 { 00073 if (filePath().isEmpty() || !QFile::exists(filePath())) 00074 return ParseOk; 00075 00076 bool bDefault = options&ParseDefaults; 00077 bool allowExecutableValues = options&ParseExpansions; 00078 00079 QByteArray currentGroup("<default>"); 00080 00081 QFile file(filePath()); 00082 if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) 00083 return ParseOpenError; 00084 00085 QList<QByteArray> immutableGroups; 00086 00087 bool fileOptionImmutable = false; 00088 bool groupOptionImmutable = false; 00089 bool groupSkip = false; 00090 00091 int lineNo = 0; 00092 // on systems using \r\n as end of line, \r will be taken care of by 00093 // trim() below 00094 QByteArray buffer = file.readAll(); 00095 BufferFragment contents(buffer.data(), buffer.size()); 00096 unsigned int len = contents.length(); 00097 unsigned int startOfLine = 0; 00098 00099 while (startOfLine < len) { 00100 BufferFragment line = contents.split('\n', &startOfLine); 00101 line.trim(); 00102 lineNo++; 00103 00104 // skip empty lines and lines beginning with '#' 00105 if (line.isEmpty() || line.at(0) == '#') 00106 continue; 00107 00108 if (line.at(0) == '[') { // found a group 00109 groupOptionImmutable = fileOptionImmutable; 00110 00111 QByteArray newGroup; 00112 int start = 1, end; 00113 do { 00114 end = start; 00115 for (;;) { 00116 if (end == line.length()) { 00117 qWarning() << warningProlog(file, lineNo) << "Invalid group header."; 00118 // XXX maybe reset the current group here? 00119 goto next_line; 00120 } 00121 if (line.at(end) == ']') 00122 break; 00123 end++; 00124 } 00125 if (end + 1 == line.length() && start + 2 == end && 00126 line.at(start) == '$' && line.at(start + 1) == 'i') 00127 { 00128 if (newGroup.isEmpty()) 00129 fileOptionImmutable = !kde_kiosk_exception; 00130 else 00131 groupOptionImmutable = !kde_kiosk_exception; 00132 } 00133 else { 00134 if (!newGroup.isEmpty()) 00135 newGroup += '\x1d'; 00136 BufferFragment namePart=line.mid(start, end - start); 00137 printableToString(&namePart, file, lineNo); 00138 newGroup += namePart.toByteArray(); 00139 } 00140 } while ((start = end + 2) <= line.length() && line.at(end + 1) == '['); 00141 currentGroup = newGroup; 00142 00143 groupSkip = entryMap.getEntryOption(currentGroup, 0, 0, KEntryMap::EntryImmutable); 00144 00145 if (groupSkip && !bDefault) 00146 continue; 00147 00148 if (groupOptionImmutable) 00149 // Do not make the groups immutable until the entries from 00150 // this file have been added. 00151 immutableGroups.append(currentGroup); 00152 } else { 00153 if (groupSkip && !bDefault) 00154 continue; // skip entry 00155 00156 BufferFragment aKey; 00157 int eqpos = line.indexOf('='); 00158 if (eqpos < 0) { 00159 aKey = line; 00160 line.clear(); 00161 } else { 00162 BufferFragment temp = line.left(eqpos); 00163 temp.trim(); 00164 aKey = temp; 00165 line.truncateLeft(eqpos + 1); 00166 } 00167 if (aKey.isEmpty()) { 00168 qWarning() << warningProlog(file, lineNo) << "Invalid entry (empty key)"; 00169 continue; 00170 } 00171 00172 KEntryMap::EntryOptions entryOptions=0; 00173 if (groupOptionImmutable) 00174 entryOptions |= KEntryMap::EntryImmutable; 00175 00176 BufferFragment locale; 00177 int start; 00178 while ((start = aKey.lastIndexOf('[')) >= 0) { 00179 int end = aKey.indexOf(']', start); 00180 if (end < 0) { 00181 qWarning() << warningProlog(file, lineNo) 00182 << "Invalid entry (missing ']')"; 00183 goto next_line; 00184 } else if (end > start + 1 && aKey.at(start + 1) == '$') { // found option(s) 00185 int i = start + 2; 00186 while (i < end) { 00187 switch (aKey.at(i)) { 00188 case 'i': 00189 if (!kde_kiosk_exception) 00190 entryOptions |= KEntryMap::EntryImmutable; 00191 break; 00192 case 'e': 00193 if (allowExecutableValues) 00194 entryOptions |= KEntryMap::EntryExpansion; 00195 break; 00196 case 'd': 00197 entryOptions |= KEntryMap::EntryDeleted; 00198 aKey = aKey.left(start); 00199 printableToString(&aKey, file, lineNo); 00200 entryMap.setEntry(currentGroup, aKey.toByteArray(), QByteArray(), entryOptions); 00201 goto next_line; 00202 default: 00203 break; 00204 } 00205 i++; 00206 } 00207 } else { // found a locale 00208 if (!locale.isNull()) { 00209 qWarning() << warningProlog(file, lineNo) 00210 << "Invalid entry (second locale!?)"; 00211 goto next_line; 00212 } 00213 00214 locale = aKey.mid(start + 1,end - start - 1); 00215 } 00216 aKey.truncate(start); 00217 } 00218 if (eqpos < 0) { // Do this here after [$d] was checked 00219 qWarning() << warningProlog(file, lineNo) << "Invalid entry (missing '=')"; 00220 continue; 00221 } 00222 printableToString(&aKey, file, lineNo); 00223 if (!locale.isEmpty()) { 00224 if (locale != currentLocale) { 00225 // backward compatibility. C == en_US 00226 if (locale.at(0) != 'C' || currentLocale != "en_US") { 00227 if (merging) 00228 entryOptions |= KEntryMap::EntryRawKey; 00229 else 00230 goto next_line; // skip this entry if we're not merging 00231 } 00232 } 00233 } 00234 00235 if (!(entryOptions & KEntryMap::EntryRawKey)) 00236 printableToString(&aKey, file, lineNo); 00237 00238 if (options&ParseGlobal) 00239 entryOptions |= KEntryMap::EntryGlobal; 00240 if (bDefault) 00241 entryOptions |= KEntryMap::EntryDefault; 00242 if (!locale.isNull()) 00243 entryOptions |= KEntryMap::EntryLocalized; 00244 printableToString(&line, file, lineNo); 00245 if (entryOptions & KEntryMap::EntryRawKey) { 00246 QByteArray rawKey; 00247 rawKey.reserve(aKey.length() + locale.length() + 2); 00248 rawKey.append(aKey.toVolatileByteArray()); 00249 rawKey.append('[').append(locale.toVolatileByteArray()).append(']'); 00250 entryMap.setEntry(currentGroup, rawKey, line.toByteArray(), entryOptions); 00251 } else { 00252 entryMap.setEntry(currentGroup, aKey.toByteArray(), line.toByteArray(), entryOptions); 00253 } 00254 } 00255 next_line: 00256 continue; 00257 } 00258 00259 // now make sure immutable groups are marked immutable 00260 foreach(const QByteArray& group, immutableGroups) { 00261 entryMap.setEntry(group, QByteArray(), QByteArray(), KEntryMap::EntryImmutable); 00262 } 00263 00264 return fileOptionImmutable ? ParseImmutable : ParseOk; 00265 } 00266 00267 void KConfigIniBackend::writeEntries(const QByteArray& locale, QFile& file, 00268 const KEntryMap& map, bool defaultGroup, bool &firstEntry) 00269 { 00270 QByteArray currentGroup; 00271 bool groupIsImmutable = false; 00272 const KEntryMapConstIterator end = map.constEnd(); 00273 for (KEntryMapConstIterator it = map.constBegin(); it != end; ++it) { 00274 const KEntryKey& key = it.key(); 00275 00276 // Either process the default group or all others 00277 if ((key.mGroup != "<default>") == defaultGroup) 00278 continue; // skip 00279 00280 // the only thing we care about groups is, is it immutable? 00281 if (key.mKey.isNull()) { 00282 groupIsImmutable = it->bImmutable; 00283 continue; // skip 00284 } 00285 00286 const KEntry& currentEntry = *it; 00287 if (!defaultGroup && currentGroup != key.mGroup) { 00288 if (!firstEntry) 00289 file.putChar('\n'); 00290 currentGroup = key.mGroup; 00291 for (int start = 0, end;; start = end + 1) { 00292 file.putChar('['); 00293 end = currentGroup.indexOf('\x1d', start); 00294 if (end < 0) { 00295 int cgl = currentGroup.length(); 00296 if (currentGroup.at(start) == '$' && cgl - start <= 10) { 00297 for (int i = start + 1; i < cgl; i++) { 00298 char c = currentGroup.at(i); 00299 if (c < 'a' || c > 'z') 00300 goto nope; 00301 } 00302 file.write("\\x24"); 00303 start++; 00304 } 00305 nope: 00306 file.write(stringToPrintable(currentGroup.mid(start), GroupString)); 00307 file.putChar(']'); 00308 if (groupIsImmutable) { 00309 file.write("[$i]", 4); 00310 } 00311 file.putChar('\n'); 00312 break; 00313 } else { 00314 file.write(stringToPrintable(currentGroup.mid(start, end - start), GroupString)); 00315 file.putChar(']'); 00316 } 00317 } 00318 } 00319 00320 firstEntry = false; 00321 // it is data for a group 00322 00323 if (key.bRaw) // unprocessed key with attached locale from merge 00324 file.write(key.mKey); 00325 else { 00326 file.write(stringToPrintable(key.mKey, KeyString)); // Key 00327 if (key.bLocal && locale != "C") { // 'C' locale == untranslated 00328 file.putChar('['); 00329 file.write(locale); // locale tag 00330 file.putChar(']'); 00331 } 00332 } 00333 if (currentEntry.bDeleted) { 00334 if (currentEntry.bImmutable) 00335 file.write("[$di]", 5); // Deleted + immutable 00336 else 00337 file.write("[$d]", 4); // Deleted 00338 } else { 00339 if (currentEntry.bImmutable || currentEntry.bExpand) { 00340 file.write("[$", 2); 00341 if (currentEntry.bImmutable) 00342 file.putChar('i'); 00343 if (currentEntry.bExpand) 00344 file.putChar('e'); 00345 file.putChar(']'); 00346 } 00347 file.putChar('='); 00348 file.write(stringToPrintable(currentEntry.mValue, ValueString)); 00349 } 00350 file.putChar('\n'); 00351 } 00352 } 00353 00354 void KConfigIniBackend::writeEntries(const QByteArray& locale, QFile& file, const KEntryMap& map) 00355 { 00356 bool firstEntry = true; 00357 00358 // write default group 00359 writeEntries(locale, file, map, true, firstEntry); 00360 00361 // write all other groups 00362 writeEntries(locale, file, map, false, firstEntry); 00363 } 00364 00365 bool KConfigIniBackend::writeConfig(const QByteArray& locale, KEntryMap& entryMap, 00366 WriteOptions options, const KComponentData &data) 00367 { 00368 Q_ASSERT(!filePath().isEmpty()); 00369 00370 KEntryMap writeMap; 00371 const bool bGlobal = options & WriteGlobal; 00372 00373 // First, reparse the file on disk, to merge our changes with the ones done by other apps 00374 { 00375 ParseOptions opts = ParseExpansions; 00376 if (bGlobal) 00377 opts |= ParseGlobal; 00378 ParseInfo info = parseConfig(locale, writeMap, opts, true); 00379 if (info != ParseOk) // either there was an error or the file became immutable 00380 return false; 00381 } 00382 00383 const KEntryMapIterator end = entryMap.end(); 00384 for (KEntryMapIterator it=entryMap.begin(); it != end; ++it) { 00385 if (!it.key().mKey.isEmpty() && !it->bDirty) // not dirty, doesn't overwrite entry in writeMap. skips default entries, too. 00386 continue; 00387 00388 const KEntryKey& key = it.key(); 00389 00390 // only write entries that have the same "globality" as the file 00391 if (it->bGlobal == bGlobal) { 00392 if (!it->bDeleted) { 00393 writeMap[key] = *it; 00394 } else { 00395 KEntryKey defaultKey = key; 00396 defaultKey.bDefault = true; 00397 if (!entryMap.contains(defaultKey)) { 00398 writeMap.remove(key); // remove the deleted entry if there is no default 00399 //qDebug() << "Detected as deleted=>removed:" << key.mGroup << key.mKey << "global=" << bGlobal; 00400 } else { 00401 writeMap[key] = *it; // otherwise write an explicitly deleted entry 00402 //qDebug() << "Detected as deleted=>[$d]:" << key.mGroup << key.mKey << "global=" << bGlobal; 00403 } 00404 } 00405 it->bDirty = false; 00406 } 00407 } 00408 00409 // now writeMap should contain only entries to be written 00410 // so write it out to disk 00411 00412 // check if file exists 00413 QFile::Permissions fileMode = QFile::ReadUser | QFile::WriteUser; 00414 bool createNew = true; 00415 00416 QFileInfo fi(filePath()); 00417 if (fi.exists()) 00418 { 00419 if (fi.ownerId() == ::getuid()) 00420 { 00421 // Preserve file mode if file exists and is owned by user. 00422 fileMode = fi.permissions(); 00423 } 00424 else 00425 { 00426 // File is not owned by user: 00427 // Don't create new file but write to existing file instead. 00428 createNew = false; 00429 } 00430 } 00431 00432 if (createNew) { 00433 KSaveFile file( filePath(), data ); 00434 if (!file.open()) { 00435 return false; 00436 } 00437 00438 file.setPermissions(fileMode); 00439 00440 file.setTextModeEnabled(true); // to get eol translation 00441 writeEntries(locale, file, writeMap); 00442 00443 if (!file.flush()) { 00444 // Couldn't write. Disk full? 00445 kWarning() << "Couldn't write" << filePath() << ". Disk full?"; 00446 file.abort(); 00447 return false; 00448 } 00449 00450 if (!file.size() && (fileMode == (QFile::ReadUser | QFile::WriteUser))) { 00451 // File is empty and doesn't have special permissions: delete it. 00452 file.abort(); 00453 00454 if (fi.exists()) { 00455 // also remove the old file in case it existed. this can happen 00456 // when we delete all the entries in an existing config file. 00457 // if we don't do this, then deletions and revertToDefault's 00458 // will mysteriously fail 00459 QFile::remove(filePath()); 00460 } 00461 } else { 00462 // Normal case: Close the file 00463 return file.finalize(); 00464 } 00465 } else { 00466 // Open existing file. *DON'T* create it if it suddenly does not exist! 00467 #ifdef Q_OS_UNIX 00468 int fd = KDE_open(QFile::encodeName(filePath()), O_WRONLY | O_TRUNC); 00469 if (fd < 0) { 00470 return false; 00471 } 00472 FILE *fp = KDE_fdopen(fd, "w"); 00473 if (!fp) { 00474 close(fd); 00475 return false; 00476 } 00477 QFile f; 00478 if (!f.open(fp, QIODevice::WriteOnly)) { 00479 fclose(fp); 00480 return false; 00481 } 00482 writeEntries(locale, f, writeMap); 00483 f.close(); 00484 fclose(fp); 00485 #else 00486 QFile f( filePath() ); 00487 // XXX This is broken - it DOES create the file if it is suddenly gone. 00488 if (!f.open( QIODevice::WriteOnly | QIODevice::Truncate )) { 00489 return false; 00490 } 00491 f.setTextModeEnabled(true); 00492 writeEntries(locale, f, writeMap); 00493 #endif 00494 } 00495 return true; 00496 } 00497 00498 bool KConfigIniBackend::isWritable() const 00499 { 00500 if (!filePath().isEmpty()) { 00501 if (KStandardDirs::checkAccess(filePath(), W_OK)) { 00502 return true; 00503 } 00504 // The check might have failed because any of the containing dirs 00505 // did not exist. If the file does not exist, check if the deepest 00506 // existing dir is writable. 00507 if (!QFileInfo(filePath()).exists()) { 00508 QDir dir = QFileInfo(filePath()).absolutePath(); 00509 while (!dir.exists()) { 00510 if (!dir.cdUp()) { 00511 return false; 00512 } 00513 } 00514 return QFileInfo(dir.absolutePath()).isWritable(); 00515 } 00516 } 00517 00518 return false; 00519 } 00520 00521 QString KConfigIniBackend::nonWritableErrorMessage() const 00522 { 00523 return i18n("Configuration file \"%1\" not writable.\n", filePath()); 00524 } 00525 00526 void KConfigIniBackend::createEnclosing() 00527 { 00528 const QString file = filePath(); 00529 if (file.isEmpty()) 00530 return; // nothing to do 00531 00532 // Create the containing dir, maybe it wasn't there 00533 QDir dir; 00534 dir.mkpath(QFileInfo(file).absolutePath()); 00535 } 00536 00537 void KConfigIniBackend::setFilePath(const QString& file) 00538 { 00539 if (file.isEmpty()) 00540 return; 00541 00542 Q_ASSERT(QDir::isAbsolutePath(file)); 00543 00544 const QFileInfo info(file); 00545 if (info.exists()) { 00546 setLocalFilePath(info.canonicalFilePath()); 00547 setLastModified(info.lastModified()); 00548 setSize(info.size()); 00549 } else { 00550 setLocalFilePath(file); 00551 setSize(0); 00552 QDateTime dummy; 00553 dummy.setTime_t(0); 00554 setLastModified(dummy); 00555 } 00556 } 00557 00558 KConfigBase::AccessMode KConfigIniBackend::accessMode() const 00559 { 00560 if (filePath().isEmpty()) 00561 return KConfigBase::NoAccess; 00562 00563 if (isWritable()) 00564 return KConfigBase::ReadWrite; 00565 00566 return KConfigBase::ReadOnly; 00567 } 00568 00569 bool KConfigIniBackend::lock(const KComponentData& componentData) 00570 { 00571 Q_ASSERT(!filePath().isEmpty()); 00572 00573 if (!lockFile) { 00574 lockFile = new KLockFile(filePath() + QLatin1String(".lock"), componentData); 00575 } 00576 00577 if (lockFile->lock() == KLockFile::LockStale) // attempt to break the lock 00578 lockFile->lock(KLockFile::ForceFlag); 00579 return lockFile->isLocked(); 00580 } 00581 00582 void KConfigIniBackend::unlock() 00583 { 00584 lockFile->unlock(); 00585 lockFile.clear(); 00586 } 00587 00588 bool KConfigIniBackend::isLocked() const 00589 { 00590 return lockFile && lockFile->isLocked(); 00591 } 00592 00593 QByteArray KConfigIniBackend::stringToPrintable(const QByteArray& aString, StringType type) 00594 { 00595 static const char nibbleLookup[] = { 00596 '0', '1', '2', '3', '4', '5', '6', '7', 00597 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 00598 }; 00599 00600 if (aString.isEmpty()) 00601 return aString; 00602 const int l = aString.length(); 00603 00604 QByteArray result; // Guesstimated that it's good to avoid data() initialization for a length of l*4 00605 result.resize(l * 4); // Maximum 4x as long as source string due to \x<ab> escape sequences 00606 register const char *s = aString.constData(); 00607 int i = 0; 00608 char *data = result.data(); 00609 char *start = data; 00610 00611 // Protect leading space 00612 if (s[0] == ' ' && type != GroupString) { 00613 *data++ = '\\'; 00614 *data++ = 's'; 00615 i++; 00616 } 00617 00618 for (; i < l; ++i/*, r++*/) { 00619 switch (s[i]) { 00620 default: 00621 // The \n, \t, \r cases (all < 32) are handled below; we can ignore them here 00622 if (((unsigned char)s[i]) < 32) 00623 goto doEscape; 00624 *data++ = s[i]; 00625 break; 00626 case '\n': 00627 *data++ = '\\'; 00628 *data++ = 'n'; 00629 break; 00630 case '\t': 00631 *data++ = '\\'; 00632 *data++ = 't'; 00633 break; 00634 case '\r': 00635 *data++ = '\\'; 00636 *data++ = 'r'; 00637 break; 00638 case '\\': 00639 *data++ = '\\'; 00640 *data++ = '\\'; 00641 break; 00642 case '=': 00643 if (type != KeyString) { 00644 *data++ = s[i]; 00645 break; 00646 } 00647 goto doEscape; 00648 case '[': 00649 case ']': 00650 // Above chars are OK to put in *value* strings as plaintext 00651 if (type == ValueString) { 00652 *data++ = s[i]; 00653 break; 00654 } 00655 doEscape: 00656 *data++ = '\\'; 00657 *data++ = 'x'; 00658 *data++ = nibbleLookup[((unsigned char)s[i]) >> 4]; 00659 *data++ = nibbleLookup[((unsigned char)s[i]) & 0x0f]; 00660 break; 00661 } 00662 } 00663 *data = 0; 00664 result.resize(data - start); 00665 00666 // Protect trailing space 00667 if (result.endsWith(' ') && type != GroupString) { 00668 result.replace(result.length() - 1, 1, "\\s"); 00669 } 00670 result.squeeze(); 00671 00672 return result; 00673 } 00674 00675 char KConfigIniBackend::charFromHex(const char *str, const QFile& file, int line) 00676 { 00677 unsigned char ret = 0; 00678 for (int i = 0; i < 2; i++) { 00679 ret <<= 4; 00680 quint8 c = quint8(str[i]); 00681 00682 if (c >= '0' && c <= '9') { 00683 ret |= c - '0'; 00684 } else if (c >= 'a' && c <= 'f') { 00685 ret |= c - 'a' + 0x0a; 00686 } else if (c >= 'A' && c <= 'F') { 00687 ret |= c - 'A' + 0x0a; 00688 } else { 00689 QByteArray e(str, 2); 00690 e.prepend("\\x"); 00691 qWarning() << warningProlog(file, line) << "Invalid hex character " << c 00692 << " in \\x<nn>-type escape sequence \"" << e.constData() << "\"."; 00693 return 'x'; 00694 } 00695 } 00696 return char(ret); 00697 } 00698 00699 void KConfigIniBackend::printableToString(BufferFragment* aString, const QFile& file, int line) 00700 { 00701 if (aString->isEmpty() || aString->indexOf('\\')==-1) 00702 return; 00703 aString->trim(); 00704 int l = aString->length(); 00705 char *r = aString->data(); 00706 char *str=r; 00707 00708 for(int i = 0; i < l; i++, r++) { 00709 if (str[i]!= '\\') { 00710 *r=str[i]; 00711 } else { 00712 // Probable escape sequence 00713 i++; 00714 if (i >= l) { // Line ends after backslash - stop. 00715 *r = '\\'; 00716 break; 00717 } 00718 00719 switch(str[i]) { 00720 case 's': 00721 *r = ' '; 00722 break; 00723 case 't': 00724 *r = '\t'; 00725 break; 00726 case 'n': 00727 *r = '\n'; 00728 break; 00729 case 'r': 00730 *r = '\r'; 00731 break; 00732 case '\\': 00733 *r = '\\'; 00734 break; 00735 case 'x': 00736 if (i + 2 < l) { 00737 *r = charFromHex(str + i + 1, file, line); 00738 i += 2; 00739 } else { 00740 *r = 'x'; 00741 i = l - 1; 00742 } 00743 break; 00744 default: 00745 *r = '\\'; 00746 qWarning() << warningProlog(file, line) 00747 << QString::fromLatin1("Invalid escape sequence \"\\%1\".").arg(str[i]); 00748 } 00749 } 00750 } 00751 aString->truncate(r - aString->constData()); 00752 }
KDE 4.6 API Reference