diff --git a/src/models/fieldcomparison.cpp b/src/models/fieldcomparison.cpp index f842c9b4..34bc61a8 100644 --- a/src/models/fieldcomparison.cpp +++ b/src/models/fieldcomparison.cpp @@ -1,105 +1,104 @@ /*************************************************************************** Copyright (C) 2007-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "fieldcomparison.h" #include "stringcomparison.h" #include "../field.h" #include "../collection.h" -#include "../document.h" #include "../images/imagefactory.h" #include "../images/image.h" #include "../tellico_debug.h" #include Tellico::FieldComparison* Tellico::FieldComparison::create(Data::FieldPtr field_) { if(!field_) { myWarning() << "No field for creating a field comparison"; return nullptr; } if(field_->type() == Data::Field::Image) { return new ImageComparison(field_); } else if(field_->type() == Data::Field::Choice) { return new ChoiceComparison(field_); } return new ValueComparison(field_, StringComparison::create(field_)); } Tellico::FieldComparison::FieldComparison(Data::FieldPtr field_) : m_field(field_) { } int Tellico::FieldComparison::compare(Data::EntryPtr entry1_, Data::EntryPtr entry2_) { return compare(entry1_->formattedField(m_field), entry2_->formattedField(m_field)); } Tellico::ValueComparison::ValueComparison(Data::FieldPtr field, StringComparison* comp) : FieldComparison(field) , m_stringComparison(comp) { Q_ASSERT(comp); } Tellico::ValueComparison::~ValueComparison() { delete m_stringComparison; } int Tellico::ValueComparison::compare(const QString& str1_, const QString& str2_) { return m_stringComparison->compare(str1_, str2_); } Tellico::ImageComparison::ImageComparison(Data::FieldPtr field) : FieldComparison(field) { } int Tellico::ImageComparison::compare(const QString& str1_, const QString& str2_) { if(str1_.isEmpty()) { if(str2_.isEmpty()) { return 0; } return -1; } if(str2_.isEmpty()) { return 1; } const Data::Image& image1 = ImageFactory::imageById(str1_); const Data::Image& image2 = ImageFactory::imageById(str2_); if(image1.isNull()) { if(image2.isNull()) { return 0; } return -1; } if(image2.isNull()) { return 1; } // large images come first return image1.width() - image2.width(); } Tellico::ChoiceComparison::ChoiceComparison(Data::FieldPtr field) : FieldComparison(field) { m_values = field->allowed(); } int Tellico::ChoiceComparison::compare(const QString& str1, const QString& str2) { return m_values.indexOf(str1) - m_values.indexOf(str2); } diff --git a/src/models/stringcomparison.cpp b/src/models/stringcomparison.cpp index c81744cf..73548cd3 100644 --- a/src/models/stringcomparison.cpp +++ b/src/models/stringcomparison.cpp @@ -1,225 +1,231 @@ /*************************************************************************** Copyright (C) 2007-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "stringcomparison.h" #include "../fieldformat.h" #include "../tellico_debug.h" #include namespace { int compareFloat(const QString& s1, const QString& s2) { bool ok1, ok2; float n1 = s1.toFloat(&ok1); if(!ok1) { return 0; } float n2 = s2.toFloat(&ok2); if(!ok2) { return 0; } return n1 > n2 ? 1 : (n1 < n2 ? -1 : 0); } } Tellico::StringComparison* Tellico::StringComparison::create(Data::FieldPtr field_) { if(!field_) { myWarning() << "No field for creating a string comparison"; return nullptr; } if(field_->type() == Data::Field::Number || field_->type() == Data::Field::Rating) { return new NumberComparison(); } else if(field_->type() == Data::Field::Bool) { return new BoolComparison(); } else if(field_->type() == Data::Field::Date || field_->formatType() == FieldFormat::FormatDate) { return new ISODateComparison(); } else if(field_->formatType() == FieldFormat::FormatTitle) { return new TitleComparison(); } else if(field_->property(QStringLiteral("lcc")) == QLatin1String("true") || field_->name() == QLatin1String("lcc")) { // allow LCC comparison if LCC property is set, or if the name is lcc return new LCCComparison(); } return new StringComparison(); } Tellico::StringComparison::StringComparison() { } int Tellico::StringComparison::compare(const QString& str1_, const QString& str2_) { return str1_.localeAwareCompare(str2_); } Tellico::BoolComparison::BoolComparison() : StringComparison() { } int Tellico::BoolComparison::compare(const QString& str1_, const QString& str2_) { return str1_.compare(str2_); } Tellico::TitleComparison::TitleComparison() : StringComparison() { } int Tellico::TitleComparison::compare(const QString& str1_, const QString& str2_) { const QString title1 = FieldFormat::sortKeyTitle(str1_).toLower(); const QString title2 = FieldFormat::sortKeyTitle(str2_).toLower(); return title1.localeAwareCompare(title2); } Tellico::NumberComparison::NumberComparison() : StringComparison() { } int Tellico::NumberComparison::compare(const QString& str1_, const QString& str2_) { bool ok1, ok2; float num1 = 0, num2 = 0; const QStringList values1 = FieldFormat::splitValue(str1_); const QStringList values2 = FieldFormat::splitValue(str2_); int index = 0; do { if((ok1 = index < values1.count())) { num1 = values1.at(index).toFloat(&ok1); } if((ok2 = index < values2.count())) { num2 = values2.at(index).toFloat(&ok2); } if(ok1 && ok2) { if(!qFuzzyCompare(num1, num2)) { const float ret = num1 - num2; // if abs(ret) < 0.5, we want to round up/down to -1 or 1 // so that comparing 0.2 to 0.4 yields 1, for example, and not 0 return ret < 0 ? qMin(-1, qRound(ret)) : qMax(1, qRound(ret)); } } ++index; } while(ok1 && ok2); if(ok1 && !ok2) { return 1; } else if(!ok1 && ok2) { return -1; } return 0; } // for details on the LCC comparison, see // http://www.mcgees.org/2001/08/08/sort-by-library-of-congress-call-number-in-perl/ // http://library.dts.edu/Pages/RM/Helps/lc_call.shtml Tellico::LCCComparison::LCCComparison() : StringComparison(), m_regexp(QLatin1String("^([A-Z]+)" "(\\d+(?:\\.\\d+)?)" "\\.?([A-Z]*)" "(\\d*)" "\\.?([A-Z]*)" "(\\d*)" "(?: (.+))?")) { } int Tellico::LCCComparison::compare(const QString& str1_, const QString& str2_) { + if(str1_.isEmpty()) { + return str2_.isEmpty() ? 0 : -1; + } + if(str2_.isEmpty()) { + return 1; + } // myDebug() << str1_ << " to " << str2_; int pos1 = m_regexp.indexIn(str1_); const QStringList cap1 = m_regexp.capturedTexts(); int pos2 = m_regexp.indexIn(str2_); const QStringList cap2 = m_regexp.capturedTexts(); if(pos1 > -1 && pos2 > -1) { int res = compareLCC(cap1, cap2); // myLog() << "...result = " << res; return res; } else { if(pos1 == -1) { myDebug() << "no regexp match:" << str1_; } if(pos2 == -1) { myDebug() << "no regexp match:" << str2_; } } return StringComparison::compare(str1_, str2_); } int Tellico::LCCComparison::compareLCC(const QStringList& cap1, const QStringList& cap2) const { // the first item in the list is the full match, so start array index at 1 int res = 0; return (res = cap1[1].compare(cap2[1])) != 0 ? res : (res = compareFloat(cap1[2], cap2[2])) != 0 ? res : (res = cap1[3].compare(cap2[3])) != 0 ? res : (res = compareFloat(QLatin1String("0.") + cap1[4], QLatin1String("0.") + cap2[4])) != 0 ? res : (res = cap1[5].compare(cap2[5])) != 0 ? res : (res = compareFloat(QLatin1String("0.") + cap1[6], QLatin1String("0.") + cap2[6])) != 0 ? res : (res = cap1[7].compare(cap2[7])) != 0 ? res : 0; } Tellico::ISODateComparison::ISODateComparison() : StringComparison() { } int Tellico::ISODateComparison::compare(const QString& str1, const QString& str2) { if(str1.isEmpty()) { return str2.isEmpty() ? 0 : -1; } if(str2.isEmpty()) { // str1 is not return 1; } // modelled after Field::formatDate() // so dates would sort as expected without padding month and day with zero // and accounting for "current year - 1 - 1" default scheme QStringList dlist1 = str1.split(QLatin1Char('-'), QString::KeepEmptyParts); bool ok = true; int y1 = dlist1.count() > 0 ? dlist1[0].toInt(&ok) : QDate::currentDate().year(); if(!ok) { y1 = QDate::currentDate().year(); } int m1 = dlist1.count() > 1 ? dlist1[1].toInt(&ok) : 1; if(!ok) { m1 = 1; } int d1 = dlist1.count() > 2 ? dlist1[2].toInt(&ok) : 1; if(!ok) { d1 = 1; } QDate date1(y1, m1, d1); QStringList dlist2 = str2.split(QLatin1Char('-'), QString::KeepEmptyParts); int y2 = dlist2.count() > 0 ? dlist2[0].toInt(&ok) : QDate::currentDate().year(); if(!ok) { y2 = QDate::currentDate().year(); } int m2 = dlist2.count() > 1 ? dlist2[1].toInt(&ok) : 1; if(!ok) { m2 = 1; } int d2 = dlist2.count() > 2 ? dlist2[2].toInt(&ok) : 1; if(!ok) { d2 = 1; } QDate date2(y2, m2, d2); if(date1 < date2) { return -1; } else if(date1 > date2) { return 1; } return 0; } diff --git a/src/tests/comparisontest.cpp b/src/tests/comparisontest.cpp index 494111dc..e7e2fdb4 100644 --- a/src/tests/comparisontest.cpp +++ b/src/tests/comparisontest.cpp @@ -1,68 +1,90 @@ /*************************************************************************** Copyright (C) 2010 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #undef QT_NO_CAST_FROM_ASCII #include "comparisontest.h" #include "../models/stringcomparison.h" #include QTEST_APPLESS_MAIN( ComparisonTest ) void ComparisonTest::testNumber() { QFETCH(QString, string1); QFETCH(QString, string2); QFETCH(int, res); Tellico::NumberComparison comp; QCOMPARE(comp.compare(string1, string2), res); } void ComparisonTest::testNumber_data() { QTest::addColumn("string1"); QTest::addColumn("string2"); QTest::addColumn("res"); QTest::newRow("null") << QString() << QString() << 0; - QTest::newRow("empty") << QString() << QString() << 0; QTest::newRow("< 0") << QString() << QStringLiteral("0") << -1; QTest::newRow("> 0") << QStringLiteral("0") << QString() << 1; QTest::newRow("=1 1") << QStringLiteral("1") << QStringLiteral("1") << 0; QTest::newRow("< 1") << QStringLiteral("0") << QStringLiteral("1") << -1; QTest::newRow("> 1") << QStringLiteral("1") << QStringLiteral("0") << 1; QTest::newRow("> -1") << QStringLiteral("0") << QStringLiteral("-1") << 1; QTest::newRow("< -1") << QStringLiteral("-1") << QStringLiteral("0") << -1; QTest::newRow("> 10") << QStringLiteral("10") << QStringLiteral("5") << 5; QTest::newRow("< 10") << QStringLiteral("5") << QStringLiteral("10") << -5; QTest::newRow("multiple1") << QStringLiteral("1; 2") << QStringLiteral("2") << -1; QTest::newRow("multiple2") << QStringLiteral("3; 2") << QStringLiteral("2") << 1; QTest::newRow("multiple3") << QStringLiteral("2") << QStringLiteral("2; 3") << -1; QTest::newRow("multiple4") << QStringLiteral("1; 2") << QStringLiteral("1; 3") << -1; QTest::newRow("float1") << QStringLiteral("5.1") << QStringLiteral("6.9") << -2; QTest::newRow("float2") << QStringLiteral("5.1") << QStringLiteral("5.2") << -1; QTest::newRow("float3") << QStringLiteral("5.2") << QStringLiteral("5.1") << 1; QTest::newRow("float4") << QStringLiteral("5.1") << QStringLiteral("5.1") << 0; } + +void ComparisonTest::testLCC() { + QFETCH(QString, string1); + QFETCH(QString, string2); + QFETCH(int, res); + + Tellico::LCCComparison comp; + + QCOMPARE(comp.compare(string1, string2), res); +} + +void ComparisonTest::testLCC_data() { + QTest::addColumn("string1"); + QTest::addColumn("string2"); + QTest::addColumn("res"); + + QTest::newRow("null") << QString() << QString() << 0; + QTest::newRow("empty1") << QString() << QStringLiteral("B") << -1; + QTest::newRow("empty2") << QStringLiteral("B") << QString() << 1; + QTest::newRow("test1") << QStringLiteral("BX932 .C53 1993") << QStringLiteral("BX2230.3") << -1; + QTest::newRow("test2") << QStringLiteral("BX932 .C53 1993") << QStringLiteral("BX2380 .R67 2002") << -1; + QTest::newRow("test3") << QStringLiteral("AE25 E3 2002") << QStringLiteral("AE5 E333 2003") << 1; +} diff --git a/src/tests/comparisontest.h b/src/tests/comparisontest.h index ad51f5d7..4d02a7e9 100644 --- a/src/tests/comparisontest.h +++ b/src/tests/comparisontest.h @@ -1,38 +1,40 @@ /*************************************************************************** Copyright (C) 2010 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #ifndef COMPARISONTEST_H #define COMPARISONTEST_H #include class ComparisonTest : public QObject { Q_OBJECT private Q_SLOTS: void testNumber(); void testNumber_data(); + void testLCC(); + void testLCC_data(); }; #endif