202 changes: 202 additions & 0 deletions mythtv/libs/libmythbase/stringutil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,205 @@ bool StringUtil::isValidUTF8(const QByteArray& data)

return true;
}

/**
This method chops the input a and b into pieces of
digits and non-digits (a1.05 becomes a | 1 | . | 05)
and compares these pieces of a and b to each other
(first with first, second with second, ...).
This is based on the natural sort order code code by Martin Pool
http://sourcefrog.net/projects/natsort/
Martin Pool agreed to license this under LGPL or GPL.
\todo FIXME: Using toLower() to implement case insensitive comparison is
sub-optimal, but is needed because we compare strings with
localeAwareCompare(), which does not know about case sensitivity.
A task has been filled for this in Qt Task Tracker with ID 205990.
http://trolltech.com/developer/task-tracker/index_html?method=entry&id=205990
Dead link. QCollator might be of relevance.
*/
int StringUtil::naturalCompare(const QString &_a, const QString &_b, Qt::CaseSensitivity caseSensitivity)
{
QString a;
QString b;

if (caseSensitivity == Qt::CaseSensitive)
{
a = _a;
b = _b;
}
else
{
a = _a.toLower();
b = _b.toLower();
}

const QChar* currA = a.unicode(); // iterator over a
const QChar* currB = b.unicode(); // iterator over b

if (currA == currB)
{
return 0;
}

while (!currA->isNull() && !currB->isNull())
{
const QChar* begSeqA = currA; // beginning of a new character sequence of a
const QChar* begSeqB = currB;

if (currA->unicode() == QChar::ObjectReplacementCharacter)
{
return 1;
}

if (currB->unicode() == QChar::ObjectReplacementCharacter)
{
return -1;
}

if (currA->unicode() == QChar::ReplacementCharacter)
{
return 1;
}

if (currB->unicode() == QChar::ReplacementCharacter)
{
return -1;
}

// find sequence of characters ending at the first non-character
while (!currA->isNull() && !currA->isDigit() && !currA->isPunct() &&
!currA->isSpace())
{
++currA;
}

while (!currB->isNull() && !currB->isDigit() && !currB->isPunct() &&
!currB->isSpace())
{
++currB;
}

// compare these sequences
const QString& subA(a.mid(begSeqA - a.unicode(), currA - begSeqA));
const QString& subB(b.mid(begSeqB - b.unicode(), currB - begSeqB));
const int cmp = QString::localeAwareCompare(subA, subB);

if (cmp != 0)
{
return cmp < 0 ? -1 : +1;
}

if (currA->isNull() || currB->isNull())
{
break;
}

// find sequence of characters ending at the first non-character
while ((currA->isPunct() || currA->isSpace()) &&
(currB->isPunct() || currB->isSpace()))
{
if (*currA != *currB)
{
return (*currA < *currB) ? -1 : +1;
}
++currA;
++currB;
if (currA->isNull() || currB->isNull())
{
break;
}
}

// now some digits follow...
if ((*currA == QLatin1Char('0')) || (*currB == QLatin1Char('0')))
{
// one digit-sequence starts with 0 -> assume we are in a fraction part
// do left aligned comparison (numbers are considered left aligned)
while (true)
{
if (!currA->isDigit() && !currB->isDigit())
{
break;
}
if (!currA->isDigit())
{
return +1;
}
if (!currB->isDigit())
{
return -1;
}
if (*currA < *currB)
{
return -1;
}
if (*currA > *currB)
{
return + 1;
}
++currA;
++currB;
}
}
else
{
// No digit-sequence starts with 0 -> assume we are looking at some integer
// do right aligned comparison.
//
// The longest run of digits wins. That aside, the greatest
// value wins, but we can't know that it will until we've scanned
// both numbers to know that they have the same magnitude.

bool isFirstRun = true;
int weight = 0;

while (true)
{
if (!currA->isDigit() && !currB->isDigit())
{
if (weight != 0)
{
return weight;
}
break;
}
if (!currA->isDigit())
{
if (isFirstRun)
{
return *currA < *currB ? -1 : +1;
}
return -1;
}
if (!currB->isDigit())
{
if (isFirstRun)
{
return *currA < *currB ? -1 : +1;
}
return +1;
}
if ((*currA < *currB) && (weight == 0))
{
weight = -1;
}
else if ((*currA > *currB) && (weight == 0))
{
weight = + 1;
}
++currA;
++currB;
isFirstRun = false;
}
}
}

if (currA->isNull() && currB->isNull())
{
return 0;
}

return currA->isNull() ? -1 : + 1;
}
4 changes: 4 additions & 0 deletions mythtv/libs/libmythbase/stringutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ inline QString bool_to_string(bool val)
return (val) ? QStringLiteral("true") : QStringLiteral("false");
}

