diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 19:17:32 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 19:17:32 +0000 |
commit | e38d2351b83fa65c66ccde443777647ef5cb6cff (patch) | |
tree | 1897fc20e9f73a81c520a5b9f76f8ed042124883 /src/isbnvalidator.cpp | |
download | tellico-e38d2351b83fa65c66ccde443777647ef5cb6cff.tar.gz tellico-e38d2351b83fa65c66ccde443777647ef5cb6cff.zip |
Added KDE3 version of Tellico
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/tellico@1097620 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/isbnvalidator.cpp')
-rw-r--r-- | src/isbnvalidator.cpp | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/src/isbnvalidator.cpp b/src/isbnvalidator.cpp new file mode 100644 index 0000000..91ed099 --- /dev/null +++ b/src/isbnvalidator.cpp @@ -0,0 +1,478 @@ +/*************************************************************************** + copyright : (C) 2002-2006 by Robby Stephenson + email : robby@periapsis.org + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#include "isbnvalidator.h" +#include "tellico_debug.h" + +using Tellico::ISBNValidator; + +//static +QString ISBNValidator::isbn10(QString isbn13) { + if(!isbn13.startsWith(QString::fromLatin1("978"))) { + myDebug() << "ISBNValidator::isbn10() - can't convert, must start with 978: " << isbn13 << endl; + return isbn13; + } + isbn13 = isbn13.mid(3); + isbn13.remove('-'); + // remove checksum + isbn13.truncate(isbn13.length()-1); + // add new checksum + isbn13 += checkSum10(isbn13); + staticFixup(isbn13); + return isbn13; +} + +QString ISBNValidator::isbn13(QString isbn10) { + isbn10.remove('-'); + if(isbn10.startsWith(QString::fromLatin1("978")) || + isbn10.startsWith(QString::fromLatin1("979"))) { + return isbn10; + } + // remove checksum + isbn10.truncate(isbn10.length()-1); + // begins with 978 + isbn10.prepend(QString::fromLatin1("978")); + // add new checksum + isbn10 += checkSum13(isbn10); + staticFixup(isbn10); + return isbn10; +} + +QString ISBNValidator::cleanValue(QString isbn) { + isbn.remove(QRegExp(QString::fromLatin1("[^xX0123456789]"))); + return isbn; +} + +ISBNValidator::ISBNValidator(QObject* parent_, const char* name_/*=0*/) + : QValidator(parent_, name_) { +} + +QValidator::State ISBNValidator::validate(QString& input_, int& pos_) const { + if(input_.startsWith(QString::fromLatin1("978")) || + input_.startsWith(QString::fromLatin1("979"))) { + return validate13(input_, pos_); + } else { + return validate10(input_, pos_); + } +} + +void ISBNValidator::fixup(QString& input_) const { + return staticFixup(input_); +} + +void ISBNValidator::staticFixup(QString& input_) { + if((input_.startsWith(QString::fromLatin1("978")) + || input_.startsWith(QString::fromLatin1("979"))) + && input_.contains(QRegExp(QString::fromLatin1("\\d"))) > 10) { + return fixup13(input_); + } + return fixup10(input_); +} + +QValidator::State ISBNValidator::validate10(QString& input_, int& pos_) const { + // first check to see if it's a "perfect" ISBN + // A perfect ISBN has 9 digits plus either an 'X' or another digit + // A perfect ISBN may have 2 or 3 hyphens + // The final digit or 'X' is the correct check sum + static const QRegExp isbn(QString::fromLatin1("(\\d-?){9,11}-[\\dX]")); + uint len = input_.length(); +/* + // Don't do this since the hyphens may be in the wrong place, can't put that in a regexp + if(isbn.exactMatch(input_) // put the exactMatch() first since I use matchedLength() later + && (len == 12 || len == 13) + && input_[len-1] == checkSum(input_)) { + return QValidator::Acceptable; + } +*/ + // two easy invalid cases are too many hyphens and the 'X' not in the last position + if(input_.contains('-') > 3 + || input_.contains('X', false) > 1 + || (input_.find('X', 0, false) != -1 && input_[len-1].upper() != 'X')) { + return QValidator::Invalid; + } + + // remember if the cursor is at the end + bool atEnd = (pos_ == static_cast<int>(len)); + + // fix the case where the user attempts to delete a character from a non-checksum + // position; the solution is to delete the checksum, but only if it's X + if(!atEnd && input_[len-1].upper() == 'X') { + input_.truncate(len-1); + --len; + } + + // fix the case where the user attempts to delete the checksum; the + // solution is to delete the last digit as well + static const QRegExp digit(QString::fromLatin1("\\d")); + if(atEnd && input_.contains(digit) == 9 && input_[len-1] == '-') { + input_.truncate(len-2); + pos_ -= 2; + len -= 2; + } + + // now fixup the hyphens and maybe add a checksum + fixup10(input_); + len = input_.length(); // might have changed in fixup() + if(atEnd) { + pos_ = len; + } + + if(isbn.exactMatch(input_) && (len == 12 || len == 13)) { + return QValidator::Acceptable; + } else { + return QValidator::Intermediate; + } +} + +QValidator::State ISBNValidator::validate13(QString& input_, int& pos_) const { + // first check to see if it's a "perfect" ISBN13 + // A perfect ISBN13 has 13 digits + // A perfect ISBN13 may have 3 or 4 hyphens + // The final digit is the correct check sum + static const QRegExp isbn(QString::fromLatin1("(\\d-?){13,17}")); + uint len = input_.length(); + + const uint countX = input_.contains('X', false); + // two easy invalid cases are too many hyphens or 'X' + if(input_.contains('-') > 4 || countX > 1) { + return QValidator::Invalid; + } + + // now, it's not certain that we're getting a EAN-13, + // it could be a ISBN-10 from Nigeria or Indonesia + if(countX > 0 && (input_[len-1].upper() != 'X' || len > 13)) { + return QValidator::Invalid; + } + + // remember if the cursor is at the end + bool atEnd = (pos_ == static_cast<int>(len)); + + // fix the case where the user attempts to delete a character from a non-checksum + // position; the solution is to delete the checksum, but only if it's X + if(!atEnd && input_[len-1].upper() == 'X') { + input_.truncate(len-1); + --len; + } + + // fix the case where the user attempts to delete the checksum; the + // solution is to delete the last digit as well + static const QRegExp digit(QString::fromLatin1("\\d")); + const uint countN = input_.contains(digit); + if(atEnd && (countN == 12 || countN == 9) && input_[len-1] == '-') { + input_.truncate(len-2); + pos_ -= 2; + len -= 2; + } + + // now fixup the hyphens and maybe add a checksum + if(countN > 10) { + fixup13(input_); + } else { + fixup10(input_); + } + + len = input_.length(); // might have changed in fixup() + if(atEnd) { + pos_ = len; + } + + if(isbn.exactMatch(input_)) { + return QValidator::Acceptable; + } else { + return QValidator::Intermediate; + } +} + +void ISBNValidator::fixup10(QString& input_) { + if(input_.isEmpty()) { + return; + } + + //replace "x" with "X" + input_.replace('x', QString::fromLatin1("X")); + + // remove invalid chars + static const QRegExp badChars(QString::fromLatin1("[^\\d-X]")); + input_.remove(badChars); + + // special case for EAN values that start with 978 or 979. That's the case + // for things like barcode readers that essentially 'type' the string at + // once. The simulated typing has already caused the input to be normalized, + // so strip that off, as well as the generated checksum. Then continue as normal. + // If someone were to input a regular 978- or 979- ISBN _including_ the + // checksum, it will be regarded as barcode input and the input will be stripped accordingly. + // I consider the likelihood that someone wants to input an EAN to be higher than someone + // using a Nigerian ISBN and not noticing that the checksum gets added automatically. + if(input_.length() > 12 + && (input_.startsWith(QString::fromLatin1("978")) + || input_.startsWith(QString::fromLatin1("979")))) { + // Strip the first 4 characters (the invalid publisher) + input_ = input_.right(input_.length() - 3); + } + + // hyphen placement for some languages publishers is well-defined + // remove all hyphens, and insert them ourselves + // some countries have ill-defined second hyphen positions, and if + // the user inserts one, then be sure to put it back + + // Find the first hyphen. If there is none, + // input_.find('-') returns -1 and hyphen2_position = 0 + int hyphen2_position = input_.find('-') + 1; + + // Find the second one. If none, hyphen2_position = -2 + hyphen2_position = input_.find('-', hyphen2_position) - 1; + + // The second hyphen can not be in the last characters + if(hyphen2_position >= 9) { + hyphen2_position = 0; + } + + // Remove all existing hyphens. We will insert ours. + input_.remove('-'); + // the only place that 'X' can be is last spot + for(int xpos = input_.find('X'); xpos > -1; xpos = input_.find('X', xpos+1)) { + if(xpos < 9) { // remove if not 10th char + input_.remove(xpos, 1); + --xpos; + } + } + input_.truncate(10); + + // If we can find it, add the checksum + // but only if not started with 978 or 979 + if(input_.length() > 8 + && !input_.startsWith(QString::fromLatin1("978")) + && !input_.startsWith(QString::fromLatin1("979"))) { + input_[9] = checkSum10(input_); + } + + ulong range = input_.leftJustify(9, '0', true).toULong(); + + // now find which band the range falls in + uint band = 0; + while(range >= bands[band].MaxValue) { + ++band; + } + + // if we have space to put the first hyphen, do it + if(input_.length() > bands[band].First) { + input_.insert(bands[band].First, '-'); + } + + //add 1 since one "-" has already been inserted + if(bands[band].Mid != 0) { + hyphen2_position = bands[band].Mid; + if(static_cast<int>(input_.length()) > (hyphen2_position + 1)) { + input_.insert(hyphen2_position + 1, '-'); + } + // or put back user's hyphen + } else if(hyphen2_position > 0 && static_cast<int>(input_.length()) >= (hyphen2_position + 1)) { + input_.insert(hyphen2_position + 1, '-'); + } + + // add a "-" before the checkdigit and another one if the middle "-" exists + uint trueLast = bands[band].Last + 1 + (hyphen2_position > 0 ? 1 : 0); + if(input_.length() > trueLast) { + input_.insert(trueLast, '-'); + } +} + +void ISBNValidator::fixup13(QString& input_) { + if(input_.isEmpty()) { + return; + } + + // remove invalid chars + static const QRegExp badChars(QString::fromLatin1("[^\\d-]")); + input_.remove(badChars); + + // hyphen placement for some languages publishers is well-defined + // remove all hyphens, and insert them ourselves + // some countries have ill-defined second hyphen positions, and if + // the user inserts one, then be sure to put it back + + QString after = input_.mid(3); + if(after[0] == '-') { + after = after.mid(1); + } + + // Find the first hyphen. If there is none, + // input_.find('-') returns -1 and hyphen2_position = 0 + int hyphen2_position = after.find('-') + 1; + + // Find the second one. If none, hyphen2_position = -2 + hyphen2_position = after.find('-', hyphen2_position) - 1; + + // The second hyphen can not be in the last characters + if(hyphen2_position >= 9) { + hyphen2_position = 0; + } + + // Remove all existing hyphens. We will insert ours. + after.remove('-'); + after.truncate(10); + + // add the checksum + if(after.length() > 8) { + after[9] = checkSum13(input_.left(3) + after); + } + + ulong range = after.leftJustify(9, '0', true).toULong(); + + // now find which band the range falls in + uint band = 0; + while(range >= bands[band].MaxValue) { + ++band; + } + + // if we have space to put the first hyphen, do it + if(after.length() > bands[band].First) { + after.insert(bands[band].First, '-'); + } + + //add 1 since one "-" has already been inserted + if(bands[band].Mid != 0) { + hyphen2_position = bands[band].Mid; + if(static_cast<int>(after.length()) > (hyphen2_position + 1)) { + after.insert(hyphen2_position + 1, '-'); + } + // or put back user's hyphen + } else if(hyphen2_position > 0 && static_cast<int>(after.length()) >= (hyphen2_position + 1)) { + after.insert(hyphen2_position + 1, '-'); + } + + // add a "-" before the checkdigit and another one if the middle "-" exists + uint trueLast = bands[band].Last + 1 + (hyphen2_position > 0 ? 1 : 0); + if(after.length() > trueLast) { + after.insert(trueLast, '-'); + } + input_ = input_.left(3) + '-' + after; +} + +QChar ISBNValidator::checkSum10(const QString& input_) { + uint sum = 0; + uint multiplier = 10; + + // hyphens are already gone, only use first nine digits + for(uint i = 0; i < input_.length() && multiplier > 1; ++i) { + sum += input_[i].digitValue() * multiplier--; + } + sum %= 11; + sum = 11-sum; + + QChar c; + if(sum == 10) { + c = 'X'; + } else if(sum == 11) { + c = '0'; + } else { + c = QString::number(sum)[0]; + } + return c; +} + +QChar ISBNValidator::checkSum13(const QString& input_) { + uint sum = 0; + + const uint len = QMIN(12, input_.length()); + // hyphens are already gone, only use first twelve digits + for(uint i = 0; i < len; ++i) { + sum += input_[i].digitValue() * (1 + 2*(i%2)); + // multiplier goes 1, 3, 1, 3, etc... + } + sum %= 10; + sum = 10-sum; + + QChar c; + if(sum == 10) { + c = '0'; + } else { + c = QString::number(sum)[0]; + } + return c; +} + +// ISBN code from Regis Boudin +#define ISBNGRP_1DIGIT(digit, max, middle, last) \ + {((digit)*100000000) + (max), 1, middle, last} +#define ISBNGRP_2DIGIT(digit, max, middle, last) \ + {((digit)*10000000) + ((max)/10), 2, middle, last} +#define ISBNGRP_3DIGIT(digit, max, middle, last) \ + {((digit)*1000000) + ((max)/100), 3, middle, last} +#define ISBNGRP_4DIGIT(digit, max, middle, last) \ + {((digit)*100000) + ((max)/1000), 4, middle, last} +#define ISBNGRP_5DIGIT(digit, max, middle, last) \ + {((digit)*10000) + ((max)/10000), 5, middle, last} + +#define ISBNPUB_2DIGIT(grp) (((grp)+1)*1000000) +#define ISBNPUB_3DIGIT(grp) (((grp)+1)*100000) +#define ISBNPUB_4DIGIT(grp) (((grp)+1)*10000) +#define ISBNPUB_5DIGIT(grp) (((grp)+1)*1000) +#define ISBNPUB_6DIGIT(grp) (((grp)+1)*100) +#define ISBNPUB_7DIGIT(grp) (((grp)+1)*10) +#define ISBNPUB_8DIGIT(grp) (((grp)+1)*1) + +// how to format an ISBN, after categorising it into a range of numbers. +struct ISBNValidator::isbn_band ISBNValidator::bands[] = { + /* Groups 0 & 1 : English */ + ISBNGRP_1DIGIT(0, ISBNPUB_2DIGIT(19), 3, 9), + ISBNGRP_1DIGIT(0, ISBNPUB_3DIGIT(699), 4, 9), + ISBNGRP_1DIGIT(0, ISBNPUB_4DIGIT(8499), 5, 9), + ISBNGRP_1DIGIT(0, ISBNPUB_5DIGIT(89999), 6, 9), + ISBNGRP_1DIGIT(0, ISBNPUB_6DIGIT(949999), 7, 9), + ISBNGRP_1DIGIT(0, ISBNPUB_7DIGIT(9999999), 8, 9), + + ISBNGRP_1DIGIT(1, ISBNPUB_5DIGIT(54999), 6, 9), + ISBNGRP_1DIGIT(1, ISBNPUB_5DIGIT(86979), 6, 9), + ISBNGRP_1DIGIT(1, ISBNPUB_6DIGIT(998999), 7, 9), + ISBNGRP_1DIGIT(1, ISBNPUB_7DIGIT(9999999), 8, 9), + /* Group 2 : French */ + ISBNGRP_1DIGIT(2, ISBNPUB_2DIGIT(19), 3, 9), + ISBNGRP_1DIGIT(2, ISBNPUB_3DIGIT(349), 4, 9), + ISBNGRP_1DIGIT(2, ISBNPUB_5DIGIT(39999), 6, 9), + ISBNGRP_1DIGIT(2, ISBNPUB_3DIGIT(699), 4, 9), + ISBNGRP_1DIGIT(2, ISBNPUB_4DIGIT(8399), 5, 9), + ISBNGRP_1DIGIT(2, ISBNPUB_5DIGIT(89999), 6, 9), + ISBNGRP_1DIGIT(2, ISBNPUB_6DIGIT(949999), 7, 9), + ISBNGRP_1DIGIT(2, ISBNPUB_7DIGIT(9999999), 8, 9), + + /* Group 2 : German */ + ISBNGRP_1DIGIT(3, ISBNPUB_2DIGIT(19), 3, 9), + ISBNGRP_1DIGIT(3, ISBNPUB_3DIGIT(699), 4, 9), + ISBNGRP_1DIGIT(3, ISBNPUB_4DIGIT(8499), 5, 9), + ISBNGRP_1DIGIT(3, ISBNPUB_5DIGIT(89999), 6, 9), + ISBNGRP_1DIGIT(3, ISBNPUB_6DIGIT(949999), 7, 9), + ISBNGRP_1DIGIT(3, ISBNPUB_7DIGIT(9999999), 8, 9), + + ISBNGRP_1DIGIT(7, ISBNPUB_2DIGIT(99), 0, 9), + /* Group 80 : Czech */ + ISBNGRP_2DIGIT(80, ISBNPUB_2DIGIT(19), 4, 9), + ISBNGRP_2DIGIT(80, ISBNPUB_3DIGIT(699), 5, 9), + ISBNGRP_2DIGIT(80, ISBNPUB_4DIGIT(8499), 6, 9), + ISBNGRP_2DIGIT(80, ISBNPUB_5DIGIT(89999), 7, 9), + ISBNGRP_2DIGIT(80, ISBNPUB_6DIGIT(949999), 8, 9), + + /* Group 90 * Netherlands */ + ISBNGRP_2DIGIT(90, ISBNPUB_2DIGIT(19), 4, 9), + ISBNGRP_2DIGIT(90, ISBNPUB_3DIGIT(499), 5, 9), + ISBNGRP_2DIGIT(90, ISBNPUB_4DIGIT(6999), 6, 9), + ISBNGRP_2DIGIT(90, ISBNPUB_5DIGIT(79999), 7, 9), + ISBNGRP_2DIGIT(90, ISBNPUB_6DIGIT(849999), 8, 9), + ISBNGRP_2DIGIT(90, ISBNPUB_4DIGIT(8999), 6, 9), + ISBNGRP_2DIGIT(90, ISBNPUB_7DIGIT(9999999), 9, 9), + + ISBNGRP_2DIGIT(94, ISBNPUB_2DIGIT(99), 0, 9), + ISBNGRP_3DIGIT(993, ISBNPUB_2DIGIT(99), 0, 9), + ISBNGRP_4DIGIT(9989, ISBNPUB_2DIGIT(99), 0, 9), + ISBNGRP_5DIGIT(99999, ISBNPUB_2DIGIT(99), 0, 9) +}; |