1 /***************************************************************************
2     Copyright (C) 2002-2009 Robby Stephenson <robby@periapsis.org>
3  ***************************************************************************/
4 
5 /***************************************************************************
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or         *
8  *   modify it under the terms of the GNU General Public License as        *
9  *   published by the Free Software Foundation; either version 2 of        *
10  *   the License or (at your option) version 3 or any later version        *
11  *   accepted by the membership of KDE e.V. (or its successor approved     *
12  *   by the membership of KDE e.V.), which shall act as a proxy            *
13  *   defined in Section 14 of version 3 of the license.                    *
14  *                                                                         *
15  *   This program is distributed in the hope that it will be useful,       *
16  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
17  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
18  *   GNU General Public License for more details.                          *
19  *                                                                         *
20  *   You should have received a copy of the GNU General Public License     *
21  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
22  *                                                                         *
23  ***************************************************************************/
24 
25 #include "isbnvalidator.h"
26 #include "upcvalidator.h"
27 
28 #include <QStringList>
29 #include <QRegularExpression>
30 
31 using Tellico::ISBNValidator;
32 
33 //static
isbn10(QString isbn13)34 QString ISBNValidator::isbn10(QString isbn13) {
35   QString original = isbn13;
36   isbn13.remove(QLatin1Char('-'));
37   if(isbn13.length() == 10) {
38     fixup10(isbn13);
39     return isbn13;
40   }
41   if(!isbn13.startsWith(QStringLiteral("978"))) {
42     return original;
43   }
44   if(isbn13.length() < 13) {
45     fixup10(isbn13);
46     return isbn13;
47   }
48   isbn13 = isbn13.mid(3);
49   // remove checksum
50   isbn13.truncate(isbn13.length()-1);
51   // add new checksum
52   isbn13 += checkSum10(isbn13);
53   fixup10(isbn13);
54   return isbn13;
55 }
56 
isbn13(QString isbn10)57 QString ISBNValidator::isbn13(QString isbn10) {
58   isbn10.remove(QLatin1Char('-'));
59   if(isbn10.length() < 10) {
60     return isbn10;
61   }
62   if(isbn10.length() > 10) {
63     // assume it's already an isbn13 value
64     fixup13(isbn10);
65     return isbn10;
66   }
67   // remove checksum
68   isbn10.truncate(isbn10.length()-1);
69   // begins with 978
70   isbn10.prepend(QStringLiteral("978"));
71   // add new checksum
72   isbn10 += checkSum13(isbn10);
73   fixup13(isbn10);
74   return isbn10;
75 }
76 
listDifference(const QStringList & list1_,const QStringList & list2_)77 QStringList ISBNValidator::listDifference(const QStringList& list1_, const QStringList& list2_) {
78   ISBNComparison comp;
79 
80   QStringList notFound;
81   foreach(const QString& value1, list1_) {
82     bool found = false;
83     foreach(const QString& value2, list2_) {
84       if(comp(value1, value2)) {
85         found = true;
86         break;
87       }
88     }
89     if(!found) {
90       notFound.append(value1);
91     }
92   }
93   return notFound;
94 }
95 
cleanValue(QString isbn)96 QString ISBNValidator::cleanValue(QString isbn) {
97   static const QRegularExpression badChars(QStringLiteral("[^xX0123456789]"));
98   isbn.remove(badChars);
99   return isbn;
100 }
101 
ISBNValidator(QObject * parent_)102 ISBNValidator::ISBNValidator(QObject* parent_)
103     : QValidator(parent_) {
104 }
105 
validate(QString & input_,int & pos_) const106 QValidator::State ISBNValidator::validate(QString& input_, int& pos_) const {
107   // check if it's a cuecat first
108   State catState = CueCat::decode(input_);
109   if(catState != Invalid) {
110     pos_ = input_.length();
111     return catState;
112   }
113 
114   if(input_.startsWith(QStringLiteral("978")) ||
115      input_.startsWith(QStringLiteral("979"))) {
116     return validate13(input_, pos_);
117   } else {
118     return validate10(input_, pos_);
119   }
120 }
121 
fixup(QString & input_) const122 void ISBNValidator::fixup(QString& input_) const {
123   staticFixup(input_);
124 }
125 
staticFixup(QString & input_)126 void ISBNValidator::staticFixup(QString& input_) {
127   if((input_.startsWith(QStringLiteral("978"))
128        || input_.startsWith(QStringLiteral("979")))
129      && input_.count(QRegularExpression(QStringLiteral("\\d"))) > 10) {
130     fixup13(input_);
131   } else {
132     fixup10(input_);
133   }
134 }
135 
validate10(QString & input_,int & pos_) const136 QValidator::State ISBNValidator::validate10(QString& input_, int& pos_) const {
137   int len = input_.length();
138 /*
139   // Don't do this since the hyphens may be in the wrong place, can't put that in a regexp
140   if(isbn.exactMatch(input_) // put the exactMatch() first since I use matchedLength() later
141      && (len == 12 || len == 13)
142      && input_[len-1] == checkSum(input_)) {
143     return QValidator::Acceptable;
144   }
145 */
146   // two easy invalid cases are too many hyphens and the 'X' not in the last position
147   if(input_.count(QLatin1Char('-')) > 3
148      || input_.count(QLatin1Char('X'), Qt::CaseInsensitive) > 1
149      || (input_.indexOf(QLatin1Char('X'), 0, Qt::CaseInsensitive) != -1 && input_[len-1].toUpper() != QLatin1Char('X'))) {
150     return QValidator::Invalid;
151   }
152 
153   // remember if the cursor is at the end
154   bool atEnd = (pos_ == static_cast<int>(len));
155 
156   // fix the case where the user attempts to delete a character from a non-checksum
157   // position; the solution is to delete the checksum, but only if it's X
158   if(!atEnd && input_[len-1].toUpper() == QLatin1Char('X')) {
159     input_.truncate(len-1);
160     --len;
161   }
162 
163   // fix the case where the user attempts to delete the checksum; the
164   // solution is to delete the last digit as well
165   static const QRegularExpression digit(QStringLiteral("\\d"));
166   if(atEnd && input_.count(digit) == 9 && input_[len-1] == QLatin1Char('-')) {
167     input_.truncate(len-2);
168     pos_ -= 2;
169   }
170 
171   // now fixup the hyphens and maybe add a checksum
172   fixup10(input_);
173   len = input_.length(); // might have changed in fixup()
174   if(atEnd) {
175     pos_ = len;
176   }
177 
178   // first check to see if it's a "perfect" ISBN
179   // A perfect ISBN has 9 digits plus either an 'X' or another digit
180   // A perfect ISBN may have 2 or 3 hyphens
181   // The final digit or 'X' is the correct check sum
182   static const QRegularExpression isbn(QStringLiteral("^(\\d-?){9,11}-[\\dX]$"));
183   if(isbn.match(input_).hasMatch() && (len == 12 || len == 13)) {
184     return QValidator::Acceptable;
185   } else {
186     return QValidator::Intermediate;
187   }
188 }
189 
validate13(QString & input_,int & pos_) const190 QValidator::State ISBNValidator::validate13(QString& input_, int& pos_) const {
191   int len = input_.length();
192 
193   const uint countX = input_.count(QLatin1Char('X'), Qt::CaseInsensitive);
194   // two easy invalid cases are too many hyphens or 'X'
195   if(input_.count(QLatin1Char('-')) > 4 || countX > 1) {
196     return QValidator::Invalid;
197   }
198 
199   // now, it's not certain that we're getting a EAN-13,
200   // it could be a ISBN-10 from Nigeria or Indonesia
201   if(countX > 0 && (input_[len-1].toUpper() != QLatin1Char('X') || len > 13)) {
202     return QValidator::Invalid;
203   }
204 
205   // remember if the cursor is at the end
206   bool atEnd = (pos_ == len);
207 
208   // fix the case where the user attempts to delete a character from a non-checksum
209   // position; the solution is to delete the checksum, but only if it's X
210   if(!atEnd && input_[len-1].toUpper() == QLatin1Char('X')) {
211     input_.truncate(len-1);
212     --len;
213   }
214 
215   // fix the case where the user attempts to delete the checksum; the
216   // solution is to delete the last digit as well
217   static const QRegularExpression digit(QStringLiteral("\\d"));
218   const uint countN = input_.count(digit);
219   if(atEnd && (countN == 12 || countN == 9) && input_[len-1] == QLatin1Char('-')) {
220     input_.truncate(len-2);
221     pos_ -= 2;
222   }
223 
224   // now fixup the hyphens and maybe add a checksum
225   if(countN > 10) {
226     fixup13(input_);
227   } else {
228     fixup10(input_);
229   }
230 
231   len = input_.length(); // might have changed in fixup()
232   if(atEnd) {
233     pos_ = len;
234   }
235 
236   // first check to see if it's a "perfect" ISBN13
237   // A perfect ISBN13 has 13 digits
238   // A perfect ISBN13 may have 3 or 4 hyphens
239   // The final digit is the correct check sum
240   static const QRegularExpression isbn(QStringLiteral("^(\\d-?){13,17}$"));
241   if(isbn.match(input_).hasMatch()) {
242     return QValidator::Acceptable;
243   } else {
244     return QValidator::Intermediate;
245   }
246 }
247 
fixup10(QString & input_)248 void ISBNValidator::fixup10(QString& input_) {
249   if(input_.isEmpty()) {
250     return;
251   }
252 
253   //replace "x" with "X"
254   input_.replace(QLatin1Char('x'), QLatin1Char('X'));
255 
256   // remove invalid chars
257   static const QRegularExpression badChars(QStringLiteral("[^\\d\\-X]"));
258   input_.remove(badChars);
259 
260   // special case for EAN values that start with 978 or 979. That's the case
261   // for things like barcode readers that essentially 'type' the string at
262   // once. The simulated typing has already caused the input to be normalized,
263   // so strip that off, as well as the generated checksum. Then continue as normal.
264   //  If someone were to input a regular 978- or 979- ISBN _including_ the
265   // checksum, it will be regarded as barcode input and the input will be stripped accordingly.
266   // I consider the likelihood that someone wants to input an EAN to be higher than someone
267   // using a Nigerian ISBN and not noticing that the checksum gets added automatically.
268   if(input_.length() > 12
269      && (input_.startsWith(QStringLiteral("978"))
270          || input_.startsWith(QStringLiteral("979")))) {
271      // Strip the first 3 characters (the invalid publisher)
272 //     input_ = input_.right(input_.length() - 3);
273   }
274 
275   // hyphen placement for some languages publishers is well-defined
276   // remove all hyphens, and insert them ourselves
277   // some countries have ill-defined second hyphen positions, and if
278   // the user inserts one, then be sure to put it back
279 
280   // Find the first hyphen. If there is none,
281   // input_.indexOf('-') returns -1 and hyphen1_position = -1
282   int hyphen1_position = input_.indexOf(QLatin1Char('-'));
283 
284   // Find the second one. If none, hyphen2_position = -2
285   int hyphen2_position = input_.indexOf(QLatin1Char('-'), hyphen1_position+1) - 1;
286 
287   // The second hyphen can not be in the last character
288   if(hyphen2_position >= 9) {
289     hyphen2_position = 0;
290   }
291 
292   const bool hyphenAtEnd = input_.endsWith(QLatin1Char('-'));
293 
294   // Remove all existing hyphens. We will insert ours.
295   input_.remove(QLatin1Char('-'));
296   // the only place that 'X' can be is last spot
297   for(int xpos = input_.indexOf(QLatin1Char('X')); xpos > -1; xpos = input_.indexOf(QLatin1Char('X'), xpos+1)) {
298     if(xpos < 9) { // remove if not 10th char
299       input_.remove(xpos, 1);
300       --xpos;
301     }
302   }
303   input_.truncate(10);
304 
305   // If we can find it, add the checksum
306   // but only if not started with 978 or 979
307   if(input_.length() > 8
308      && !input_.startsWith(QStringLiteral("978"))
309      && !input_.startsWith(QStringLiteral("979"))) {
310     if(input_.length() == 9) input_.resize(10);
311     input_[9] = checkSum10(input_);
312   }
313 
314   ulong range = input_.leftJustified(9, QLatin1Char('0'), true).toULong();
315 
316   // now find which band the range falls in
317   int band = 0;
318   while(range >= bands[band].MaxValue) {
319     ++band;
320   }
321 
322   // if we have space to put the first hyphen, do it
323   if(input_.length() > bands[band].First) {
324     input_.insert(bands[band].First, QLatin1Char('-'));
325   }
326 
327   //add 1 since one "-" character has already been inserted
328   if(bands[band].Mid != 0) {
329     hyphen2_position = bands[band].Mid;
330     if(static_cast<int>(input_.length()) > (hyphen2_position + 1)) {
331       input_.insert(hyphen2_position + 1, QLatin1Char('-'));
332     }
333   // or put back user's hyphen
334   } else if(hyphen2_position > 0 && static_cast<int>(input_.length()) >= (hyphen2_position + 1)) {
335     input_.insert(hyphen2_position + 1, QLatin1Char('-'));
336   }
337 
338   // add a "-" before the checkdigit and another one if the middle "-" exists
339   const int trueLast = bands[band].Last + 1 + (hyphen2_position > 0 ? 1 : 0);
340   if(input_.length() > trueLast) {
341     input_.insert(trueLast, QLatin1Char('-'));
342   } else if(hyphenAtEnd && !input_.endsWith(QLatin1Char('-'))) {
343     input_ += QLatin1Char('-');
344   }
345 }
346 
fixup13(QString & input_)347 void ISBNValidator::fixup13(QString& input_) {
348   if(input_.isEmpty()) {
349     return;
350   }
351 
352   // remove invalid chars
353   static const QRegularExpression badChars(QStringLiteral("[^\\d-]"));
354   input_.remove(badChars);
355 
356   // hyphen placement for some languages publishers is well-defined
357   // remove all hyphens, and insert them ourselves
358   // some countries have ill-defined second hyphen positions, and if
359   // the user inserts one, then be sure to put it back
360 
361   QString after = input_.mid(3);
362   if(after[0] == QLatin1Char('-')) {
363     after = after.mid(1);
364   }
365 
366   // Find the first hyphen. If there is none,
367   // input_.indexOf('-') returns -1 and hyphen1_position = -1
368   int hyphen1_position = after.indexOf(QLatin1Char('-'));
369 
370   // Find the second one. If none, hyphen2_position = -2
371   int hyphen2_position = after.indexOf(QLatin1Char('-'), hyphen1_position+1) - 1;
372 
373   // The second hyphen can not be in the last characters
374   if(hyphen2_position >= 9) {
375     hyphen2_position = 0;
376   }
377 
378   // Remove all existing hyphens. We will insert ours.
379   after.remove(QLatin1Char('-'));
380   after.truncate(10);
381 
382   // add the checksum
383   if(after.length() > 8) {
384     if(after.length() == 9) after.resize(10);
385     after[9] = checkSum13(input_.left(3) + after);
386   }
387 
388   ulong range = after.leftJustified(9, QLatin1Char('0'), true).toULong();
389 
390   // now find which band the range falls in
391   int band = 0;
392   while(range >= bands[band].MaxValue) {
393     ++band;
394   }
395 
396   // if we have space to put the first hyphen, do it
397   if(after.length() > bands[band].First) {
398     after.insert(bands[band].First, QLatin1Char('-'));
399   }
400 
401   //add 1 since one "-" has already been inserted
402   if(bands[band].Mid != 0) {
403     hyphen2_position = bands[band].Mid;
404     if(static_cast<int>(after.length()) > (hyphen2_position + 1)) {
405       after.insert(hyphen2_position + 1, QLatin1Char('-'));
406     }
407   // or put back user's hyphen
408   } else if(hyphen2_position > 0 && static_cast<int>(after.length()) >= (hyphen2_position + 1)) {
409     after.insert(hyphen2_position + 1, QLatin1Char('-'));
410   }
411 
412   // add a "-" before the checkdigit and another one if the middle "-" exists
413   int trueLast = bands[band].Last + 1 + (hyphen2_position > 0 ? 1 : 0);
414   if(after.length() > trueLast) {
415     after.insert(trueLast, QLatin1Char('-'));
416   }
417   input_ = input_.left(3) + QLatin1Char('-') + after;
418 }
419 
checkSum10(const QString & input_)420 QChar ISBNValidator::checkSum10(const QString& input_) {
421   uint sum = 0;
422   uint multiplier = 10;
423 
424   // hyphens are already gone, only use first nine digits
425   for(int i = 0; i < input_.length() && multiplier > 1; ++i) {
426     sum += input_[i].digitValue() * multiplier--;
427   }
428   sum %= 11;
429   sum = 11-sum;
430 
431   QChar c;
432   if(sum == 10) {
433     c = QLatin1Char('X');
434   } else if(sum == 11) {
435     c = QLatin1Char('0');
436   } else {
437     c = QString::number(sum)[0];
438   }
439   return c;
440 }
441 
checkSum13(const QString & input_)442 QChar ISBNValidator::checkSum13(const QString& input_) {
443   uint sum = 0;
444 
445   const int len = qMin(12, input_.length());
446   // hyphens are already gone, only use first twelve digits
447   for(int i = 0; i < len; ++i) {
448     sum += input_[i].digitValue() * (1 + 2*(i%2));
449     // multiplier goes 1, 3, 1, 3, etc...
450   }
451   sum %= 10;
452   sum = 10-sum;
453 
454   QChar c;
455   if(sum == 10) {
456     c = QLatin1Char('0');
457   } else {
458     c = QString::number(sum)[0];
459   }
460   return c;
461 }
462 
463 // ISBN code from Regis Boudin
464 #define ISBNGRP_1DIGIT(digit, max, middle, last)        \
465           {((digit)*100000000) + (max), 1, middle, last}
466 #define ISBNGRP_2DIGIT(digit, max, middle, last)        \
467           {((digit)*10000000) + ((max)/10), 2, middle, last}
468 #define ISBNGRP_3DIGIT(digit, max, middle, last)        \
469           {((digit)*1000000) + ((max)/100), 3, middle, last}
470 #define ISBNGRP_4DIGIT(digit, max, middle, last)        \
471           {((digit)*100000) + ((max)/1000), 4, middle, last}
472 #define ISBNGRP_5DIGIT(digit, max, middle, last)        \
473           {((digit)*10000) + ((max)/10000), 5, middle, last}
474 
475 #define ISBNPUB_2DIGIT(grp) (((grp)+1)*1000000)
476 #define ISBNPUB_3DIGIT(grp) (((grp)+1)*100000)
477 #define ISBNPUB_4DIGIT(grp) (((grp)+1)*10000)
478 #define ISBNPUB_5DIGIT(grp) (((grp)+1)*1000)
479 #define ISBNPUB_6DIGIT(grp) (((grp)+1)*100)
480 #define ISBNPUB_7DIGIT(grp) (((grp)+1)*10)
481 #define ISBNPUB_8DIGIT(grp) (((grp)+1)*1)
482 
483 // how to format an ISBN, after categorising it into a range of numbers.
484 struct ISBNValidator::isbn_band ISBNValidator::bands[] = {
485   /* Groups 0 & 1 : English */
486   ISBNGRP_1DIGIT(0,     ISBNPUB_2DIGIT(19),      3, 9),
487   ISBNGRP_1DIGIT(0,     ISBNPUB_3DIGIT(699),     4, 9),
488   ISBNGRP_1DIGIT(0,     ISBNPUB_4DIGIT(8499),    5, 9),
489   ISBNGRP_1DIGIT(0,     ISBNPUB_5DIGIT(89999),   6, 9),
490   ISBNGRP_1DIGIT(0,     ISBNPUB_6DIGIT(949999),  7, 9),
491   ISBNGRP_1DIGIT(0,     ISBNPUB_7DIGIT(9999999), 8, 9),
492 
493   ISBNGRP_1DIGIT(1,     ISBNPUB_5DIGIT(54999),   6, 9),
494   ISBNGRP_1DIGIT(1,     ISBNPUB_5DIGIT(86979),   6, 9),
495   ISBNGRP_1DIGIT(1,     ISBNPUB_6DIGIT(998999),  7, 9),
496   ISBNGRP_1DIGIT(1,     ISBNPUB_7DIGIT(9999999), 8, 9),
497   /* Group 2 : French */
498   ISBNGRP_1DIGIT(2,     ISBNPUB_2DIGIT(19),      3, 9),
499   ISBNGRP_1DIGIT(2,     ISBNPUB_3DIGIT(349),     4, 9),
500   ISBNGRP_1DIGIT(2,     ISBNPUB_5DIGIT(39999),   6, 9),
501   ISBNGRP_1DIGIT(2,     ISBNPUB_3DIGIT(699),     4, 9),
502   ISBNGRP_1DIGIT(2,     ISBNPUB_4DIGIT(8399),    5, 9),
503   ISBNGRP_1DIGIT(2,     ISBNPUB_5DIGIT(89999),   6, 9),
504   ISBNGRP_1DIGIT(2,     ISBNPUB_6DIGIT(949999),  7, 9),
505   ISBNGRP_1DIGIT(2,     ISBNPUB_7DIGIT(9999999), 8, 9),
506 
507   /* Group 2 : German */
508   ISBNGRP_1DIGIT(3,     ISBNPUB_2DIGIT(19),      3, 9),
509   ISBNGRP_1DIGIT(3,     ISBNPUB_3DIGIT(699),     4, 9),
510   ISBNGRP_1DIGIT(3,     ISBNPUB_4DIGIT(8499),    5, 9),
511   ISBNGRP_1DIGIT(3,     ISBNPUB_5DIGIT(89999),   6, 9),
512   ISBNGRP_1DIGIT(3,     ISBNPUB_6DIGIT(949999),  7, 9),
513   ISBNGRP_1DIGIT(3,     ISBNPUB_7DIGIT(9999999), 8, 9),
514 
515   ISBNGRP_1DIGIT(7,     ISBNPUB_2DIGIT(99),      0, 9),
516   /* Group 80 : Czech */
517   ISBNGRP_2DIGIT(80,    ISBNPUB_2DIGIT(19),      4, 9),
518   ISBNGRP_2DIGIT(80,    ISBNPUB_3DIGIT(699),     5, 9),
519   ISBNGRP_2DIGIT(80,    ISBNPUB_4DIGIT(8499),    6, 9),
520   ISBNGRP_2DIGIT(80,    ISBNPUB_5DIGIT(89999),   7, 9),
521   ISBNGRP_2DIGIT(80,    ISBNPUB_6DIGIT(949999),  8, 9),
522 
523   /* Group 83 : Poland */
524   ISBNGRP_2DIGIT(83,    ISBNPUB_2DIGIT(19),      4, 9),
525   ISBNGRP_2DIGIT(83,    ISBNPUB_3DIGIT(599),     5, 9),
526   ISBNGRP_2DIGIT(83,    ISBNPUB_5DIGIT(69999),   7, 9),
527   ISBNGRP_2DIGIT(83,    ISBNPUB_4DIGIT(8499),    6, 9),
528   ISBNGRP_2DIGIT(83,    ISBNPUB_5DIGIT(89999),   7, 9),
529   ISBNGRP_2DIGIT(83,    ISBNPUB_6DIGIT(949999),  8, 9),
530 
531   /* Group 90 * Netherlands */
532   ISBNGRP_2DIGIT(90,    ISBNPUB_2DIGIT(19),      4, 9),
533   ISBNGRP_2DIGIT(90,    ISBNPUB_3DIGIT(499),     5, 9),
534   ISBNGRP_2DIGIT(90,    ISBNPUB_4DIGIT(6999),    6, 9),
535   ISBNGRP_2DIGIT(90,    ISBNPUB_5DIGIT(79999),   7, 9),
536   ISBNGRP_2DIGIT(90,    ISBNPUB_6DIGIT(849999),  8, 9),
537   ISBNGRP_2DIGIT(90,    ISBNPUB_4DIGIT(8999),    6, 9),
538   ISBNGRP_2DIGIT(90,    ISBNPUB_7DIGIT(9999999), 9, 9),
539 
540   ISBNGRP_2DIGIT(94,    ISBNPUB_2DIGIT(99),      0, 9),
541   ISBNGRP_3DIGIT(993,   ISBNPUB_2DIGIT(99),      0, 9),
542   ISBNGRP_4DIGIT(9989,  ISBNPUB_2DIGIT(99),      0, 9),
543   ISBNGRP_5DIGIT(99999, ISBNPUB_2DIGIT(99),      0, 9)
544 };
545 
operator ()(const QString & value1_,const QString & value2_) const546 bool Tellico::ISBNComparison::operator()(const QString& value1_, const QString& value2_) const {
547   QString value1 = ISBNValidator::cleanValue(value1_).toUpper();
548   QString value2 = ISBNValidator::cleanValue(value2_).toUpper();
549 
550   if(value1 == value2) {
551     return true;
552   }
553   const int len1 = value1.length();
554   const int len2 = value2.length();
555   if(len1 < 10 || len2 < 10) {
556     // they're not ISBN values at all
557     return false;
558   }
559   if(len1 == 13) {
560     ISBNValidator::fixup13(value1);
561   } else {
562     value1 = ISBNValidator::isbn13(value1);
563   }
564   if(len2 == 13) {
565     ISBNValidator::fixup13(value2);
566   } else {
567     value2 = ISBNValidator::isbn13(value2);
568   }
569   return value1 == value2;
570 }
571