MBASE_PUBLIC
int naturalCompare(const QString &_a, const QString &_b,
Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive);

} // namespace StringUtil

#endif // STRINGUTIL_H_
4 changes: 2 additions & 2 deletions mythtv/libs/libmythmetadata/dbaccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include "mythdb.h"
#include "cleanup.h"
#include "dbaccess.h"
#include "mythmiscutil.h"
#include "stringutil.h"

namespace
{
Expand Down Expand Up @@ -149,7 +149,7 @@ class SingleValueImp

virtual bool sort(const entry &lhs, const entry &rhs)
{
return naturalCompare(lhs.second, rhs.second) < 0;
return StringUtil::naturalCompare(lhs.second, rhs.second) < 0;
}

void cleanup()
Expand Down
7 changes: 4 additions & 3 deletions mythtv/libs/libmythmetadata/videometadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
#include <QRegularExpression>

#include "mythcorecontext.h"
#include "mythmiscutil.h"
#include "mythmiscutil.h" // for FileHash
#include "stringutil.h"
#include "mythcontext.h"
#include "mythdb.h"
#include "storagegroup.h"
Expand Down Expand Up @@ -401,10 +402,10 @@ class VideoMetadataImp
*/
bool VideoMetadataImp::sortBefore(const VideoMetadataImp *rhs) const
{
int ret = naturalCompare(m_sortTitle, rhs->m_sortTitle);
int ret = StringUtil::naturalCompare(m_sortTitle, rhs->m_sortTitle);
if (ret != 0)
return (ret == -1);
ret = naturalCompare(m_sortFilename, rhs->m_sortFilename);
ret = StringUtil::naturalCompare(m_sortFilename, rhs->m_sortFilename);
if (ret != 0)
return (ret == -1);
return (m_id < rhs->m_id);
Expand Down
6 changes: 3 additions & 3 deletions mythtv/libs/libmythtv/channelutil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#include "channelutil.h"
#include "mythdb.h"
#include "dvbtables.h"
#include "mythmiscutil.h"
#include "stringutil.h"
#include "HLSReader.h"

#define LOC QString("ChanUtil: ")
Expand Down Expand Up @@ -2159,7 +2159,7 @@ std::vector<uint> ChannelUtil::GetChanIDs(int sourceid, bool onlyVisible)

inline bool lt_callsign(const ChannelInfo &a, const ChannelInfo &b)
{
return naturalCompare(a.m_callSign, b.m_callSign) < 0;
return StringUtil::naturalCompare(a.m_callSign, b.m_callSign) < 0;
}

inline bool lt_smart(const ChannelInfo &a, const ChannelInfo &b)
Expand Down Expand Up @@ -2247,7 +2247,7 @@ inline bool lt_smart(const ChannelInfo &a, const ChannelInfo &b)
else
{
// neither of channels have a numeric channum
int cmp = naturalCompare(a.m_chanNum, b.m_chanNum);
int cmp = StringUtil::naturalCompare(a.m_chanNum, b.m_chanNum);
if (cmp)
return cmp < 0;
}
Expand Down
4 changes: 2 additions & 2 deletions mythtv/programs/mythfrontend/editvideometadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include "mythuiimageresults.h"
#include "mythuihelper.h"
#include "mythprogressdialog.h"
#include "mythmiscutil.h"
#include "stringutil.h"
#include "remoteutil.h"
#include "globals.h"
#include "dbaccess.h"
Expand Down Expand Up @@ -203,7 +203,7 @@ namespace
{
bool operator()(const T &lhs, const T &rhs)
{
return naturalCompare(lhs.second, rhs.second) < 0;
return StringUtil::naturalCompare(lhs.second, rhs.second) < 0;
}
};

Expand Down
6 changes: 3 additions & 3 deletions mythtv/programs/mythfrontend/prevreclist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
//MythTV
#include "mythcorecontext.h"
#include "mythdb.h"
#include "mythmiscutil.h"
#include "stringutil.h"
#include "xmlparsebase.h"
#include "recordinginfo.h"
#include "recordingrule.h"
Expand Down Expand Up @@ -172,15 +172,15 @@ static bool comp_sorttitle_lt(
{
QString a_st = a->GetSortTitle();
QString b_st = b->GetSortTitle();
return naturalCompare(a_st,b_st) < 0;
return StringUtil::naturalCompare(a_st,b_st) < 0;
}

static bool comp_sorttitle_lt_rev(
const ProgramInfo *a, const ProgramInfo *b)
{
QString a_st = a->GetSortTitle();
QString b_st = b->GetSortTitle();
return naturalCompare(b_st, a_st) < 0;
return StringUtil::naturalCompare(b_st, a_st) < 0;
}

static bool comp_sortdate_lt(
Expand Down
10 changes: 5 additions & 5 deletions mythtv/programs/mythfrontend/proglist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#include <utility>

// MythTV
#include "mythmiscutil.h"
#include "stringutil.h"
#include "scheduledrecording.h"
#include "mythuibuttonlist.h"
#include "mythuistatetype.h"
Expand Down Expand Up @@ -1015,9 +1015,9 @@ class plTitleSort : public plCompare
bool operator()(const ProgramInfo *a, const ProgramInfo *b) override // plCompare
{
if (a->GetSortTitle() != b->GetSortTitle())
return naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return StringUtil::naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
if (a->GetSortSubtitle() != b->GetSortSubtitle())
return naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;

if (a->GetRecordingStatus() == b->GetRecordingStatus())
return a->GetScheduledStartTime() < b->GetScheduledStartTime();
Expand Down Expand Up @@ -1050,9 +1050,9 @@ class plPrevTitleSort : public plCompare
bool operator()(const ProgramInfo *a, const ProgramInfo *b) override // plCompare
{
if (a->GetSortTitle() != b->GetSortTitle())
return naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return StringUtil::naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
if (a->GetSortSubtitle() != b->GetSortSubtitle())
return naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;

if (a->GetProgramID() != b->GetProgramID())
return a->GetProgramID() < b->GetProgramID();
Expand Down
42 changes: 21 additions & 21 deletions mythtv/programs/mythfrontend/programrecpriority.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// libmythbase
#include "mythdb.h"
#include "mythlogging.h"
#include "mythmiscutil.h"
#include "stringutil.h"
#include "remoteutil.h"

// libmythui
Expand Down Expand Up @@ -123,15 +123,15 @@ class TitleSort
if (a->GetSortTitle() != b->GetSortTitle())
{
if (m_reverse)
return naturalCompare(b->GetSortTitle(), a->GetSortTitle()) < 0;
return naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return StringUtil::naturalCompare(b->GetSortTitle(), a->GetSortTitle()) < 0;
return StringUtil::naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
}

if (a->GetSortSubtitle() != b->GetSortSubtitle())
{
if (m_reverse)
return naturalCompare(b->GetSortSubtitle(), a->GetSortSubtitle()) < 0;
return naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(b->GetSortSubtitle(), a->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
}

int finalA = a->GetRecordingPriority();
Expand Down Expand Up @@ -273,12 +273,12 @@ class ProgramCountSort
if (m_reverse)
{
if (a->GetSortTitle() != b->GetSortTitle())
return naturalCompare(b->GetSortTitle(), a->GetSortTitle()) < 0;
return naturalCompare(b->GetSortSubtitle(), a->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(b->GetSortTitle(), a->GetSortTitle()) < 0;
return StringUtil::naturalCompare(b->GetSortSubtitle(), a->GetSortSubtitle()) < 0;
}
if (a->GetSortTitle() != b->GetSortTitle())
return naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return StringUtil::naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
}

private:
Expand Down Expand Up @@ -315,12 +315,12 @@ class ProgramRecCountSort
if (m_reverse)
{
if (a->GetSortTitle() != b->GetSortTitle())
return naturalCompare(b->GetSortTitle(), a->GetSortTitle()) < 0;
return naturalCompare(b->GetSortSubtitle(), a->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(b->GetSortTitle(), a->GetSortTitle()) < 0;
return StringUtil::naturalCompare(b->GetSortSubtitle(), a->GetSortSubtitle()) < 0;
}
if (a->GetSortTitle() != b->GetSortTitle())
return naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return StringUtil::naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
}

private:
Expand Down Expand Up @@ -348,12 +348,12 @@ class ProgramLastRecordSort
if (m_reverse)
{
if (a->GetSortTitle() != b->GetSortTitle())
return naturalCompare(b->GetSortTitle(), a->GetSortTitle()) < 0;
return naturalCompare(b->GetSortSubtitle(), a->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(b->GetSortTitle(), a->GetSortTitle()) < 0;
return StringUtil::naturalCompare(b->GetSortSubtitle(), a->GetSortSubtitle()) < 0;
}
if (a->GetSortTitle() != b->GetSortTitle())
return naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return StringUtil::naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
}

private:
Expand Down Expand Up @@ -381,12 +381,12 @@ class ProgramAvgDelaySort
if (m_reverse)
{
if (a->GetSortTitle() != b->GetSortTitle())
return naturalCompare(b->GetSortTitle(), a->GetSortTitle()) < 0;
return naturalCompare(b->GetSortSubtitle(), a->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(b->GetSortTitle(), a->GetSortTitle()) < 0;
return StringUtil::naturalCompare(b->GetSortSubtitle(), a->GetSortSubtitle()) < 0;
}
if (a->GetSortTitle() != b->GetSortTitle())
return naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
return StringUtil::naturalCompare(a->GetSortTitle(), b->GetSortTitle()) < 0;
return StringUtil::naturalCompare(a->GetSortSubtitle(), b->GetSortSubtitle()) < 0;
}

private:
Expand Down
6 changes: 3 additions & 3 deletions mythtv/programs/mythfrontend/videofilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "mythuibutton.h"
#include "mythuitext.h"
#include "mythuitextedit.h"
#include "mythmiscutil.h"
#include "stringutil.h"
#include "globals.h"
#include "dbaccess.h"
#include "videometadatalistmanager.h"
Expand Down Expand Up @@ -325,8 +325,8 @@ bool VideoFilterSettings::meta_less_than(const VideoMetadata &lhs,
case kOrderByLength: return lhs.GetLength() < rhs.GetLength();
case kOrderByID: return lhs.GetID() < rhs.GetID();
case kOrderByFilename:
return naturalCompare(lhs.GetSortFilename(),
rhs.GetSortFilename()) < 0;
return StringUtil::naturalCompare(lhs.GetSortFilename(),
rhs.GetSortFilename()) < 0;
case kOrderBySeasonEp:
{
if ((lhs.GetSeason() == rhs.GetSeason())
Expand Down
4 changes: 2 additions & 2 deletions mythtv/programs/mythfrontend/videolist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

#include "mythcontext.h"
#include "mythdate.h"
#include "mythmiscutil.h"
#include "stringutil.h"

#include "mythgenerictree.h"
#include "videometadatalistmanager.h"
Expand Down Expand Up @@ -194,7 +194,7 @@ struct metadata_path_sort

static bool sort(const QString &lhs, const QString &rhs)
{
return naturalCompare(lhs, rhs) < 0;
return StringUtil::naturalCompare(lhs, rhs) < 0;
}
};

Expand Down