File indexing completed on 2024-05-12 05:10:16

0001 /***************************************************************************
0002     Copyright (C) 2002-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "isbnvalidator.h"
0026 #include "upcvalidator.h"
0027 
0028 #include <QStringList>
0029 #include <QRegularExpression>
0030 
0031 using Tellico::ISBNValidator;
0032 
0033 //static
0034 QString ISBNValidator::isbn10(QString isbn13) {
0035   QString original = isbn13;
0036   isbn13.remove(QLatin1Char('-'));
0037   if(isbn13.length() == 10) {
0038     fixup10(isbn13);
0039     return isbn13;
0040   }
0041   if(!isbn13.startsWith(QStringLiteral("978"))) {
0042     return original;
0043   }
0044   if(isbn13.length() < 13) {
0045     fixup10(isbn13);
0046     return isbn13;
0047   }
0048   isbn13 = isbn13.mid(3);
0049   // remove checksum
0050   isbn13.truncate(isbn13.length()-1);
0051   // add new checksum
0052   isbn13 += checkSum10(isbn13);
0053   fixup10(isbn13);
0054   return isbn13;
0055 }
0056 
0057 QString ISBNValidator::isbn13(QString isbn10) {
0058   isbn10.remove(QLatin1Char('-'));
0059   if(isbn10.length() < 10) {
0060     return isbn10;
0061   }
0062   if(isbn10.length() > 10) {
0063     // assume it's already an isbn13 value
0064     fixup13(isbn10);
0065     return isbn10;
0066   }
0067   // remove checksum
0068   isbn10.truncate(isbn10.length()-1);
0069   // begins with 978
0070   isbn10.prepend(QStringLiteral("978"));
0071   // add new checksum
0072   isbn10 += checkSum13(isbn10);
0073   fixup13(isbn10);
0074   return isbn10;
0075 }
0076 
0077 QStringList ISBNValidator::listDifference(const QStringList& list1_, const QStringList& list2_) {
0078   ISBNComparison comp;
0079 
0080   QStringList notFound;
0081   foreach(const QString& value1, list1_) {
0082     bool found = false;
0083     foreach(const QString& value2, list2_) {
0084       if(comp(value1, value2)) {
0085         found = true;
0086         break;
0087       }
0088     }
0089     if(!found) {
0090       notFound.append(value1);
0091     }
0092   }
0093   return notFound;
0094 }
0095 
0096 QString ISBNValidator::cleanValue(QString isbn) {
0097   static const QRegularExpression badChars(QStringLiteral("[^xX0123456789]"));
0098   isbn.remove(badChars);
0099   return isbn;
0100 }
0101 
0102 ISBNValidator::ISBNValidator(QObject* parent_)
0103     : QValidator(parent_) {
0104 }
0105 
0106 QValidator::State ISBNValidator::validate(QString& input_, int& pos_) const {
0107   // check if it's a cuecat first
0108   State catState = CueCat::decode(input_);
0109   if(catState != Invalid) {
0110     pos_ = input_.length();
0111     return catState;
0112   }
0113 
0114   if(input_.startsWith(QStringLiteral("978")) ||
0115      input_.startsWith(QStringLiteral("979"))) {
0116     return validate13(input_, pos_);
0117   } else {
0118     return validate10(input_, pos_);
0119   }
0120 }
0121 
0122 void ISBNValidator::fixup(QString& input_) const {
0123   staticFixup(input_);
0124 }
0125 
0126 void ISBNValidator::staticFixup(QString& input_) {
0127   if((input_.startsWith(QStringLiteral("978"))
0128        || input_.startsWith(QStringLiteral("979")))
0129      && input_.count(QRegularExpression(QStringLiteral("\\d"))) > 10) {
0130     fixup13(input_);
0131   } else {
0132     fixup10(input_);
0133   }
0134 }
0135 
0136 QValidator::State ISBNValidator::validate10(QString& input_, int& pos_) const {
0137   int len = input_.length();
0138 /*
0139   // Don't do this since the hyphens may be in the wrong place, can't put that in a regexp
0140   if(isbn.exactMatch(input_) // put the exactMatch() first since I use matchedLength() later
0141      && (len == 12 || len == 13)
0142      && input_[len-1] == checkSum(input_)) {
0143     return QValidator::Acceptable;
0144   }
0145 */
0146   // two easy invalid cases are too many hyphens and the 'X' not in the last position
0147   if(input_.count(QLatin1Char('-')) > 3
0148      || input_.count(QLatin1Char('X'), Qt::CaseInsensitive) > 1
0149      || (input_.indexOf(QLatin1Char('X'), 0, Qt::CaseInsensitive) != -1 && input_[len-1].toUpper() != QLatin1Char('X'))) {
0150     return QValidator::Invalid;
0151   }
0152 
0153   // remember if the cursor is at the end
0154   bool atEnd = (pos_ == static_cast<int>(len));
0155 
0156   // fix the case where the user attempts to delete a character from a non-checksum
0157   // position; the solution is to delete the checksum, but only if it's X
0158   if(!atEnd && input_[len-1].toUpper() == QLatin1Char('X')) {
0159     input_.truncate(len-1);
0160     --len;
0161   }
0162 
0163   // fix the case where the user attempts to delete the checksum; the
0164   // solution is to delete the last digit as well
0165   static const QRegularExpression digit(QStringLiteral("\\d"));
0166   if(atEnd && input_.count(digit) == 9 && input_[len-1] == QLatin1Char('-')) {
0167     input_.truncate(len-2);
0168     pos_ -= 2;
0169   }
0170 
0171   // now fixup the hyphens and maybe add a checksum
0172   fixup10(input_);
0173   len = input_.length(); // might have changed in fixup()
0174   if(atEnd) {
0175     pos_ = len;
0176   }
0177 
0178   // first check to see if it's a "perfect" ISBN
0179   // A perfect ISBN has 9 digits plus either an 'X' or another digit
0180   // A perfect ISBN may have 2 or 3 hyphens
0181   // The final digit or 'X' is the correct check sum
0182   static const QRegularExpression isbn(QStringLiteral("^(\\d-?){9,11}-[\\dX]$"));
0183   if(isbn.match(input_).hasMatch() && (len == 12 || len == 13)) {
0184     return QValidator::Acceptable;
0185   } else {
0186     return QValidator::Intermediate;
0187   }
0188 }
0189 
0190 QValidator::State ISBNValidator::validate13(QString& input_, int& pos_) const {
0191   int len = input_.length();
0192 
0193   const uint countX = input_.count(QLatin1Char('X'), Qt::CaseInsensitive);
0194   // two easy invalid cases are too many hyphens or 'X'
0195   if(input_.count(QLatin1Char('-')) > 4 || countX > 1) {
0196     return QValidator::Invalid;
0197   }
0198 
0199   // now, it's not certain that we're getting a EAN-13,
0200   // it could be a ISBN-10 from Nigeria or Indonesia
0201   if(countX > 0 && (len > 13 || input_[len-1].toUpper() != QLatin1Char('X'))) {
0202     return QValidator::Invalid;
0203   }
0204 
0205   // remember if the cursor is at the end
0206   bool atEnd = (pos_ == len);
0207 
0208   // fix the case where the user attempts to delete a character from a non-checksum
0209   // position; the solution is to delete the checksum, but only if it's X
0210   if(!atEnd && input_[len-1].toUpper() == QLatin1Char('X')) {
0211     input_.truncate(len-1);
0212     --len;
0213   }
0214 
0215   // fix the case where the user attempts to delete the checksum; the
0216   // solution is to delete the last digit as well
0217   static const QRegularExpression digit(QStringLiteral("\\d"));
0218   const uint countN = input_.count(digit);
0219   if(atEnd && (countN == 12 || countN == 9) && input_[len-1] == QLatin1Char('-')) {
0220     input_.truncate(len-2);
0221     pos_ -= 2;
0222   }
0223 
0224   // now fixup the hyphens and maybe add a checksum
0225   if(countN > 10) {
0226     fixup13(input_);
0227   } else {
0228     fixup10(input_);
0229   }
0230 
0231   len = input_.length(); // might have changed in fixup()
0232   if(atEnd) {
0233     pos_ = len;
0234   }
0235 
0236   // first check to see if it's a "perfect" ISBN13
0237   // A perfect ISBN13 has 13 digits
0238   // A perfect ISBN13 may have 3 or 4 hyphens
0239   // The final digit is the correct check sum
0240   static const QRegularExpression isbn(QStringLiteral("^(\\d-?){13,17}$"));
0241   if(isbn.match(input_).hasMatch()) {
0242     return QValidator::Acceptable;
0243   } else {
0244     return QValidator::Intermediate;
0245   }
0246 }
0247 
0248 void ISBNValidator::fixup10(QString& input_) {
0249   if(input_.isEmpty()) {
0250     return;
0251   }
0252 
0253   //replace "x" with "X"
0254   input_.replace(QLatin1Char('x'), QLatin1Char('X'));
0255 
0256   // remove invalid chars
0257   static const QRegularExpression badChars(QStringLiteral("[^\\d\\-X]"));
0258   input_.remove(badChars);
0259 
0260   // special case for EAN values that start with 978 or 979. That's the case
0261   // for things like barcode readers that essentially 'type' the string at
0262   // once. The simulated typing has already caused the input to be normalized,
0263   // so strip that off, as well as the generated checksum. Then continue as normal.
0264   //  If someone were to input a regular 978- or 979- ISBN _including_ the
0265   // checksum, it will be regarded as barcode input and the input will be stripped accordingly.
0266   // I consider the likelihood that someone wants to input an EAN to be higher than someone
0267   // using a Nigerian ISBN and not noticing that the checksum gets added automatically.
0268   if(input_.length() > 12
0269      && (input_.startsWith(QStringLiteral("978"))
0270          || input_.startsWith(QStringLiteral("979")))) {
0271      // Strip the first 3 characters (the invalid publisher)
0272 //     input_ = input_.right(input_.length() - 3);
0273   }
0274 
0275   // hyphen placement for some languages publishers is well-defined
0276   // remove all hyphens, and insert them ourselves
0277   // some countries have ill-defined second hyphen positions, and if
0278   // the user inserts one, then be sure to put it back
0279 
0280   // Find the first hyphen. If there is none,
0281   // input_.indexOf('-') returns -1 and hyphen1_position = -1
0282   int hyphen1_position = input_.indexOf(QLatin1Char('-'));
0283 
0284   // Find the second one. If none, hyphen2_position = -2
0285   int hyphen2_position = input_.indexOf(QLatin1Char('-'), hyphen1_position+1) - 1;
0286 
0287   // The second hyphen can not be in the last character
0288   if(hyphen2_position >= 9) {
0289     hyphen2_position = 0;
0290   }
0291 
0292   const bool hyphenAtEnd = input_.endsWith(QLatin1Char('-'));
0293 
0294   // Remove all existing hyphens. We will insert ours.
0295   input_.remove(QLatin1Char('-'));
0296   // the only place that 'X' can be is last spot
0297   for(int xpos = input_.indexOf(QLatin1Char('X')); xpos > -1; xpos = input_.indexOf(QLatin1Char('X'), xpos+1)) {
0298     if(xpos < 9) { // remove if not 10th char
0299       input_.remove(xpos, 1);
0300       --xpos;
0301     }
0302   }
0303   input_.truncate(10);
0304 
0305   // If we can find it, add the checksum
0306   // but only if not started with 978 or 979
0307   if(input_.length() > 8
0308      && !input_.startsWith(QStringLiteral("978"))
0309      && !input_.startsWith(QStringLiteral("979"))) {
0310     if(input_.length() == 9) input_.resize(10);
0311     input_[9] = checkSum10(input_);
0312   }
0313 
0314   ulong range = input_.leftJustified(9, QLatin1Char('0'), true).toULong();
0315 
0316   // now find which band the range falls in
0317   int band = 0;
0318   while(range >= bands[band].MaxValue) {
0319     ++band;
0320   }
0321 
0322   // if we have space to put the first hyphen, do it
0323   if(input_.length() > bands[band].First) {
0324     input_.insert(bands[band].First, QLatin1Char('-'));
0325   }
0326 
0327   //add 1 since one "-" character has already been inserted
0328   if(bands[band].Mid != 0) {
0329     hyphen2_position = bands[band].Mid;
0330     if(static_cast<int>(input_.length()) > (hyphen2_position + 1)) {
0331       input_.insert(hyphen2_position + 1, QLatin1Char('-'));
0332     }
0333   // or put back user's hyphen
0334   } else if(hyphen2_position > 0 && static_cast<int>(input_.length()) >= (hyphen2_position + 1)) {
0335     input_.insert(hyphen2_position + 1, QLatin1Char('-'));
0336   }
0337 
0338   // add a "-" before the checkdigit and another one if the middle "-" exists
0339   const int trueLast = bands[band].Last + 1 + (hyphen2_position > 0 ? 1 : 0);
0340   if(input_.length() > trueLast) {
0341     input_.insert(trueLast, QLatin1Char('-'));
0342   } else if(hyphenAtEnd && !input_.endsWith(QLatin1Char('-'))) {
0343     input_ += QLatin1Char('-');
0344   }
0345 }
0346 
0347 void ISBNValidator::fixup13(QString& input_) {
0348   if(input_.isEmpty()) {
0349     return;
0350   }
0351 
0352   // remove invalid chars
0353   static const QRegularExpression badChars(QStringLiteral("[^\\d-]"));
0354   input_.remove(badChars);
0355 
0356   // hyphen placement for some languages publishers is well-defined
0357   // remove all hyphens, and insert them ourselves
0358   // some countries have ill-defined second hyphen positions, and if
0359   // the user inserts one, then be sure to put it back
0360 
0361   QString after = input_.mid(3);
0362   if(after[0] == QLatin1Char('-')) {
0363     after = after.mid(1);
0364   }
0365 
0366   // Find the first hyphen. If there is none,
0367   // input_.indexOf('-') returns -1 and hyphen1_position = -1
0368   int hyphen1_position = after.indexOf(QLatin1Char('-'));
0369 
0370   // Find the second one. If none, hyphen2_position = -2
0371   int hyphen2_position = after.indexOf(QLatin1Char('-'), hyphen1_position+1) - 1;
0372 
0373   // The second hyphen can not be in the last characters
0374   if(hyphen2_position >= 9) {
0375     hyphen2_position = 0;
0376   }
0377 
0378   // Remove all existing hyphens. We will insert ours.
0379   after.remove(QLatin1Char('-'));
0380   after.truncate(10);
0381 
0382   // add the checksum
0383   if(after.length() > 8) {
0384     if(after.length() == 9) after.resize(10);
0385     after[9] = checkSum13(input_.left(3) + after);
0386   }
0387 
0388   ulong range = after.leftJustified(9, QLatin1Char('0'), true).toULong();
0389 
0390   // now find which band the range falls in
0391   int band = 0;
0392   while(range >= bands[band].MaxValue) {
0393     ++band;
0394   }
0395 
0396   // if we have space to put the first hyphen, do it
0397   if(after.length() > bands[band].First) {
0398     after.insert(bands[band].First, QLatin1Char('-'));
0399   }
0400 
0401   //add 1 since one "-" has already been inserted
0402   if(bands[band].Mid != 0) {
0403     hyphen2_position = bands[band].Mid;
0404     if(static_cast<int>(after.length()) > (hyphen2_position + 1)) {
0405       after.insert(hyphen2_position + 1, QLatin1Char('-'));
0406     }
0407   // or put back user's hyphen
0408   } else if(hyphen2_position > 0 && static_cast<int>(after.length()) >= (hyphen2_position + 1)) {
0409     after.insert(hyphen2_position + 1, QLatin1Char('-'));
0410   }
0411 
0412   // add a "-" before the checkdigit and another one if the middle "-" exists
0413   int trueLast = bands[band].Last + 1 + (hyphen2_position > 0 ? 1 : 0);
0414   if(after.length() > trueLast) {
0415     after.insert(trueLast, QLatin1Char('-'));
0416   }
0417   input_ = input_.left(3) + QLatin1Char('-') + after;
0418 }
0419 
0420 QChar ISBNValidator::checkSum10(const QString& input_) {
0421   uint sum = 0;
0422   uint multiplier = 10;
0423 
0424   // hyphens are already gone, only use first nine digits
0425   for(int i = 0; i < input_.length() && multiplier > 1; ++i) {
0426     sum += input_[i].digitValue() * multiplier--;
0427   }
0428   sum %= 11;
0429   sum = 11-sum;
0430 
0431   QChar c;
0432   if(sum == 10) {
0433     c = QLatin1Char('X');
0434   } else if(sum == 11) {
0435     c = QLatin1Char('0');
0436   } else {
0437     c = QString::number(sum)[0];
0438   }
0439   return c;
0440 }
0441 
0442 QChar ISBNValidator::checkSum13(const QString& input_) {
0443   uint sum = 0;
0444 
0445   const int len = qMin(12, input_.length());
0446   // hyphens are already gone, only use first twelve digits
0447   for(int i = 0; i < len; ++i) {
0448     sum += input_[i].digitValue() * (1 + 2*(i%2));
0449     // multiplier goes 1, 3, 1, 3, etc...
0450   }
0451   sum %= 10;
0452   sum = 10-sum;
0453 
0454   QChar c;
0455   if(sum == 10) {
0456     c = QLatin1Char('0');
0457   } else {
0458     c = QString::number(sum)[0];
0459   }
0460   return c;
0461 }
0462 
0463 // ISBN code from Regis Boudin
0464 #define ISBNGRP_1DIGIT(digit, max, middle, last)        \
0465           {((digit)*100000000) + (max), 1, middle, last}
0466 #define ISBNGRP_2DIGIT(digit, max, middle, last)        \
0467           {((digit)*10000000) + ((max)/10), 2, middle, last}
0468 #define ISBNGRP_3DIGIT(digit, max, middle, last)        \
0469           {((digit)*1000000) + ((max)/100), 3, middle, last}
0470 #define ISBNGRP_4DIGIT(digit, max, middle, last)        \
0471           {((digit)*100000) + ((max)/1000), 4, middle, last}
0472 #define ISBNGRP_5DIGIT(digit, max, middle, last)        \
0473           {((digit)*10000) + ((max)/10000), 5, middle, last}
0474 
0475 #define ISBNPUB_2DIGIT(grp) (((grp)+1)*1000000)
0476 #define ISBNPUB_3DIGIT(grp) (((grp)+1)*100000)
0477 #define ISBNPUB_4DIGIT(grp) (((grp)+1)*10000)
0478 #define ISBNPUB_5DIGIT(grp) (((grp)+1)*1000)
0479 #define ISBNPUB_6DIGIT(grp) (((grp)+1)*100)
0480 #define ISBNPUB_7DIGIT(grp) (((grp)+1)*10)
0481 #define ISBNPUB_8DIGIT(grp) (((grp)+1)*1)
0482 
0483 // how to format an ISBN, after categorising it into a range of numbers.
0484 struct ISBNValidator::isbn_band ISBNValidator::bands[] = {
0485   /* Groups 0 & 1 : English */
0486   ISBNGRP_1DIGIT(0,     ISBNPUB_2DIGIT(19),      3, 9),
0487   ISBNGRP_1DIGIT(0,     ISBNPUB_3DIGIT(699),     4, 9),
0488   ISBNGRP_1DIGIT(0,     ISBNPUB_4DIGIT(8499),    5, 9),
0489   ISBNGRP_1DIGIT(0,     ISBNPUB_5DIGIT(89999),   6, 9),
0490   ISBNGRP_1DIGIT(0,     ISBNPUB_6DIGIT(949999),  7, 9),
0491   ISBNGRP_1DIGIT(0,     ISBNPUB_7DIGIT(9999999), 8, 9),
0492 
0493   ISBNGRP_1DIGIT(1,     ISBNPUB_5DIGIT(54999),   6, 9),
0494   ISBNGRP_1DIGIT(1,     ISBNPUB_5DIGIT(86979),   6, 9),
0495   ISBNGRP_1DIGIT(1,     ISBNPUB_6DIGIT(998999),  7, 9),
0496   ISBNGRP_1DIGIT(1,     ISBNPUB_7DIGIT(9999999), 8, 9),
0497   /* Group 2 : French */
0498   ISBNGRP_1DIGIT(2,     ISBNPUB_2DIGIT(19),      3, 9),
0499   ISBNGRP_1DIGIT(2,     ISBNPUB_3DIGIT(349),     4, 9),
0500   ISBNGRP_1DIGIT(2,     ISBNPUB_5DIGIT(39999),   6, 9),
0501   ISBNGRP_1DIGIT(2,     ISBNPUB_3DIGIT(699),     4, 9),
0502   ISBNGRP_1DIGIT(2,     ISBNPUB_4DIGIT(8399),    5, 9),
0503   ISBNGRP_1DIGIT(2,     ISBNPUB_5DIGIT(89999),   6, 9),
0504   ISBNGRP_1DIGIT(2,     ISBNPUB_6DIGIT(949999),  7, 9),
0505   ISBNGRP_1DIGIT(2,     ISBNPUB_7DIGIT(9999999), 8, 9),
0506 
0507   /* Group 2 : German */
0508   ISBNGRP_1DIGIT(3,     ISBNPUB_2DIGIT(19),      3, 9),
0509   ISBNGRP_1DIGIT(3,     ISBNPUB_3DIGIT(699),     4, 9),
0510   ISBNGRP_1DIGIT(3,     ISBNPUB_4DIGIT(8499),    5, 9),
0511   ISBNGRP_1DIGIT(3,     ISBNPUB_5DIGIT(89999),   6, 9),
0512   ISBNGRP_1DIGIT(3,     ISBNPUB_6DIGIT(949999),  7, 9),
0513   ISBNGRP_1DIGIT(3,     ISBNPUB_7DIGIT(9999999), 8, 9),
0514 
0515   ISBNGRP_1DIGIT(7,     ISBNPUB_2DIGIT(99),      0, 9),
0516   /* Group 80 : Czech */
0517   ISBNGRP_2DIGIT(80,    ISBNPUB_2DIGIT(19),      4, 9),
0518   ISBNGRP_2DIGIT(80,    ISBNPUB_3DIGIT(699),     5, 9),
0519   ISBNGRP_2DIGIT(80,    ISBNPUB_4DIGIT(8499),    6, 9),
0520   ISBNGRP_2DIGIT(80,    ISBNPUB_5DIGIT(89999),   7, 9),
0521   ISBNGRP_2DIGIT(80,    ISBNPUB_6DIGIT(949999),  8, 9),
0522 
0523   /* Group 83 : Poland */
0524   ISBNGRP_2DIGIT(83,    ISBNPUB_2DIGIT(19),      4, 9),
0525   ISBNGRP_2DIGIT(83,    ISBNPUB_3DIGIT(599),     5, 9),
0526   ISBNGRP_2DIGIT(83,    ISBNPUB_5DIGIT(69999),   7, 9),
0527   ISBNGRP_2DIGIT(83,    ISBNPUB_4DIGIT(8499),    6, 9),
0528   ISBNGRP_2DIGIT(83,    ISBNPUB_5DIGIT(89999),   7, 9),
0529   ISBNGRP_2DIGIT(83,    ISBNPUB_6DIGIT(949999),  8, 9),
0530 
0531   /* Group 90 * Netherlands */
0532   ISBNGRP_2DIGIT(90,    ISBNPUB_2DIGIT(19),      4, 9),
0533   ISBNGRP_2DIGIT(90,    ISBNPUB_3DIGIT(499),     5, 9),
0534   ISBNGRP_2DIGIT(90,    ISBNPUB_4DIGIT(6999),    6, 9),
0535   ISBNGRP_2DIGIT(90,    ISBNPUB_5DIGIT(79999),   7, 9),
0536   ISBNGRP_2DIGIT(90,    ISBNPUB_6DIGIT(849999),  8, 9),
0537   ISBNGRP_2DIGIT(90,    ISBNPUB_4DIGIT(8999),    6, 9),
0538   ISBNGRP_2DIGIT(90,    ISBNPUB_7DIGIT(9999999), 9, 9),
0539 
0540   ISBNGRP_2DIGIT(94,    ISBNPUB_2DIGIT(99),      0, 9),
0541   ISBNGRP_3DIGIT(993,   ISBNPUB_2DIGIT(99),      0, 9),
0542   ISBNGRP_4DIGIT(9989,  ISBNPUB_2DIGIT(99),      0, 9),
0543   ISBNGRP_5DIGIT(99999, ISBNPUB_2DIGIT(99),      0, 9)
0544 };
0545 
0546 bool Tellico::ISBNComparison::operator()(const QString& value1_, const QString& value2_) const {
0547   QString value1 = ISBNValidator::cleanValue(value1_).toUpper();
0548   QString value2 = ISBNValidator::cleanValue(value2_).toUpper();
0549 
0550   if(value1 == value2) {
0551     return true;
0552   }
0553   const int len1 = value1.length();
0554   const int len2 = value2.length();
0555   if(len1 < 10 || len2 < 10) {
0556     // they're not ISBN values at all
0557     return false;
0558   }
0559   if(len1 == 13) {
0560     ISBNValidator::fixup13(value1);
0561   } else {
0562     value1 = ISBNValidator::isbn13(value1);
0563   }
0564   if(len2 == 13) {
0565     ISBNValidator::fixup13(value2);
0566   } else {
0567     value2 = ISBNValidator::isbn13(value2);
0568   }
0569   return value1 == value2;
0570 }