summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qdir.cpp
diff options
context:
space:
mode:
authorOliver Wolff <oliver.wolff@digia.com>2014-09-25 14:25:42 +0200
committerRainer Keller <rainer.keller@digia.com>2014-09-25 16:16:59 +0200
commiteedefa28bd66123f7787e989a4e4bccbb09f20bc (patch)
tree94fd75d1785c797297d1a25e4a0b178531fe7930 /src/corelib/io/qdir.cpp
parentcc3875c2e463be5cf126a18637295a0c56358eda (diff)
Refactored qt_normalizePathSegments
There were several use cases that did not work with the old implementation and it was not really readable. Task-number: QTBUG-3472 Task-number: QTBUG-40067 Task-number: QTBUG-23892 Change-Id: I1e038792dc54cdc6f8d9bb59d80b11dd3c56fac6 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com>
Diffstat (limited to 'src/corelib/io/qdir.cpp')
-rw-r--r--src/corelib/io/qdir.cpp194
1 files changed, 104 insertions, 90 deletions
diff --git a/src/corelib/io/qdir.cpp b/src/corelib/io/qdir.cpp
index 926c5e7d7b..a5e189a825 100644
--- a/src/corelib/io/qdir.cpp
+++ b/src/corelib/io/qdir.cpp
@@ -2018,106 +2018,120 @@ bool QDir::match(const QString &filter, const QString &fileName)
This method is shared with QUrl, so it doesn't deal with QDir::separator(),
nor does it remove the trailing slash, if any.
*/
-QString qt_normalizePathSegments(const QString &name, bool allowUncPaths)
+Q_AUTOTEST_EXPORT QString qt_normalizePathSegments(const QString &name, bool allowUncPaths)
{
- int used = 0, levels = 0;
const int len = name.length();
+
+ if (len == 0)
+ return name;
+
+ int i = len - 1;
QVarLengthArray<QChar> outVector(len);
+ int used = len;
QChar *out = outVector.data();
-
const QChar *p = name.unicode();
- for (int i = 0, last = -1, iwrite = 0; i < len; ++i) {
- if (p[i] == QLatin1Char('/')) {
- while (i+1 < len && p[i+1] == QLatin1Char('/')) {
- if (allowUncPaths && i == 0)
- break;
- i++;
- }
- bool eaten = false;
- if (i+1 < len && p[i+1] == QLatin1Char('.')) {
- int dotcount = 1;
- if (i+2 < len && p[i+2] == QLatin1Char('.'))
- dotcount++;
- if (i == len - dotcount - 1) {
- if (dotcount == 1) {
- break;
- } else if (levels) {
- if (last == -1) {
- for (int i2 = iwrite-1; i2 >= 0; i2--) {
- if (out[i2] == QLatin1Char('/')) {
- last = i2;
- break;
- }
- }
- }
- used -= iwrite - last - 1;
- break;
- }
- } else if (p[i+dotcount+1] == QLatin1Char('/')) {
- if (dotcount == 2 && levels) {
- if (last == -1 || iwrite - last == 1) {
- for (int i2 = (last == -1) ? (iwrite-1) : (last-1); i2 >= 0; i2--) {
- if (out[i2] == QLatin1Char('/')) {
- eaten = true;
- last = i2;
- break;
- }
- }
- } else {
- eaten = true;
- }
- if (eaten) {
- levels--;
- used -= iwrite - last;
- iwrite = last;
- last = -1;
- }
- } else if (dotcount == 2 && i > 0 && p[i - 1] != QLatin1Char('.')) {
- eaten = true;
- used -= iwrite - qMax(0, last);
- iwrite = qMax(0, last);
- last = -1;
- ++i;
- } else if (dotcount == 1) {
- eaten = true;
- }
- if (eaten)
- i += dotcount;
- } else {
- levels++;
- }
- } else if (last != -1 && iwrite - last == 1) {
-#if defined(Q_OS_WIN)
- eaten = (iwrite > 2);
-#else
- eaten = true;
+ const QChar *prefix = p;
+ int up = 0;
+
+ int prefixLength = 0;
+
+ if (allowUncPaths && len >= 2 && p[1].unicode() == '/' && p[0].unicode() == '/') {
+ // starts with double slash
+ prefixLength = 2;
+#ifdef Q_OS_WIN
+ } else if (len >= 2 && p[1].unicode() == ':') {
+ // remember the drive letter
+ prefixLength = (len > 2 && p[2].unicode() == '/') ? 3 : 2;
#endif
- last = -1;
- } else {
- levels++;
- }
- if (!eaten)
- last = i - (i - iwrite);
- else
- continue;
- } else if (!i && p[i] == QLatin1Char('.')) {
- int dotcount = 1;
- if (len >= 1 && p[1] == QLatin1Char('.'))
- dotcount++;
- if (len >= dotcount && p[dotcount] == QLatin1Char('/')) {
- if (dotcount == 1) {
- i++;
- while (i+1 < len-1 && p[i+1] == QLatin1Char('/'))
- i++;
- continue;
- }
+ } else if (p[0].unicode() == '/') {
+ prefixLength = 1;
+ }
+ p += prefixLength;
+ i -= prefixLength;
+
+ // replicate trailing slash (i > 0 checks for emptiness of input string p)
+ if (i > 0 && p[i].unicode() == '/') {
+ out[--used].unicode() = '/';
+ --i;
+ }
+
+ while (i >= 0) {
+ // remove trailing slashes
+ if (p[i].unicode() == '/') {
+ --i;
+ continue;
+ }
+
+ // remove current directory
+ if (p[i].unicode() == '.' && (i == 0 || p[i-1].unicode() == '/')) {
+ --i;
+ continue;
+ }
+
+ // detect up dir
+ if (i >= 1 && p[i].unicode() == '.' && p[i-1].unicode() == '.'
+ && (i == 1 || (i >= 2 && p[i-2].unicode() == '/'))) {
+ ++up;
+ i -= 2;
+ continue;
+ }
+
+ // prepend a slash before copying when not empty
+ if (!up && used != len && out[used].unicode() != '/')
+ out[--used] = QLatin1Char('/');
+
+ // skip or copy
+ while (i >= 0) {
+ if (p[i].unicode() == '/') { // do not copy slashes
+ --i;
+ break;
}
+
+ // actual copy
+ if (!up)
+ out[--used] = p[i];
+ --i;
+ }
+
+ // decrement up after copying/skipping
+ if (up)
+ --up;
+ }
+
+ // add remaining '..'
+ while (up) {
+ if (used != len && out[used].unicode() != '/') // is not empty and there isn't already a '/'
+ out[--used] = QLatin1Char('/');
+ out[--used] = QLatin1Char('.');
+ out[--used] = QLatin1Char('.');
+ --up;
+ }
+
+ bool isEmpty = used == len;
+
+ if (prefixLength) {
+ if (!isEmpty && out[used].unicode() == '/') {
+ // Eventhough there is a prefix the out string is a slash. This happens, if the input
+ // string only consists of a prefix followed by one or more slashes. Just skip the slash.
+ ++used;
+ }
+ for (int i = prefixLength - 1; i >= 0; --i)
+ out[--used] = prefix[i];
+ } else {
+ if (isEmpty) {
+ // After resolving the input path, the resulting string is empty (e.g. "foo/.."). Return
+ // a dot in that case.
+ out[--used] = QLatin1Char('.');
+ } else if (out[used].unicode() == '/') {
+ // After parsing the input string, out only contains a slash. That happens whenever all
+ // parts are resolved and there is a trailing slash ("./" or "foo/../" for example).
+ // Prepend a dot to have the correct return value.
+ out[--used] = QLatin1Char('.');
}
- out[iwrite++] = p[i];
- used++;
}
- QString ret = (used == len ? name : QString(out, used));
+ // If path was not modified return the original value
+ QString ret = (used == 0 ? name : QString(out + used, len - used));
return ret;
}