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