diff --git a/CMakeLists.txt b/CMakeLists.txt index d626488f..a64ef12b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,270 +1,270 @@ # FindKF5 requires CMake >= 2.8.12 cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) project(Tellico) set(TELLICO_VERSION "3.99+git") set(QT_MIN_VERSION "5.4.0") # FindPoppler was added to ECM in 5.19 find_package(ECM 5.19 REQUIRED NO_MODULE) CMAKE_POLICY(SET CMP0028 OLD) if(POLICY CMP0063) cmake_policy(SET CMP0063 NEW) endif(POLICY CMP0063) -# http://www.cmake.org/Wiki/CMake_Useful_Variables +# https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/Useful-Variables # automatically add CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT_BINARY_DIR # to the include directories in every processed CMakeLists.txt set(CMAKE_INCLUDE_CURRENT_DIR TRUE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) #include(ECMAddTests) #include(ECMMarkNonGuiExecutable) #include(ECMInstallIcons) include(ECMAddAppIcon) include(ECMUninstallTarget) include(FeatureSummary) include(KDEInstallDirs) #include(KDECompilerSettings) include(KDEFrameworkCompilerSettings) include(KDECMakeSettings) ############# Options ################# option(ENABLE_AMAZON "Enable Amazon.com searching" TRUE) option(ENABLE_IMDB "Enable IMDb searching" TRUE) option(ENABLE_CDTEXT "Enable cdtext" TRUE) option(ENABLE_WEBCAM "Enable support for webcams" FALSE) option(BUILD_TESTS "Build the tests" TRUE) option(BUILD_FETCHER_TESTS "Build tests which verify data sources" FALSE) include(CheckSymbolExists) check_symbol_exists(strlwr "string.h" HAVE_STRLWR) check_symbol_exists(strupr "string.h" HAVE_STRUPR) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Widgets Xml DBus Test Network ) find_package(KF5 REQUIRED COMPONENTS Archive Codecs Config ConfigWidgets CoreAddons Crash DocTools GuiAddons IconThemes ItemModels I18n JobWidgets KIO Solid Wallet WidgetsAddons WindowSystem XmlGui ) find_package(KF5KHtml REQUIRED NO_MODULE) include(MacroBoolTo01) find_package(Gettext REQUIRED) find_package(LibXml2 REQUIRED) find_package(LibXslt REQUIRED) find_package(KF5FileMetaData) set_package_properties(KF5FileMetaData PROPERTIES DESCRIPTION "Support for reading file metadata" - URL "http://www.kde.org" + URL "https://www.kde.org" TYPE OPTIONAL) find_package(KF5NewStuff) set_package_properties(KF5NewStuff PROPERTIES DESCRIPTION "Support for fetching new templates and scripts" - URL "http://www.kde.org" + URL "https://www.kde.org" TYPE OPTIONAL) find_package(KF5Sane) set_package_properties(KF5Sane PROPERTIES DESCRIPTION "Support for adding scanned images to a collection" - URL "http://www.kde.org" + URL "https://www.kde.org" TYPE OPTIONAL) if(KF5Sane_FOUND) include_directories(${KF5Sane_INCLUDE_DIR}) endif(KF5Sane_FOUND) #macro_optional_find_package(KdepimLibs 4.5) -#macro_log_feature(KDEPIMLIBS_FOUND "kdepimlibs" "Support for using the address book and calendar for loans" "http://pim.kde.org" FALSE "4.5.0" "") +#macro_log_feature(KDEPIMLIBS_FOUND "kdepimlibs" "Support for using the address book and calendar for loans" "https://community.kde.org/PIM" FALSE "4.5.0" "") #find_package(KdepimLibs 4.5) #set_package_properties(KdepimLibs PROPERTIES # DESCRIPTION "Support for using the address book and calendar for loans" -# URL "http://pim.kde.org" +# URL "https://community.kde.org/PIM" # TYPE OPTIONAL) if(KDEPIMLIBS_FOUND) include_directories(${KDEPIMLIBS_INCLUDE_DIRS}) endif(KDEPIMLIBS_FOUND) # There is a port of libkcddb to use KF5 style linking and headers find_package(KF5Cddb) set_package_properties(KF5Cddb PROPERTIES DESCRIPTION "Support for CDDB searches" URL "https://cgit.kde.org/libkcddb.git" TYPE OPTIONAL) find_package(Taglib) set_package_properties(Taglib PROPERTIES DESCRIPTION "Support for reading multimedia files" - URL "http://taglib.github.io" + URL "https://taglib.org/" TYPE OPTIONAL) if(TAGLIB_FOUND) add_definitions(${TAGLIB_CFLAGS}) include_directories(${TAGLIB_INCLUDES}) endif(TAGLIB_FOUND) find_package(Yaz 2.0) set_package_properties(Yaz PROPERTIES DESCRIPTION "Support for searching z39.50 databases" - URL "http://www.indexdata.dk/yaz/" + URL "https://www.indexdata.com/resources/software/yaz/" TYPE OPTIONAL) if(Yaz_FOUND) include_directories(${Yaz_INCLUDE_DIRS}) endif(Yaz_FOUND) # FindPoppler is part of ECM >= 5.19 find_package(Poppler COMPONENTS Qt5) find_package(Exempi 2.0) set_package_properties(Exempi PROPERTIES DESCRIPTION "Support for reading PDF/XMP metadata" - URL "http://libopenraw.freedesktop.org/wiki/Exempi/" + URL "https://libopenraw.freedesktop.org/exempi/" TYPE OPTIONAL) if(Exempi_FOUND) include_directories(${Exempi_INCLUDE_DIRS}) endif(Exempi_FOUND) find_package(Btparse) set_package_properties(Btparse PROPERTIES DESCRIPTION "External support for parsing and processing BibTeX data files" URL "https://metacpan.org/release/Text-BibTeX" TYPE OPTIONAL) if(Btparse_FOUND) include_directories(${Btparse_INCLUDE_DIRS}) set(TELLICO_BTPARSE_LIBS ${Btparse_LIBRARIES}) else(Btparse_FOUND) set(TELLICO_BTPARSE_LIBS btparse-tellico) endif(Btparse_FOUND) find_package(CDIO) set_package_properties(CDIO PROPERTIES DESCRIPTION "Support for reading cdtext from audio CDs" URL "https://www.gnu.org/software/libcdio/" TYPE OPTIONAL) if(CDIO_FOUND) include_directories(${CDIO_INCLUDE_DIRS}) endif(CDIO_FOUND) find_package(Csv 3.0) set_package_properties(Csv PROPERTIES DESCRIPTION "External support for reading CSV files" URL "http://sourceforge.net/projects/libcsv/" TYPE OPTIONAL) if(Csv_FOUND) include_directories(${Csv_INCLUDE_DIRS}) set(TELLICO_CSV_LIBS ${Csv_LIBRARIES}) else(Csv_FOUND) set(TELLICO_CSV_LIBS csv-tellico) endif(Csv_FOUND) # webcam uses libv4l, which only works on Linux for now # Linux 2.6.38 removed the videodev.h header # libv4l 0.8.3 includes a compat header for videodev.h if(ENABLE_WEBCAM) if(NOT CMAKE_SYSTEM_NAME MATCHES "Linux") message("WARNING: Webcam support is not available on your platform") set( ENABLE_WEBCAM FALSE ) else(NOT CMAKE_SYSTEM_NAME MATCHES "Linux") pkg_check_modules(LIBV4L libv4l1>=0.6) - #macro_log_feature(LIBV4L_FOUND "libv4l" "Support for barcode scanning with a webcam" "http://hansdegoede.livejournal.com/3636.html" FALSE "" "") + #macro_log_feature(LIBV4L_FOUND "libv4l" "Support for barcode scanning with a webcam" "https://hansdegoede.livejournal.com/3636.html" FALSE "" "") set_package_properties(LIBV4L PROPERTIES DESCRIPTION "Support for barcode scanning with a webcam" - URL "http://hansdegoede.livejournal.com/3636.html" + URL "https://hansdegoede.livejournal.com/3636.html" TYPE OPTIONAL) if(LIBV4L_FOUND) if(LIBV4L_VERSION VERSION_LESS "0.8.3" AND CMAKE_SYSTEM_VERSION VERSION_GREATER "2.6.37") message("WARNING: libv4l 0.8.3 or later is required for Linux kernel 2.6.38 or later") set( ENABLE_WEBCAM FALSE ) else(LIBV4L_VERSION VERSION_LESS "0.8.3" AND CMAKE_SYSTEM_VERSION VERSION_GREATER "2.6.37") include_directories(${LIBV4L_INCLUDE_DIR}) endif(LIBV4L_VERSION VERSION_LESS "0.8.3" AND CMAKE_SYSTEM_VERSION VERSION_GREATER "2.6.37") else(LIBV4L_FOUND) set(ENABLE_WEBCAM FALSE) endif(LIBV4L_FOUND) endif(NOT CMAKE_SYSTEM_NAME MATCHES "Linux") endif(ENABLE_WEBCAM) if(CMAKE_COMPILER_IS_GNUCXX) # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor -Wno-long-long -Wextra -fno-check-new -Woverloaded-virtual") # remove -Wno-deprecated when porting to KF5 is more advanced set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor -Wno-long-long -fno-check-new -Woverloaded-virtual -Wno-deprecated -Wno-deprecated-declarations") endif(CMAKE_COMPILER_IS_GNUCXX) #add_definitions(${QT_DEFINITIONS} # ${KDE4_DEFINITIONS} # ${YAZ_CFLAGS}) add_definitions(-DQT_STL -DQT_NO_CAST_FROM_ASCII -DQT_NO_URL_CAST_FROM_STRING) remove_definitions(-DQT_NO_STL) include_directories(${LIBXML2_INCLUDE_DIR} ${LIBXSLT_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${Tellico_SOURCE_DIR}/src/config ${Tellico_SOURCE_DIR}/src/3rdparty) set(TELLICO_DATA_INSTALL_DIR ${KDE_INSTALL_DATADIR}/tellico) add_subdirectory(src) add_subdirectory(icons) add_subdirectory(xslt) add_subdirectory(doc) ########## Wrap tests results around the tests done within the source macro_bool_to_01(TAGLIB_FOUND HAVE_TAGLIB) macro_bool_to_01(Poppler_Qt5_FOUND HAVE_POPPLER) macro_bool_to_01(Exempi_FOUND HAVE_EXEMPI) macro_bool_to_01(Yaz_FOUND HAVE_YAZ) macro_bool_to_01(KF5Sane_FOUND HAVE_KSANE) macro_bool_to_01(Libkcddb_FOUND HAVE_KCDDB) macro_bool_to_01(KF5Cddb_FOUND HAVE_KF5KCDDB) macro_bool_to_01(KDEPIMLIBS_FOUND HAVE_KABC) macro_bool_to_01(KDEPIMLIBS_FOUND HAVE_KCAL) macro_bool_to_01(LIBV4L_FOUND HAVE_V4L) macro_bool_to_01(KF5NewStuff_FOUND ENABLE_KNEWSTUFF3) macro_bool_to_01(Btparse_FOUND HAVE_LIBBTPARSE) macro_bool_to_01(CDIO_FOUND HAVE_CDIO) macro_bool_to_01(Csv_FOUND HAVE_LIBCSV) macro_bool_to_01(KF5FileMetaData_FOUND HAVE_KFILEMETADATA) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) ########### install files ############### install(PROGRAMS org.kde.tellico.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES tellico.dtd tellico.tips DESTINATION ${TELLICO_DATA_INSTALL_DIR}) install(FILES tellico.xml DESTINATION ${KDE_INSTALL_MIMEDIR}) install(FILES org.kde.tellico.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/barcode/barcode.cpp b/src/barcode/barcode.cpp index ae400efa..20b7bfb8 100644 --- a/src/barcode/barcode.cpp +++ b/src/barcode/barcode.cpp @@ -1,882 +1,882 @@ /*************************************************************************** Copyright (C) 2007-2009 Sebastian Held ***************************************************************************/ /*************************************************************************** * * - * ### based on BaToo: http://people.inf.ethz.ch/adelmanr/batoo/ ### * + * based on BaToo: http://www.vs.inf.ethz.ch/res/show.html?what=barcode * * * * 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 . * * * ***************************************************************************/ -// includes code from http://v4l2spec.bytesex.org/spec/a16323.htm +// includes code from https://www.linuxtv.org/downloads/v4l-dvb-apis-old/capture-example.html #include "barcode.h" #include #include #include #include using barcodeRecognition::barcodeRecognitionThread; using barcodeRecognition::Barcode_EAN13; using barcodeRecognition::Decoder_EAN13; using barcodeRecognition::MatchMakerResult; barcodeRecognitionThread::barcodeRecognitionThread() { m_stop = false; m_barcode_v4l = new barcode_v4l(); } barcodeRecognitionThread::~barcodeRecognitionThread() { delete m_barcode_v4l; } bool barcodeRecognitionThread::isWebcamAvailable() { return m_barcode_v4l->isOpen(); } QSize barcodeRecognitionThread::getPreviewSize() const { return QSize( 320,240 ); } void barcodeRecognitionThread::run() { bool stop; m_stop_mutex.lock(); stop = m_stop; m_stop_mutex.unlock(); if (!isWebcamAvailable()) stop = true; Barcode_EAN13 old; while (!stop) { QImage img; m_barcode_img_mutex.lock(); if (m_barcode_img.isNull()) img = m_barcode_v4l->grab_one2(); else { img = m_barcode_img; m_barcode_img = QImage(); } m_barcode_img_mutex.unlock(); // //DEBUG // img.load( "/home/sebastian/black.png" ); // m_stop = true; // //DEBUG if (!img.isNull()) { QImage preview = img.scaled( 320, 240, Qt::KeepAspectRatio ); emit gotImage( preview ); Barcode_EAN13 barcode = recognize( img ); if (barcode.isValid() && (old != barcode)) { emit recognized( barcode.toString() ); old = barcode; } } msleep( 10 ); // reduce load m_stop_mutex.lock(); stop = m_stop; m_stop_mutex.unlock(); } } void barcodeRecognitionThread::stop() { // attention! This function is called from GUI context m_stop_mutex.lock(); m_stop = true; m_stop_mutex.unlock(); } void barcodeRecognitionThread::recognizeBarcode( QImage img ) { // attention! This function is called from GUI context m_barcode_img_mutex.lock(); m_barcode_img = img; m_barcode_img_mutex.unlock(); } Barcode_EAN13 barcodeRecognitionThread::recognize( QImage img ) { // PARAMETERS: int amount_scanlines = 30; int w = img.width(); int h = img.height(); // the array which will contain the result: QVector< QVector > numbers( amount_scanlines, QVector(13,-1) ); // no init in java source!!!!!!!!! // generate and initialize the array that will contain all detected // digits at a specific code position: int possible_numbers[10][13][2]; for (int i = 0; i < 10; i++) { for (int j = 0; j < 13; j++) { possible_numbers[i][j][0] = -1; possible_numbers[i][j][1] = 0; } } int successfull_lines = 0; // try to detect the barcode along scanlines: for (int i = 0; i < amount_scanlines; i++) { int x1 = 0; int y = (h / amount_scanlines) * i; int x2 = w - 1; // try to recognize a barcode along that path: Barcode_EAN13 ean13_code = recognizeCode( img, x1, x2, y ); numbers[i] = ean13_code.getNumbers(); if (ean13_code.isValid()) { successfull_lines++; // add the recognized digits to the array of possible numbers: addNumberToPossibleNumbers( numbers[i], possible_numbers, true ); } else { numbers[i] = ean13_code.getNumbers(); // add the recognized digits to the array of possible numbers: addNumberToPossibleNumbers(numbers[i], possible_numbers, false); } #ifdef BarcodeDecoder_DEBUG // show the information that has been recognized along the scanline: qDebug( "Scanline %i result: %s\n", i, ean13_code.toString().latin1() ); #endif } // sort the detected digits at each code position, in accordance to the // amount of their detection: sortDigits(possible_numbers); #ifdef BarcodeDecoder_DEBUG fprintf( stderr, "detected digits:\n" ); printArray( possible_numbers, 0 ); fprintf( stderr, "# of their occurrence:\n" ); printArray( possible_numbers, 1 ); #endif // get the most likely barcode: Barcode_EAN13 code = extractBarcode(possible_numbers); return code; } void barcodeRecognitionThread::printArray( int array[10][13][2], int level ) { for (int i = 0; i < 10; i++) { QString temp; temp = QString::number( i ) + QLatin1String(" : "); for (int j = 0; j < 13; j++) { if (array[i][j][level] == -1) temp += QLatin1String("x "); else temp += QString::number( array[i][j][level] ) + QLatin1String(" "); } qDebug() << temp; } } barcodeRecognition::Barcode_EAN13 barcodeRecognitionThread::recognizeCode( QImage img, int x1, int x2, int y ) { QVector raw_path(x2-x1+1); for (int x=x1; x<=x2; x++) raw_path[x-x1] = img.pixel(x,y); // convert the given path into a string of black and white pixels: QVector string = transformPathToBW( raw_path ); // convert the string of black&white pixels into a list, containing // information about the black and white fields // first indes = field nr. // second index: 0 = color of the field // 1 = field length QVector< QVector > fields = extractFieldInformation( string ); // try to recognize a EAN13 code: Barcode_EAN13 barcode = Decoder_EAN13::recognize( fields ); return barcode; } void barcodeRecognitionThread::addNumberToPossibleNumbers( QVector number, int possible_numbers[10][13][2], bool correct_code ) { int i; bool digit_contained; for (int j = 0; j < 13; j++) { if (number[j] >= 0) { i = 0; digit_contained = false; while ((i < 10) && (possible_numbers[i][j][0] >= 0)) { if (possible_numbers[i][j][0] == number[j]) { digit_contained = true; if (correct_code) possible_numbers[i][j][1] = possible_numbers[i][j][1] + 100; else possible_numbers[i][j][1]++; break; } i++; } if ((i < 10) && (!digit_contained)) { // add new digit: possible_numbers[i][j][0] = number[j]; if (correct_code) possible_numbers[i][j][1] = possible_numbers[i][j][1] + 100; else possible_numbers[i][j][1]++; } } } } void barcodeRecognitionThread::sortDigits( int possible_numbers[10][13][2] ) { int i; int temp_value; int temp_occurrence; bool changes; for (int j = 0; j < 13; j++) { i = 1; changes = false; while (true) { if ((possible_numbers[i - 1][j][0] >= 0) && (possible_numbers[i][j][0] >= 0)) { if (possible_numbers[i - 1][j][1] < possible_numbers[i][j][1]) { temp_value = possible_numbers[i - 1][j][0]; temp_occurrence = possible_numbers[i - 1][j][1]; possible_numbers[i - 1][j][0] = possible_numbers[i][j][0]; possible_numbers[i - 1][j][1] = possible_numbers[i][j][1]; possible_numbers[i][j][0] = temp_value; possible_numbers[i][j][1] = temp_occurrence; changes = true; } } if ((possible_numbers[i][j][0] < 0) || (i >= 9)) { if (!changes) break; else { i = 1; changes = false; } } else i++; } } } Barcode_EAN13 barcodeRecognitionThread::extractBarcode( int possible_numbers[10][13][2] ) { // create and initialize the temporary variables: QVector temp_code(13); for (int i = 0; i < 13; i++) temp_code[i] = possible_numbers[0][i][0]; #ifdef Barcode_DEBUG fprintf( stderr, "barcodeRecognitionThread::extractBarcode(): " ); for (int i=0; i<13; i++) fprintf( stderr, "%i", temp_code[i] ); fprintf( stderr, "\n" ); #endif return Barcode_EAN13(temp_code); } Barcode_EAN13 barcodeRecognitionThread::detectValidBarcode ( int possible_numbers[10][13][2], int max_amount_of_considered_codes ) { // create and initialize the temporary variables: QVector temp_code(13); for ( int i = 0; i < 13; i++ ) temp_code[i] = possible_numbers[0][i][0]; int alternative_amount = 0; QVector counter( 13 ); // no init in java source!!! int counter_nr = 11; // check if there is at least one complete code present: for ( int i = 0; i < 13; i++ ) { // exit and return the "most likely" code parts: if ( temp_code[i] < 0 ) return Barcode_EAN13( temp_code ); } // if there is at least one complete node, try to detect a valid barcode: while ( alternative_amount < max_amount_of_considered_codes ) { // fill the temporary code array with one possible version: for ( int i = 0; i < 13; i++ ) temp_code[i] = possible_numbers[counter[i]][i][0]; alternative_amount++; // check if this version represents a valid code: if (isValid( temp_code )) return Barcode_EAN13( temp_code ); // increment the counters: if ( ( counter[counter_nr] < 9 ) && ( possible_numbers[counter[counter_nr] + 1][counter_nr][0] >= 0 ) ) { // increment the actual counter. counter[counter_nr]++; } else { // check if we have reached the end and no valid barcode has been found: if ( counter_nr == 1 ) { // exit and return the "most likely" code parts: for ( int i = 0; i < 13; i++ ) temp_code[i] = possible_numbers[0][i][0]; return Barcode_EAN13( temp_code ); } else { // reset the actual counter and increment the next one(s): counter[counter_nr] = 0; while ( true ) { if ( counter_nr > 2 ) counter_nr--; else { for ( int i = 0; i < 13; i++ ) temp_code[i] = possible_numbers[0][i][0]; return Barcode_EAN13( temp_code ); } if ( counter[counter_nr] < 9 ) { counter[counter_nr]++; if ( possible_numbers[counter[counter_nr]][counter_nr][0] < 0 ) counter[counter_nr] = 0; else break; } else counter[counter_nr] = 0; } counter_nr = 12; } } } for ( int i = 0; i < 13; i++ ) temp_code[i] = possible_numbers[0][i][0]; return Barcode_EAN13( temp_code ); } bool barcodeRecognitionThread::isValid( int numbers[13] ) { QVector temp(13); for (int i=0; i<13; i++) temp[i] = numbers[i]; return isValid( temp ); } bool barcodeRecognitionThread::isValid( QVector numbers ) { Q_ASSERT( numbers.count() == 13 ); // calculate the checksum of the barcode: int sum1 = numbers[0] + numbers[2] + numbers[4] + numbers[6] + numbers[8] + numbers[10]; int sum2 = 3 * (numbers[1] + numbers[3] + numbers[5] + numbers[7] + numbers[9] + numbers[11]); int checksum_value = sum1 + sum2; int checksum_digit = 10 - (checksum_value % 10); if (checksum_digit == 10) checksum_digit = 0; #ifdef Barcode_DEBUG fprintf( stderr, "barcodeRecognitionThread::isValid(): " ); for (int i=0; i<13; i++) fprintf( stderr, "%i", numbers[i] ); fprintf( stderr, "\n" ); #endif return (numbers[12] == checksum_digit); } QVector barcodeRecognitionThread::transformPathToBW( QVector line ) { int w = line.count(); QVector bw_line(w,0); bw_line[0] = 255; // create greyscale values: QVector grey_line(w,0); int average_illumination = 0; for (int x = 0; x < w; x++) { grey_line[x] = (qRed(line.at(x)) + qGreen(line.at(x)) + qBlue(line.at(x))) / 3; average_illumination = average_illumination + grey_line[x]; } average_illumination = average_illumination / w; // perform the binarization: int range = w / 20; // temp values: int moving_sum; int moving_average; int v1_index = -range + 1; int v2_index = range; int v1 = grey_line[0]; int v2 = grey_line[range]; int current_value; int comparison_value; // initialize the moving sum: moving_sum = grey_line[0] * range; for (int i = 0; i < range; i++) moving_sum = moving_sum + grey_line[i]; // apply the adaptive thresholding algorithm: for (int i = 1; i < w - 1; i++) { if (v1_index > 0) v1 = grey_line[v1_index]; if (v2_index < w) v2 = grey_line[v2_index]; else v2 = grey_line[w - 1]; moving_sum = moving_sum - v1 + v2; moving_average = moving_sum / (range << 1); v1_index++; v2_index++; current_value = (grey_line[i - 1] + grey_line[i]) >> 1; // decide if the current pixel should be black or white: comparison_value = (3 * moving_average + average_illumination) >> 2; if ((current_value < comparison_value - 3)) bw_line[i] = 0; else bw_line[i] = 255; } // filter the values: (remove too small fields) if (w >= 640) { for (int x = 1; x < w - 1; x++) { if ((bw_line[x] != bw_line[x - 1]) && (bw_line[x] != bw_line[x + 1])) bw_line[x] = bw_line[x - 1]; } } QVector ret(w,0); for (int i=0; i > barcodeRecognitionThread::extractFieldInformation( QVector string ) { QVector< QVector > temp_fields( string.count(), QVector(2,0) ); if (string.count() == 0) return QVector< QVector >(); int field_counter = 0; int last_value = string.at(0); int last_fields = 1; for (int i = 1; i < string.size(); i++) { if ((string.at(i) == last_value) && (i < string.size() - 1)) { last_fields++; } else { // create new field entry: temp_fields[field_counter][0] = last_value; temp_fields[field_counter][1] = last_fields; last_value = string.at(i); last_fields = 0; field_counter++; } } temp_fields.resize( field_counter ); #ifdef Barcode_DEBUG fprintf( stderr, "barcodeRecognitionThread::extractFieldInformation(): " ); for (int i=0; i code ) { setCode( code ); } //ok void Barcode_EAN13::setCode( QVector code ) { if (code.count() != 13) { m_numbers.clear(); m_numbers.insert(0,13,-1); m_null = true; return; } m_numbers = code; m_null = false; } //ok bool Barcode_EAN13::isValid() const { if (m_null) return false; for (int i = 0; i < 13; i++) if ((m_numbers[i] < 0) || (m_numbers[i] > 9)) return false; // calculate the checksum of the barcode: int sum1 = m_numbers[0] + m_numbers[2] + m_numbers[4] + m_numbers[6] + m_numbers[8] + m_numbers[10]; int sum2 = 3 * (m_numbers[1] + m_numbers[3] + m_numbers[5] + m_numbers[7] + m_numbers[9] + m_numbers[11]); int checksum_value = sum1 + sum2; int checksum_digit = 10 - (checksum_value % 10); if (checksum_digit == 10) checksum_digit = 0; return (m_numbers[12] == checksum_digit); } //ok QVector Barcode_EAN13::getNumbers() const { return m_numbers; } //ok QString Barcode_EAN13::toString() const { QString s; for (int i = 0; i < 13; i++) if ((m_numbers[i] >= 0) && (m_numbers[i] <= 9)) s += QString::number(m_numbers[i]); else s += QChar::fromLatin1('?'); return s; } //ok bool Barcode_EAN13::operator!= ( const Barcode_EAN13 &code ) { if (m_null != code.m_null) return true; if (!m_null) for (int i=0; i<13; i++) if (m_numbers[i] != code.m_numbers[i]) return true; return false; } //ok Barcode_EAN13 Decoder_EAN13::recognize( QVector< QVector > fields ) { // try to extract the encoded information from the field series: QVector numbers = decode( fields, 0, fields.count() ); Barcode_EAN13 barcode( numbers ); // return the results: return barcode; } QVector Decoder_EAN13::decode( QVector< QVector > fields, int start_i, int end_i ) { // determine the length of the path in pixels int length = 0; for (int i = 0; i < fields.size(); i++) length += fields.at(i).at(1); // set the parameters accordingly: int max_start_sentry_bar_differences; int max_unit_length; int min_unit_length; if (length <= 800) { max_start_sentry_bar_differences = 6; max_unit_length = 10; min_unit_length = 1; } else { max_start_sentry_bar_differences = 30; max_unit_length = 50; min_unit_length = 1; } // consistency checks: if (fields.count() <= 0) return QVector(); if (start_i > end_i - 3) return QVector(); if (end_i - start_i < 30) return QVector(); // (just a rough value) // relevant indexes: int start_sentinel_i; int end_sentinel_i; int left_numbers_i; int middle_guard_i; int right_numbers_i; // results: QVector numbers( 13, -1 ); // the java source does no initialization // determine the relevant positions: // Try to detect the start sentinel (a small black-white-black serie): start_sentinel_i = -1; for (int i = start_i; i < end_i - 56; i++) { if (fields[i][0] == 0) { if ((fields[i][1] >= min_unit_length) && (fields[i][1] <= max_unit_length)) { if ((qAbs(fields[i][1] - fields[i + 1][1]) <= max_start_sentry_bar_differences) && (qAbs(fields[i][1] - fields[i + 2][1]) <= max_start_sentry_bar_differences) && (fields[i + 3][1] < fields[i][1] << 3)) { start_sentinel_i = i; break; } } } } #ifdef Decoder_EAN13_DEBUG fprintf( stderr, "start_sentinal_index: %i\n", start_sentinel_i ); #endif if (start_sentinel_i < 0) return QVector(); // calculate the other positions: left_numbers_i = start_sentinel_i + 3; middle_guard_i = left_numbers_i + 6 * 4; right_numbers_i = middle_guard_i + 5; end_sentinel_i = right_numbers_i + 6 * 4; if (end_sentinel_i + 3 > end_i) return QVector(); // calculate the average (pixel) length of a bar that is one unit wide: // (a complete barcode consists out of 95 length units) int temp_length = 0; int field_amount = (end_sentinel_i - start_sentinel_i + 3); for (int i = start_sentinel_i; i < start_sentinel_i + field_amount; i++) temp_length += fields[i][1]; #ifdef Decoder_EAN13_DEBUG float unit_length = (float) ((float) temp_length / 95.0f); fprintf( stderr, "unit_width: %f\n", unit_length ); #endif QVector< QVector > current_number_field( 4, QVector(2,0) ); if (left_numbers_i + 1 > end_i) return QVector(); // test the side from which we are reading the barcode: for (int j = 0; j < 4; j++) { current_number_field[j][0] = fields[left_numbers_i + j][0]; current_number_field[j][1] = fields[left_numbers_i + j][1]; } MatchMakerResult matchMakerResult = recognizeNumber( current_number_field, BOTH_TABLES ); if (matchMakerResult.isEven()) { // we are reading the barcode from the back side: // use the already obtained information: numbers[12] = matchMakerResult.getDigit(); // try to recognize the "right" numbers: int counter = 11; for (int i = left_numbers_i + 4; i < left_numbers_i + 24; i = i + 4) { for (int j = 0; j < 4; j++) { current_number_field[j][0] = fields[i + j][0]; current_number_field[j][1] = fields[i + j][1]; } matchMakerResult = recognizeNumber(current_number_field, EVEN_TABLE); numbers[counter] = matchMakerResult.getDigit(); counter--; } bool parity_pattern[6]; // true = even, false = odd //(counter has now the value 6) // try to recognize the "left" numbers: for (int i = right_numbers_i; i < right_numbers_i + 24; i = i + 4) { for (int j = 0; j < 4; j++) { current_number_field[j][0] = fields[i + j][0]; current_number_field[j][1] = fields[i + j][1]; } matchMakerResult = recognizeNumber(current_number_field, BOTH_TABLES); numbers[counter] = matchMakerResult.getDigit(); parity_pattern[counter-1] = !matchMakerResult.isEven(); counter--; } // try to determine the system code: matchMakerResult = recognizeSystemCode(parity_pattern); numbers[0] = matchMakerResult.getDigit(); } else { // we are reading the barcode from the "correct" side: bool parity_pattern[6]; // true = even, false = odd // use the already obtained information: numbers[1] = matchMakerResult.getDigit(); parity_pattern[0] = matchMakerResult.isEven(); // try to recognize the left numbers: int counter = 2; for (int i = left_numbers_i + 4; i < left_numbers_i + 24; i = i + 4) { for (int j = 0; j < 4; j++) { current_number_field[j][0] = fields[i + j][0]; current_number_field[j][1] = fields[i + j][1]; } matchMakerResult = recognizeNumber(current_number_field, BOTH_TABLES); numbers[counter] = matchMakerResult.getDigit(); parity_pattern[counter-1] = matchMakerResult.isEven(); counter++; } // try to determine the system code: matchMakerResult = recognizeSystemCode(parity_pattern); numbers[0] = matchMakerResult.getDigit(); // try to recognize the right numbers: counter = 0; for (int i = right_numbers_i; i < right_numbers_i + 24; i = i + 4) { for (int j = 0; j < 4; j++) { current_number_field[j][0] = fields[i + j][0]; current_number_field[j][1] = fields[i + j][1]; } matchMakerResult = recognizeNumber(current_number_field, ODD_TABLE); numbers[counter + 7] = matchMakerResult.getDigit(); counter++; } } return numbers; } MatchMakerResult Decoder_EAN13::recognizeNumber( QVector< QVector > fields, int code_table_to_use) { // convert the pixel lengths of the four black&white fields into // normed values that have together a length of 70; int pixel_sum = fields[0][1] + fields[1][1] + fields[2][1] + fields[3][1]; int b[4]; for (int i = 0; i < 4; i++) { b[i] = ::round((((float) fields[i][1]) / ((float) pixel_sum)) * 70); } #ifdef Decoder_EAN13_DEBUG fprintf( stderr, "Recognize Number (code table to use: %i):\n", code_table_to_use ); fprintf( stderr, "lengths: %i %i %i %i\n", fields[0][1], fields[1][1], fields[2][1], fields[3][1] ); fprintf( stderr, "normed lengths: %i %i %i %i\n", b[0], b[1], b[2], b[3] ); #endif // try to detect the digit that is encoded by the set of four normed bar lengths: int max_difference_for_acceptance = 60; int temp; int even_min_difference = 100000; int even_min_difference_index = 0; int odd_min_difference = 100000; int odd_min_difference_index = 0; if ((code_table_to_use == BOTH_TABLES)||(code_table_to_use == EVEN_TABLE)) { QVector even_differences(10,0); for (int i = 0; i < 10; i++) { for (int j = 0; j < 4; j++) { // calculate the differences in the even group: temp = b[j] - code_even[i][j]; if (temp < 0) even_differences[i] = even_differences[i] + ((-temp) << 1); else even_differences[i] = even_differences[i] + (temp << 1); } if (even_differences[i] < even_min_difference) { even_min_difference = even_differences[i]; even_min_difference_index = i; } } } if ((code_table_to_use == BOTH_TABLES) || (code_table_to_use == ODD_TABLE)) { QVector odd_differences(10,0); for (int i = 0; i < 10; i++) { for (int j = 0; j < 4; j++) { // calculate the differences in the odd group: temp = b[j] - code_odd[i][j]; if (temp < 0) odd_differences[i] = odd_differences[i] + ((-temp) << 1); else odd_differences[i] = odd_differences[i] + (temp << 1); } if (odd_differences[i] < odd_min_difference) { odd_min_difference = odd_differences[i]; odd_min_difference_index = i; } } } // select the digit and parity with the lowest difference to the found pattern: if (even_min_difference <= odd_min_difference) { if (even_min_difference < max_difference_for_acceptance) return MatchMakerResult( true, even_min_difference_index ); } else { if (odd_min_difference < max_difference_for_acceptance) return MatchMakerResult( false, odd_min_difference_index ); } return MatchMakerResult( false, -1 ); } MatchMakerResult Decoder_EAN13::recognizeSystemCode( bool parity_pattern[6] ) { // search for a fitting parity pattern: bool fits = false; for (int i = 0; i < 10; i++) { fits = true; for (int j = 0; j < 6; j++) { if (parity_pattern_list[i][j] != parity_pattern[j]) { fits = false; break; } } if (fits) return MatchMakerResult( false, i ); } return MatchMakerResult( false, -1 ); } //ok MatchMakerResult::MatchMakerResult( bool even, int digit ) { m_even = even; m_digit = digit; } diff --git a/src/barcode/barcode.h b/src/barcode/barcode.h index 492d7a18..e7a8c877 100644 --- a/src/barcode/barcode.h +++ b/src/barcode/barcode.h @@ -1,146 +1,146 @@ /*************************************************************************** Copyright (C) 2007-2009 Sebastian Held ***************************************************************************/ /*************************************************************************** * * - * ### based on BaToo: http://people.inf.ethz.ch/adelmanr/batoo/ ### * + * based on BaToo: http://www.vs.inf.ethz.ch/res/show.html?what=barcode * * * * 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 BARCODE_H #define BARCODE_H #include "barcode_v4l.h" #include #include #include #include #include //#define BarcodeDecoder_DEBUG //#define Decoder_EAN13_DEBUG namespace barcodeRecognition { static int code_odd[][4] = { { 30, 20, 10, 10 }, { 20, 20, 20, 10 }, { 20, 10, 20, 20 }, { 10, 40, 10, 10 }, { 10, 10, 30, 20 }, { 10, 20, 30, 10 }, { 10, 10, 10, 40 }, { 10, 30, 10, 20 }, { 10, 20, 10, 30 }, { 30, 10, 10, 20 } }; static int code_even[][4] = { { 10, 10, 20, 30 }, { 10, 20, 20, 20 }, { 20, 20, 10, 20 }, { 10, 10, 40, 10 }, { 20, 30, 10, 10 }, { 10, 30, 20, 10 }, { 40, 10, 10, 10 }, { 20, 10, 30, 10 }, { 30, 10, 20, 10 }, { 20, 10, 10, 30 } }; static bool parity_pattern_list[][6] = { { false, false, false, false, false, false }, { false, false, true, false, true, true }, { false, false, true, true, false, true }, { false, false, true, true, true, false }, { false, true, false, false, true, true }, { false, true, true, false, false, true }, { false, true, true, true, false, false }, { false, true, false, true, false, true }, { false, true, false, true, true, false }, { false, true, true, false, true, false } }; class Barcode_EAN13 { public: Barcode_EAN13(); Barcode_EAN13( QVector code ); bool isNull() const { return m_null; } bool isValid() const; QVector getNumbers() const; void setCode( QVector code ); QString toString() const; bool operator!= ( const Barcode_EAN13 &code ); protected: QVector m_numbers; bool m_null; }; class MatchMakerResult { public: MatchMakerResult( bool even, int digit ); bool isEven() const {return m_even;} int getDigit() const {return m_digit;} protected: int m_digit; bool m_even; }; class Decoder_EAN13 { public: enum { BOTH_TABLES = 0, EVEN_TABLE = 1, ODD_TABLE = 2 }; static Barcode_EAN13 recognize( QVector< QVector > fields ); static QVector decode( QVector< QVector > fields, int start_i, int end_i ); static MatchMakerResult recognizeNumber( QVector< QVector > fields, int code_table_to_use ); static MatchMakerResult recognizeSystemCode( bool parity_pattern[6] ); }; /** \brief this thread handles barcode recognition using webcams * @author Sebastian Held */ class barcodeRecognitionThread : public QThread { Q_OBJECT public: barcodeRecognitionThread(); ~barcodeRecognitionThread(); virtual void run() Q_DECL_OVERRIDE; void stop(); void recognizeBarcode( QImage img ); bool isWebcamAvailable(); QSize getPreviewSize() const; Q_SIGNALS: void recognized( QString barcode ); void gotImage( const QImage &img ); protected: volatile bool m_stop; QImage m_barcode_img; QMutex m_stop_mutex, m_barcode_img_mutex; barcode_v4l *m_barcode_v4l; Barcode_EAN13 recognize( QImage img ); Barcode_EAN13 recognizeCode( QImage img, int x1, int x2, int y ); void addNumberToPossibleNumbers( QVector number, int possible_numbers[10][13][2], bool correct_code ); void sortDigits( int possible_numbers[10][13][2] ); Barcode_EAN13 extractBarcode( int possible_numbers[10][13][2] ); QVector transformPathToBW( QVector line); QVector< QVector > extractFieldInformation( QVector string ); Barcode_EAN13 detectValidBarcode ( int possible_numbers[10][13][2], int max_amount_of_considered_codes ); bool isValid( int numbers[13] ); bool isValid( QVector numbers ); void printArray( int array[10][13][2], int level ); }; } #endif diff --git a/src/collections/stampcollection.cpp b/src/collections/stampcollection.cpp index f12d4212..dff3d6fd 100644 --- a/src/collections/stampcollection.cpp +++ b/src/collections/stampcollection.cpp @@ -1,144 +1,144 @@ /*************************************************************************** Copyright (C) 2003-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 "stampcollection.h" #include namespace { static const char* stamp_general = I18N_NOOP("General"); static const char* stamp_condition = I18N_NOOP("Condition"); static const char* stamp_personal = I18N_NOOP("Personal"); } using Tellico::Data::StampCollection; StampCollection::StampCollection(bool addDefaultFields_, const QString& title_) : Collection(title_.isEmpty() ? i18n("My Stamps") : title_) { setDefaultGroupField(QStringLiteral("denomination")); if(addDefaultFields_) { addFields(defaultFields()); } } Tellico::Data::FieldList StampCollection::defaultFields() { FieldList list; FieldPtr field; field = Field::createDefaultField(Field::TitleField); field->setProperty(QStringLiteral("template"), QStringLiteral("%{year} %{description} %{denomination}")); field->setFlags(Field::NoDelete | Field::Derived); list.append(field); field = new Field(QStringLiteral("description"), i18n("Description")); field->setCategory(i18n(stamp_general)); field->setFlags(Field::AllowCompletion | Field::AllowGrouped); field->setFormatType(FieldFormat::FormatTitle); list.append(field); field = new Field(QStringLiteral("denomination"), i18n("Denomination")); field->setCategory(i18n(stamp_general)); field->setFlags(Field::AllowCompletion | Field::AllowGrouped); list.append(field); field = new Field(QStringLiteral("country"), i18n("Country")); field->setCategory(i18n(stamp_general)); field->setFormatType(FieldFormat::FormatPlain); field->setFlags(Field::AllowGrouped); list.append(field); field = new Field(QStringLiteral("year"), i18n("Issue Year"), Field::Number); field->setCategory(i18n(stamp_general)); field->setFlags(Field::AllowMultiple | Field::AllowGrouped); list.append(field); field = new Field(QStringLiteral("color"), i18n("Color")); field->setCategory(i18n(stamp_general)); field->setFlags(Field::AllowCompletion | Field::AllowGrouped); list.append(field); field = new Field(QStringLiteral("scott"), i18n("Scott#")); field->setCategory(i18n(stamp_general)); list.append(field); QStringList grade = i18nc("Stamp grade levels - " "Superb,Extremely Fine,Very Fine,Fine,Average,Poor", "Superb,Extremely Fine,Very Fine,Fine,Average,Poor") .split(QRegExp(QLatin1String("\\s*,\\s*")), QString::SkipEmptyParts); field = new Field(QStringLiteral("grade"), i18n("Grade"), grade); field->setCategory(i18n(stamp_condition)); field->setFlags(Field::AllowGrouped); list.append(field); field = new Field(QStringLiteral("cancelled"), i18n("Cancelled"), Field::Bool); field->setCategory(i18n(stamp_condition)); list.append(field); - /* TRANSLATORS: See http://en.wikipedia.org/wiki/Stamp_hinge */ + /* TRANSLATORS: See https://en.wikipedia.org/wiki/Stamp_hinge */ field = new Field(QStringLiteral("hinged"), i18n("Hinged")); field->setCategory(i18n(stamp_condition)); field->setFlags(Field::AllowCompletion | Field::AllowGrouped); list.append(field); field = new Field(QStringLiteral("centering"), i18n("Centering")); field->setCategory(i18n(stamp_condition)); field->setFlags(Field::AllowCompletion | Field::AllowGrouped); list.append(field); field = new Field(QStringLiteral("gummed"), i18n("Gummed")); field->setCategory(i18n(stamp_condition)); field->setFlags(Field::AllowCompletion | Field::AllowGrouped); list.append(field); field = new Field(QStringLiteral("pur_date"), i18n("Purchase Date")); field->setCategory(i18n(stamp_personal)); field->setFormatType(FieldFormat::FormatDate); list.append(field); field = new Field(QStringLiteral("pur_price"), i18n("Purchase Price")); field->setCategory(i18n(stamp_personal)); list.append(field); field = new Field(QStringLiteral("location"), i18n("Location")); field->setCategory(i18n(stamp_personal)); field->setFlags(Field::AllowCompletion | Field::AllowGrouped); field->setFormatType(FieldFormat::FormatPlain); list.append(field); field = new Field(QStringLiteral("gift"), i18n("Gift"), Field::Bool); field->setCategory(i18n(stamp_personal)); list.append(field); field = new Field(QStringLiteral("image"), i18n("Image"), Field::Image); list.append(field); field = new Field(QStringLiteral("comments"), i18n("Comments"), Field::Para); list.append(field); list.append(Field::createDefaultField(Field::IDField)); list.append(Field::createDefaultField(Field::CreatedDateField)); list.append(Field::createDefaultField(Field::ModifiedDateField)); return list; } diff --git a/src/fetch/springerfetcher.cpp b/src/fetch/springerfetcher.cpp index 0bc764d8..75838731 100644 --- a/src/fetch/springerfetcher.cpp +++ b/src/fetch/springerfetcher.cpp @@ -1,195 +1,195 @@ /*************************************************************************** Copyright (C) 2012 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 "springerfetcher.h" #include "../entry.h" #include "../utils/isbnvalidator.h" #include "../tellico_debug.h" #include #include #include #include #include #include #include namespace { static const char* SPRINGER_BASE_URL = "http://api.springer.com/metadata/pam"; static const char* SPRINGER_API_KEY = "m2z42cbw68qhhjcrm8tbj2hc"; static const int SPRINGER_QUERY_COUNT = 10; } using namespace Tellico; using Tellico::Fetch::SpringerFetcher; SpringerFetcher::SpringerFetcher(QObject* parent_) : XMLFetcher(parent_), m_start(0), m_total(-1) { setLimit(SPRINGER_QUERY_COUNT); setXSLTFilename(QStringLiteral("springer2tellico.xsl")); } SpringerFetcher::~SpringerFetcher() { } QString SpringerFetcher::source() const { return m_name.isEmpty() ? defaultName() : m_name; } QString SpringerFetcher::attribution() const { return i18n("This data is licensed under specific terms.", - QLatin1String("http://dev.springer.com/apps/tos")); + QLatin1String("https://dev.springernature.com/")); } bool SpringerFetcher::canSearch(FetchKey k) const { return k == Title || k == Person || k == Keyword || k == ISBN || k == DOI || k == Raw; } bool SpringerFetcher::canFetch(int type) const { return type == Data::Collection::Bibtex; } void SpringerFetcher::readConfigHook(const KConfigGroup&) { } void SpringerFetcher::resetSearch() { m_start = 0; m_total = -1; } QUrl SpringerFetcher::searchUrl() { QUrl u(QString::fromLatin1(SPRINGER_BASE_URL)); QUrlQuery q; q.addQueryItem(QStringLiteral("api_key"), QLatin1String(SPRINGER_API_KEY)); q.addQueryItem(QStringLiteral("s"), QString::number(m_start + 1)); q.addQueryItem(QStringLiteral("p"), QString::number(SPRINGER_QUERY_COUNT)); switch(request().key) { case Title: q.addQueryItem(QStringLiteral("q"), QStringLiteral("title:\"%1\" OR book:\"%1\"").arg(request().value)); break; case Person: q.addQueryItem(QStringLiteral("q"), QStringLiteral("name:%1").arg(request().value)); break; case Keyword: q.addQueryItem(QStringLiteral("q"), QStringLiteral("\"%1\"").arg(request().value)); break; case ISBN: { // only grab first value QString v = request().value.section(QLatin1Char(';'), 0); v = ISBNValidator::isbn13(v); q.addQueryItem(QStringLiteral("q"), QStringLiteral("isbn:%1").arg(v)); } break; case DOI: q.addQueryItem(QStringLiteral("q"), QStringLiteral("doi:%1").arg(request().value)); break; case Raw: q.addQueryItem(QStringLiteral("q"), request().value); break; default: return QUrl(); } u.setQuery(q); // myDebug() << "url:" << u.url(); return u; } void SpringerFetcher::parseData(QByteArray& data_) { QDomDocument dom; if(!dom.setContent(data_, false)) { myWarning() << "server did not return valid XML."; return; } // total is /response/result/total QDomNode n = dom.documentElement().namedItem(QStringLiteral("result")) .namedItem(QStringLiteral("total")); QDomElement e = n.toElement(); if(!e.isNull()) { m_total = e.text().toInt(); // myDebug() << "total = " << m_total; } } void SpringerFetcher::checkMoreResults(int count_) { m_start = count_; m_hasMoreResults = m_start < m_total; } Tellico::Fetch::FetchRequest SpringerFetcher::updateRequest(Data::EntryPtr entry_) { const QString doi = entry_->field(QStringLiteral("doi")); if(!doi.isEmpty()) { return FetchRequest(Fetch::DOI, doi); } const QString isbn = entry_->field(QStringLiteral("isbn")); if(!isbn.isEmpty()) { return FetchRequest(Fetch::ISBN, isbn); } const QString title = entry_->field(QStringLiteral("title")); if(!title.isEmpty()) { return FetchRequest(Fetch::Title, title); } return FetchRequest(); } Tellico::Fetch::ConfigWidget* SpringerFetcher::configWidget(QWidget* parent_) const { return new SpringerFetcher::ConfigWidget(parent_, this); } QString SpringerFetcher::defaultName() { return QStringLiteral("SpringerLink"); } QString SpringerFetcher::defaultIcon() { return favIcon("http://www.springerlink.com"); } SpringerFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const SpringerFetcher* fetcher_) : Fetch::ConfigWidget(parent_) { QVBoxLayout* l = new QVBoxLayout(optionsWidget()); l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget())); l->addStretch(); // now add additional fields widget addFieldsWidget(SpringerFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList()); } void SpringerFetcher::ConfigWidget::saveConfigHook(KConfigGroup&) { } QString SpringerFetcher::ConfigWidget::preferredName() const { return SpringerFetcher::defaultName(); } diff --git a/src/gui/ratingdelegate.cpp b/src/gui/ratingdelegate.cpp index 66ff2756..61e0c3d7 100644 --- a/src/gui/ratingdelegate.cpp +++ b/src/gui/ratingdelegate.cpp @@ -1,66 +1,66 @@ /* * Copyright (C) 2010 Stefan Burnicki (stefan.burnicki@gmx.de) - * BANGARANG MEDIA PLAYER + * BANGARANG MEDIA PLAYER * Copyright 2011 Jörg Ehrichs * * 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) any later version. * * 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 "ratingdelegate.h" #include "starrating.h" #include #include RatingDelegate::RatingDelegate(QObject* parent /* = 0 */) : QStyledItemDelegate(parent), m_maxRating(StarRating::MaxRating) { } void RatingDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyle* style = option.widget ? option.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget); const int left = option.rect.left(); const int top = option.rect.top(); const int width = option.rect.width(); const int height = option.rect.height(); //Create base pixmap QPixmap pixmap(width, height); pixmap.fill(Qt::transparent); QPainter p(&pixmap); p.translate(-option.rect.topLeft()); //Paint rating const int rating = qRound(index.data(Qt::DisplayRole).toFloat()); StarRating starRating = StarRating(rating, StarRating::Medium); starRating.setMaxRating(m_maxRating); QSize ratingSize = starRating.sizeHint(); int ratingLeft = left + 2; int ratingTop = top + (height - ratingSize.height())/2; QRect ratingRect = QRect(QPoint(ratingLeft, ratingTop), ratingSize); starRating.setPoint(ratingRect.topLeft()); starRating.paint(&p); p.end(); //Draw finished pixmap painter->drawPixmap(option.rect.topLeft(), pixmap); } QSize RatingDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(option); Q_UNUSED(index); return StarRating::sizeHint(StarRating::Medium); } diff --git a/src/gui/ratingdelegate.h b/src/gui/ratingdelegate.h index 5dc4c812..b745e172 100644 --- a/src/gui/ratingdelegate.h +++ b/src/gui/ratingdelegate.h @@ -1,43 +1,43 @@ /* * Copyright (C) 2010 Stefan Burnicki (stefan.burnicki@gmx.de) - * BANGARANG MEDIA PLAYER + * BANGARANG MEDIA PLAYER * Copyright 2011 Jörg Ehrichs * * 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) any later version. * * 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 RATINGDELEGATE_H #define RATINGDELEGATE_H #include /** * @brief Displays the usual rating stars in the tabletwidget. Not editable */ class RatingDelegate : public QStyledItemDelegate { Q_OBJECT public: RatingDelegate(QObject* parent = nullptr); void setMaxRating(uint rating_) { m_maxRating = rating_; } void paint(QPainter* painter, const QStyleOptionViewItem& option,const QModelIndex& index) const Q_DECL_OVERRIDE; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const Q_DECL_OVERRIDE; private: uint m_maxRating; }; #endif // RATINGDELEGATE_H diff --git a/src/gui/starrating.cpp b/src/gui/starrating.cpp index 34c3b527..983c9866 100644 --- a/src/gui/starrating.cpp +++ b/src/gui/starrating.cpp @@ -1,69 +1,69 @@ /* * Copyright (C) 2010 Stefan Burnicki (stefan.burnicki@gmx.de) - * BANGARANG MEDIA PLAYER + * BANGARANG MEDIA PLAYER * Copyright 2011 Jörg Ehrichs * * 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) any later version. * * 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 "starrating.h" #include #include #include #include StarRating::StarRating(int rating, int size, QPoint point) : m_rating(rating), m_maxRating(MaxRating), m_point(point) { setSize(size); } bool StarRating::valid(int rating) const { return (rating >= MinRating && rating <= MaxRating); } void StarRating::paint(QPainter* painter) { painter->save(); painter->translate(m_point + QPoint(StarRating::Margin, StarRating::Margin)); for(int i = 1; i <= m_rating; ++i) { painter->drawPixmap(((i - 1) * (m_starSize + 2*Margin)), 0, m_starNormal); } painter->restore(); } void StarRating::setRating(int rating_) { m_rating = qMin(rating_, m_maxRating); } void StarRating::setMaxRating(int maxRating_) { m_maxRating = qMin(int(MaxRating), maxRating_); } void StarRating::setSize(int size) { int px = (size < Small) ? Small : size; QSize sz = QSize(px, px); QIcon icon = QIcon(QLatin1String(":/icons/star_on")); m_starNormal = icon.pixmap(sz); m_starSize = m_starNormal.size().width(); //maybe we didn't get the full size sz = QSize(m_starSize, m_starSize); m_starInactive = icon.pixmap(sz, QIcon::Disabled); } //static QSize StarRating::sizeHint(int size) { // *__*__*__*__* + StarRating::Margin around it const int bothMargin = (StarRating::Margin * 2); return QSize( 5 * (size + 2) - 2, size) + QSize(bothMargin, bothMargin); } diff --git a/src/gui/starrating.h b/src/gui/starrating.h index 7a223f27..5aeece05 100644 --- a/src/gui/starrating.h +++ b/src/gui/starrating.h @@ -1,64 +1,64 @@ /* * Copyright (C) 2010 Stefan Burnicki (stefan.burnicki@gmx.de) - * BANGARANG MEDIA PLAYER + * BANGARANG MEDIA PLAYER * Copyright 2011 Jörg Ehrichs * * 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) any later version. * * 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 STARRATING_H #define STARRATING_H #include #include /** * @brief This file provides the StarRating for painting rating stars. * It supports ratings from 1 to 10 in different sizes. */ class StarRating { public: enum Size { Small = 8, Medium = 12, Big = 16, Huge = 22 }; enum MinMax{ InvalidRating = -1, MinRating = 0, MaxRating = 10 }; static const int Margin = 1; explicit StarRating(int rating = 0, int size = Small, QPoint point = QPoint(0, 0)); void setRating(int rating); void setMaxRating(int maxRating); void setSize(int size); void setPoint(QPoint point) { m_point = point; } bool valid(int rating) const; int rating() const { return m_rating; } void paint(QPainter *painter); QSize sizeHint() const { return sizeHint(m_starSize); } static QSize sizeHint(int size); protected: QPixmap m_starNormal; QPixmap m_starInactive; int m_rating; int m_maxRating; QPoint m_point; int m_starSize; }; Q_DECLARE_METATYPE(StarRating) #endif diff --git a/src/main.cpp b/src/main.cpp index 9f8d20dc..ae195b9d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,185 +1,185 @@ /*************************************************************************** Copyright (C) 2001-2018 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 #include "mainwindow.h" #include "translators/translators.h" // needed for file type enum #include #include #include #include #include #include #include #include #include #include #include #include int main(int argc, char* argv[]) { QApplication app(argc, argv); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); KLocalizedString::setApplicationDomain("tellico"); app.setApplicationVersion(QStringLiteral(TELLICO_VERSION)); Q_INIT_RESOURCE(icons); // Migrate KDE4 configuration and data files Kdelibs4ConfigMigrator migrator(QStringLiteral("tellico")); migrator.setConfigFiles(QStringList() << QStringLiteral("tellicorc")); migrator.setUiFiles(QStringList() << QStringLiteral("tellicoui.rc")); if(migrator.migrate()) { // migrate old data typedef QPair StringPair; QList filesToCopy; QList dirsToCreate; Kdelibs4Migration dataMigrator; const QString sourceBasePath = dataMigrator.saveLocation("data", QStringLiteral("tellico")); const QString targetBasePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/'); QString sourceFilePath, targetFilePath; // first copy tellico-common.xsl if exists QString fileName = QStringLiteral("tellico-common.xsl"); sourceFilePath = sourceBasePath + QLatin1Char('/') + fileName; targetFilePath = targetBasePath + QLatin1Char('/') + fileName; if(QFile::exists(sourceFilePath) && !QFile::exists(targetFilePath)) { filesToCopy << qMakePair(sourceFilePath, targetFilePath); } // then migrate data directories QStack dirsToCheck; dirsToCheck.push(QStringLiteral("report-templates")); dirsToCheck.push(QStringLiteral("entry-templates")); dirsToCheck.push(QStringLiteral("data-sources")); // this will copy all the images shared between collections dirsToCheck.push(QStringLiteral("data")); while(!dirsToCheck.isEmpty()) { QString dataDir = dirsToCheck.pop(); QDir sourceDir(sourceBasePath + dataDir); if(sourceDir.exists()) { if(!QDir().exists(targetBasePath + dataDir)) { dirsToCreate << (targetBasePath + dataDir); } // grab the internal directories, so we can be recursive QStringList moreDirs = sourceDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); foreach(const QString& moreDir, moreDirs) { dirsToCheck.push(dataDir + QLatin1Char('/') + moreDir); } QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks); foreach(const QString& fileName, fileNames) { sourceFilePath = sourceBasePath + dataDir + QLatin1Char('/') + fileName; targetFilePath = targetBasePath + dataDir + QLatin1Char('/') + fileName; if(!QFile::exists(targetFilePath)) { filesToCopy << qMakePair(sourceFilePath, targetFilePath); } } } } foreach(const QString& dir, dirsToCreate) { QDir().mkpath(dir); } foreach(const StringPair& pair, filesToCopy) { QFile::copy(pair.first, pair.second); } // update the configuration cache KSharedConfig::openConfig()->reparseConfiguration(); } KCrash::initialize(); // component name = "tellico" is same as bugs.kde.org product name KAboutData aboutData(QStringLiteral("tellico"), QStringLiteral("Tellico"), QStringLiteral(TELLICO_VERSION), i18n("Tellico - a KDE collection manager"), KAboutLicense::GPL_V2, i18n("(c) 2001-2019, Robby Stephenson"), QString(), QStringLiteral("https://tellico-project.org")); aboutData.addAuthor(QStringLiteral("Robby Stephenson"), QString(), QStringLiteral("robby@periapsis.org")); aboutData.addAuthor(QStringLiteral("Mathias Monnerville"), i18n("Data source scripts")); aboutData.addAuthor(QStringLiteral("Regis Boudin"), QString(), QStringLiteral("regis@boudin.name")); aboutData.addAuthor(QStringLiteral("Petri Damstén"), QString(), QStringLiteral("damu@iki.fi")); aboutData.addAuthor(QStringLiteral("Sebastian Held"), QString()); aboutData.addCredit(QStringLiteral("Virginie Quesnay"), i18n("Icons")); aboutData.addCredit(QStringLiteral("Amarok"), i18n("Code examples and general inspiration"), - QString(), QStringLiteral("http://amarok.kde.org")); + QString(), QStringLiteral("https://amarok.kde.org")); aboutData.addCredit(QStringLiteral("Greg Ward"), i18n("Author of btparse library")); aboutData.addCredit(QStringLiteral("Robert Gamble"), i18n("Author of libcsv library")); aboutData.addCredit(QStringLiteral("Valentin Lavrinenko"), i18n("Author of rtf2html library")); aboutData.addLicense(KAboutLicense::GPL_V3); QCommandLineParser parser; parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("nofile"), i18n("Do not reopen the last open file"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("bibtex"), i18n("Import as a bibtex file"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("mods"), i18n("Import as a MODS file"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("ris"), i18n("Import as a RIS file"))); parser.addPositionalArgument(QStringLiteral("[filename]"), i18n("File to open")); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); KAboutData::setApplicationData(aboutData); if(app.isSessionRestored()) { RESTORE(Tellico::MainWindow); } else { Tellico::MainWindow* tellico = new Tellico::MainWindow(); tellico->show(); tellico->slotShowTipOfDay(false); // slotInit gets called out of a QTimer signal // but it wasn't always completing in-time // so call it manually to ensure it has finished tellico->slotInit(); QStringList args = parser.positionalArguments(); if(args.count() > 0) { if(parser.isSet(QStringLiteral("bibtex"))) { tellico->importFile(Tellico::Import::Bibtex, QUrl::fromUserInput(args.at(0)), Tellico::Import::Replace); } else if(parser.isSet(QStringLiteral("mods"))) { tellico->importFile(Tellico::Import::MODS, QUrl::fromUserInput(args.at(0)), Tellico::Import::Replace); } else if(parser.isSet(QStringLiteral("ris"))) { tellico->importFile(Tellico::Import::RIS, QUrl::fromUserInput(args.at(0)), Tellico::Import::Replace); } else { tellico->slotFileOpen(QUrl::fromUserInput(args.at(0), QDir::currentPath())); } } else { // bit of a hack, I just want the --nofile option // if --nofile is NOT passed, then the file option is set // is it's set, then go ahead and check for opening previous file tellico->initFileOpen(parser.isSet(QStringLiteral("nofile"))); } } return app.exec(); } diff --git a/src/tellico_debug.h b/src/tellico_debug.h index 284a7522..93c9ea24 100644 --- a/src/tellico_debug.h +++ b/src/tellico_debug.h @@ -1,141 +1,141 @@ /*************************************************************************** Copyright (C) 2003-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 . * * * ***************************************************************************/ #ifndef TELLICO_DEBUG_H #define TELLICO_DEBUG_H // some of this was borrowed from amarok/src/debug.h // which is copyright Max Howell // amarok is licensed under the GPL #include // std::clock_t #include #include // linux has __GNUC_PREREQ, NetBSD has __GNUC_PREREQ__ #if defined(__GNUC_PREREQ) && !defined(__GNUC_PREREQ__) # define __GNUC_PREREQ__ __GNUC_PREREQ #endif #if !defined(__GNUC_PREREQ__) # if defined __GNUC__ # define __GNUC_PREREQ__(x, y) \ ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \ (__GNUC__ > (x))) # else # define __GNUC_PREREQ__(x, y) 0 # endif #endif # if defined __cplusplus ? __GNUC_PREREQ__ (2, 6) : __GNUC_PREREQ__ (2, 4) # define MY_FUNCTION __PRETTY_FUNCTION__ # else # if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L # define MY_FUNCTION __func__ # else # define MY_FUNCTION __FILE__ ":" __LINE__ # endif # endif // some logging #if !defined(KDE_NO_DEBUG_OUTPUT) #define TELLICO_LOG #endif #ifndef DEBUG_PREFIX #define FUNC_PREFIX "" #else #define FUNC_PREFIX "[" DEBUG_PREFIX "] " #endif #define myDebug() qDebug() #define myWarning() qWarning() #ifdef TELLICO_LOG #define myLog() qDebug() #else #define myLog() //qDebug() #endif namespace Debug { class Block { public: Block(const char* label) : m_start(std::clock()), m_label(label) { QDebug(QtDebugMsg) << "BEGIN:" << label; } ~Block() { std::clock_t finish = std::clock(); const double duration = (double) (finish - m_start) / CLOCKS_PER_SEC; QDebug(QtDebugMsg) << " END:" << m_label << "- duration =" << duration; } private : std::clock_t m_start; const char* m_label; }; } /// Standard function announcer #define DEBUG_FUNC myDebug() << Q_FUNC_INFO; /// Announce a line #define DEBUG_LINE myDebug() << "[" << __FILE__ << ":" << __LINE__ << "]"; /// Convenience macro for making a standard Debug::Block #ifndef WIN32 #define DEBUG_BLOCK Debug::Block uniquelyNamedStackAllocatedStandardBlock( MY_FUNCTION ); #else #define DEBUG_BLOCK #endif #if defined(TELLICO_LOG) && !defined(WIN32) -// see http://www.gnome.org/~federico/news-2006-03.html#timeline-tools +// see https://www.gnome.org/~federico/news-2006-03.html#timeline-tools // strace -ttt -f -o /tmp/logfile.strace src/tellico // plot-timeline.py -o prettygraph.png /tmp/logfile.strace #define MARK do { \ char str[128]; \ ::snprintf(str, 128, "MARK: %s: %s (%d)", metaObject()->className(), MY_FUNCTION, __LINE__); \ ::access (str, F_OK); \ } while(false) #define MARK_MSG(s) do { \ char str[128]; \ ::snprintf(str, 128, "MARK: %s: %s (%d)", metaObject()->className(), s, __LINE__); \ ::access (str, F_OK); \ } while(false) #define MARK_LINE do { \ char str[128]; \ ::snprintf(str, 128, "MARK: tellico: %s (%d)", __FILE__, __LINE__); \ ::access (str, F_OK); \ } while(false) #else #define MARK #define MARK_MSG(s) #define MARK_LINE #endif #endif diff --git a/src/translators/bibteximporter.cpp b/src/translators/bibteximporter.cpp index 1b06ab03..decd3a64 100644 --- a/src/translators/bibteximporter.cpp +++ b/src/translators/bibteximporter.cpp @@ -1,424 +1,424 @@ /*************************************************************************** Copyright (C) 2003-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 "bibteximporter.h" #include "../utils/bibtexhandler.h" #include "../collections/bibtexcollection.h" #include "../entry.h" #include "../fieldformat.h" #include "../core/filehandler.h" #include "../tellico_debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Tellico; using Tellico::Import::BibtexImporter; int BibtexImporter::s_initCount = 0; BibtexImporter::BibtexImporter(const QList& urls_) : Importer(urls_) , m_widget(nullptr), m_readUTF8(nullptr), m_readLocale(nullptr), m_cancelled(false) { init(); } BibtexImporter::BibtexImporter(const QString& text_) : Importer(text_) , m_widget(nullptr), m_readUTF8(nullptr), m_readLocale(nullptr), m_cancelled(false) { init(); } BibtexImporter::~BibtexImporter() { --s_initCount; if(s_initCount == 0) { bt_cleanup(); } if(m_readUTF8) { KConfigGroup config(KSharedConfig::openConfig(), "Import Options"); config.writeEntry("Bibtex UTF8", m_readUTF8->isChecked()); } } void BibtexImporter::init() { if(s_initCount == 0) { bt_initialize(); } ++s_initCount; } bool BibtexImporter::canImport(int type) const { return type == Data::Collection::Bibtex; } Tellico::Data::CollPtr BibtexImporter::collection() { if(m_coll) { return m_coll; } emit signalTotalSteps(this, urls().count() * 100); bool useUTF8 = m_widget && m_readUTF8->isChecked(); m_coll = new Data::BibtexCollection(true); int count = 0; // might be importing text only if(!text().isEmpty()) { QString text = this->text(); Data::CollPtr coll = readCollection(text, count); if(!coll || coll->entryCount() == 0) { setStatusMessage(i18n("No valid bibtex entries were found")); } else { appendCollection(coll); } } foreach(const QUrl& url, urls()) { if(m_cancelled) { return Data::CollPtr(); } if(!url.isValid()) { continue; } QString text = FileHandler::readTextFile(url, false, useUTF8); if(text.isEmpty()) { continue; } Data::CollPtr coll = readCollection(text, count); if(!coll || coll->entryCount() == 0) { setStatusMessage(i18n("No valid bibtex entries were found in file - %1", this->url().fileName())); continue; } appendCollection(coll); } if(m_cancelled) { return Data::CollPtr(); } return m_coll; } Tellico::Data::CollPtr BibtexImporter::readCollection(const QString& text, int urlCount) { if(text.isEmpty()) { myDebug() << "no text"; return Data::CollPtr(); } Data::CollPtr ptr(new Data::BibtexCollection(true)); Data::BibtexCollection* c = static_cast(ptr.data()); parseText(text); // populates m_nodes if(m_cancelled) { return Data::CollPtr(); } if(m_nodes.isEmpty()) { return Data::CollPtr(); } QString str; const uint count = m_nodes.count(); const uint stepSize = qMax(s_stepSize, count/100); const bool showProgress = options() & ImportProgress; Data::CollPtr currentColl = currentCollection(); if(!currentColl || currentColl->type() != Data::Collection::Bibtex) { currentColl = ptr; } uint j = 0; for(int i = 0; !m_cancelled && i < m_nodes.count(); ++i, ++j) { AST* node = m_nodes[i]; // if we're parsing a macro string, comment or preamble, skip it for now if(bt_entry_metatype(node) == BTE_PREAMBLE) { char* preamble = bt_get_text(node); if(preamble) { c->setPreamble(QString::fromUtf8(preamble)); } continue; } if(bt_entry_metatype(node) == BTE_MACRODEF) { char* macro; (void) bt_next_field(node, nullptr, ¯o); // FIXME: replace macros within macro definitions! // lookup lowercase macro in map c->addMacro(m_macros[QString::fromUtf8(macro)], QString::fromUtf8(bt_macro_text(macro, nullptr, 0))); continue; } if(bt_entry_metatype(node) == BTE_COMMENT) { continue; } // now we're parsing a regular entry Data::EntryPtr entry(new Data::Entry(ptr)); str = QString::fromUtf8(bt_entry_type(node)); // myDebug() << "entry type: " << str; // text is automatically put into lower-case by btparse Data::BibtexCollection::setFieldValue(entry, QStringLiteral("entry-type"), str, currentColl); str = QString::fromUtf8(bt_entry_key(node)); // myDebug() << "entry key: " << str; Data::BibtexCollection::setFieldValue(entry, QStringLiteral("key"), str, currentColl); char* name; AST* field = nullptr; while((field = bt_next_field(node, field, &name))) { // myDebug() << "\tfound: " << name; // str = QLatin1String(bt_get_text(field)); str.clear(); AST* value = nullptr; bt_nodetype type; char* svalue; bool end_macro = false; while((value = bt_next_value(field, value, &type, &svalue))) { switch(type) { case BTAST_STRING: case BTAST_NUMBER: str += BibtexHandler::importText(svalue).simplified(); end_macro = false; break; case BTAST_MACRO: str += QString::fromUtf8(svalue) + QLatin1Char('#'); end_macro = true; break; default: break; } } if(end_macro) { // remove last character '#' str.truncate(str.length() - 1); } QString fieldName = QString::fromUtf8(name); if(fieldName == QLatin1String("author") || fieldName == QLatin1String("editor")) { str.replace(QRegExp(QLatin1String("\\sand\\s")),FieldFormat::delimiterString()); } // there's a 'key' field different from the citation key - // http://nwalsh.com/tex/texhelp/bibtx-37.html + // https://nwalsh.com/tex/texhelp/bibtx-37.html // TODO account for this later if(fieldName == QLatin1String("key")) { myLog() << "skipping bibtex 'key' field for" << str; } else { Data::BibtexCollection::setFieldValue(entry, fieldName, str, currentColl); } } ptr->addEntries(entry); if(showProgress && j%stepSize == 0) { emit signalProgress(this, urlCount*100 + 100*j/count); qApp->processEvents(); } } if(m_cancelled) { ptr = nullptr; } // clean-up foreach(AST* node, m_nodes) { bt_free_ast(node); } return ptr; } void BibtexImporter::parseText(const QString& text) { m_nodes.clear(); m_macros.clear(); ushort bt_options = 0; // ushort is defined in btparse.h boolean ok; // boolean is defined in btparse.h as an int // for regular nodes (entries), do NOT convert numbers to strings, do NOT expand macros bt_set_stringopts(BTE_REGULAR, 0); bt_set_stringopts(BTE_MACRODEF, 0); // bt_set_stringopts(BTE_PREAMBLE, BTO_CONVERT | BTO_EXPAND); QString entry; QRegExp rx(QLatin1String("[{}]")); QRegExp macroName(QLatin1String("@string\\s*\\{\\s*(.*)="), Qt::CaseInsensitive); macroName.setMinimal(true); int line = 1; bool needsCleanup = false; int brace = 0; int startpos = 0; int pos = rx.indexIn(text, 0); while(pos > 0 && !m_cancelled) { if(text[pos] == QLatin1Char('{')) { ++brace; } else if(text[pos] == QLatin1Char('}') && brace > 0) { --brace; } if(brace == 0) { entry = text.mid(startpos, pos-startpos+1); // All the downstream text processing on the AST node will assume utf-8 QByteArray entryText = entry.toUtf8(); QByteArray filename = QFile::encodeName(url().fileName()); AST* node = bt_parse_entry_s(entryText.data(), filename.data(), line, bt_options, &ok); if(ok && node) { if(bt_entry_metatype(node) == BTE_MACRODEF && macroName.indexIn(entry) > -1) { char* macro; (void) bt_next_field(node, nullptr, ¯o); m_macros.insert(QString::fromUtf8(macro), macroName.cap(1).trimmed()); } m_nodes.append(node); needsCleanup = true; } startpos = pos+1; line += entry.count(QLatin1Char('\n')); } pos = rx.indexIn(text, pos+1); } if(needsCleanup) { // clean up some structures bt_parse_entry_s(nullptr, nullptr, 1, 0, nullptr); } } void BibtexImporter::slotCancel() { m_cancelled = true; } QWidget* BibtexImporter::widget(QWidget* parent_) { if(m_widget) { return m_widget; } m_widget = new QWidget(parent_); QVBoxLayout* l = new QVBoxLayout(m_widget); QGroupBox* gbox = new QGroupBox(i18n("Bibtex Options"), m_widget); QVBoxLayout* vlay = new QVBoxLayout(gbox); m_readUTF8 = new QRadioButton(i18n("Use Unicode (UTF-8) encoding"), gbox); m_readUTF8->setWhatsThis(i18n("Read the imported file in Unicode (UTF-8).")); QString localStr = i18n("Use user locale (%1) encoding", QLatin1String(QTextCodec::codecForLocale()->name())); m_readLocale = new QRadioButton(localStr, gbox); m_readLocale->setChecked(true); m_readLocale->setWhatsThis(i18n("Read the imported file in the local encoding.")); vlay->addWidget(m_readUTF8); vlay->addWidget(m_readLocale); QButtonGroup* bg = new QButtonGroup(gbox); bg->addButton(m_readUTF8); bg->addButton(m_readLocale); KConfigGroup config(KSharedConfig::openConfig(), "Import Options"); bool useUTF8 = config.readEntry("Bibtex UTF8", false); if(useUTF8) { m_readUTF8->setChecked(true); } else { m_readLocale->setChecked(true); } l->addWidget(gbox); l->addStretch(1); return m_widget; } bool BibtexImporter::maybeBibtex(const QUrl& url_) { QString text = FileHandler::readTextFile(url_, true /*quiet*/); if(text.isEmpty()) { return false; } return maybeBibtex(text, url_); } bool BibtexImporter::maybeBibtex(const QString& text, const QUrl& url_) { bt_initialize(); QRegExp rx(QLatin1String("[{}]")); ushort bt_options = 0; // ushort is defined in btparse.h boolean ok; // boolean is defined in btparse.h as an int bool foundOne = false; int brace = 0; int startpos = 0; int pos = rx.indexIn(text, 0); while(pos > 0) { if(text[pos] == QLatin1Char('{')) { ++brace; } else if(text[pos] == QLatin1Char('}') && brace > 0) { --brace; } if(brace == 0) { QString entry = text.mid(startpos, pos-startpos+1).trimmed(); // All the downstream text processing on the AST node will assume utf-8 AST* node = bt_parse_entry_s(const_cast(entry.toUtf8().data()), const_cast(url_.fileName().toLocal8Bit().data()), 0, bt_options, &ok); if(ok && node) { foundOne = true; break; } startpos = pos+1; } pos = rx.indexIn(text, pos+1); } if(foundOne) { // clean up some structures bt_parse_entry_s(nullptr, nullptr, 1, 0, nullptr); } bt_cleanup(); return foundOne; } void BibtexImporter::appendCollection(Data::CollPtr coll_) { Data::BibtexCollection* mainColl = static_cast(m_coll.data()); Data::BibtexCollection* newColl = static_cast(coll_.data()); foreach(Data::FieldPtr field, coll_->fields()) { m_coll->mergeField(field); } mainColl->addEntries(newColl->entries()); // append the preamble and macro lists if(!newColl->preamble().isEmpty()) { QString pre = mainColl->preamble(); if(!pre.isEmpty()) { pre += QLatin1Char('\n'); } mainColl->setPreamble(pre + newColl->preamble()); } StringMap macros = mainColl->macroList(); macros.unite(newColl->macroList()); mainColl->setMacroList(macros); } diff --git a/src/translators/boardgamegeekimporter.cpp b/src/translators/boardgamegeekimporter.cpp index f5714d6c..39c2635f 100644 --- a/src/translators/boardgamegeekimporter.cpp +++ b/src/translators/boardgamegeekimporter.cpp @@ -1,277 +1,277 @@ /*************************************************************************** Copyright (C) 2014 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 "boardgamegeekimporter.h" #include "../collections/boardgamecollection.h" #include "xslthandler.h" #include "tellicoimporter.h" #include "../core/filehandler.h" #include "../utils/datafileregistry.h" #include "../tellico_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { static const char* BGG_THING_URL = "http://boardgamegeek.com/xmlapi2/thing"; static const char* BGG_COLLECTION_URL = "http://boardgamegeek.com/xmlapi2/collection"; static int BGG_STEPSIZE = 25; } using Tellico::Import::BoardGameGeekImporter; BoardGameGeekImporter::BoardGameGeekImporter() : Import::Importer(), m_cancelled(false), m_widget(nullptr) , m_userEdit(nullptr), m_checkOwned(nullptr) { QString xsltFile = DataFileRegistry::self()->locate(QStringLiteral("boardgamegeek2tellico.xsl")); if(!xsltFile.isEmpty()) { m_xsltURL = QUrl::fromLocalFile(xsltFile); } else { myWarning() << "unable to find boardgamegeek2tellico.xsl!"; } KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("ImportOptions - BoardGameGeek")); m_user = config.readEntry("User ID"); m_ownedOnly = config.readEntry("Owned", false); } bool BoardGameGeekImporter::canImport(int type) const { return type == Data::Collection::BoardGame; } Tellico::Data::CollPtr BoardGameGeekImporter::collection() { if(m_coll) { return m_coll; } if(m_xsltURL.isEmpty() || !m_xsltURL.isValid()) { setStatusMessage(i18n("A valid XSLT file is needed to import the file.")); return Data::CollPtr(); } if(!m_widget) { myWarning() << "no widget!"; return Data::CollPtr(); } m_user = m_userEdit->text().trimmed(); if(m_user.isEmpty()) { setStatusMessage(i18n("A valid user ID must be entered.")); return Data::CollPtr(); } XSLTHandler handler(m_xsltURL); if(!handler.isValid()) { setStatusMessage(i18n("Tellico encountered an error in XSLT processing.")); return Data::CollPtr(); } m_ownedOnly = m_checkOwned->isChecked(); // first get the bgg id list QUrl u(QString::fromLatin1(BGG_COLLECTION_URL)); QUrlQuery q; q.addQueryItem(QStringLiteral("username"), m_user); q.addQueryItem(QStringLiteral("subtype"), QStringLiteral("boardgame")); q.addQueryItem(QStringLiteral("brief"), QStringLiteral("1")); if(m_ownedOnly) { q.addQueryItem(QStringLiteral("own"), QStringLiteral("1")); } u.setQuery(q); QStringList idList; QDomDocument dom = FileHandler::readXMLDocument(u, false, true); // could return HTTP 202 while the caching system generates the file - // see http://boardgamegeek.com/thread/1188687/export-collections-has-been-updated-xmlapi-develop + // see https://boardgamegeek.com/thread/1188687/export-collections-has-been-updated-xmlapi-develop // also has a root node of message. Try 5 times, waiting by 2 seconds each time bool hasMessage = dom.documentElement().tagName() == QStringLiteral("message"); for(int loopCount = 0; hasMessage && loopCount < 5; ++loopCount) { // wait 2 seconds and try again QTime timer; timer.start(); while(timer.elapsed() < 2000) { QCoreApplication::processEvents(QEventLoop::AllEvents, 100); QThread::msleep(500); } dom = FileHandler::readXMLDocument(u, false, true); hasMessage = dom.documentElement().tagName() == QStringLiteral("message"); } if(hasMessage) { myDebug() << "BoardGameGeekImporter still has message and no collection"; #if 0 myWarning() << "Remove debug from boardgamegeekimporter.cpp"; QFile f(QStringLiteral("/tmp/bgg-message.xml")); if(f.open(QIODevice::WriteOnly)) { QTextStream t(&f); t.setCodec("UTF-8"); t << dom.toString(); } f.close(); #endif } QDomNodeList items = dom.documentElement().elementsByTagName(QStringLiteral("item")); for(int i = 0; i < items.count(); ++i) { if(!items.at(i).isElement()) { continue; } const QString id = items.at(i).toElement().attribute(QStringLiteral("objectid")); if(!id.isEmpty()) { idList += id; } } if(idList.isEmpty()) { myLog() << "No items found in BGG collection"; return Data::CollPtr(); } const bool showProgress = options() & ImportProgress; if(showProgress) { // use 10 as the amount for reading the ids emit signalTotalSteps(this, 10 + 100); emit signalProgress(this, 10); } m_coll = new Data::BoardGameCollection(true); for(int j = 0; j < idList.size() && !m_cancelled; j += BGG_STEPSIZE) { QStringList ids; const int maxSize = qMin(j+BGG_STEPSIZE, idList.size()); for(int k = j; k < maxSize; ++k) { ids += idList.at(k); } #if 0 const QString xmlData = text(ids); myWarning() << "Remove debug from boardgamegeekimporter.cpp"; QFile f(QStringLiteral("/tmp/test.xml")); if(f.open(QIODevice::WriteOnly)) { QTextStream t(&f); t.setCodec("UTF-8"); t << xmlData; } f.close(); #endif QString str = handler.applyStylesheet(text(ids)); // QString str = handler.applyStylesheet(xmlData); // myDebug() << str; #if 0 myWarning() << "Remove debug2 from boardgamegeekimporter.cpp"; QFile f2(QStringLiteral("/tmp/test.tc")); if(f2.open(QIODevice::WriteOnly)) { QTextStream t(&f2); t.setCodec("UTF-8"); t << str; } f2.close(); #endif Import::TellicoImporter imp(str); imp.setOptions(imp.options() ^ Import::ImportShowImageErrors); Data::CollPtr c = imp.collection(); if(!c) { continue; } // assume we always want the 3 extra fields defined in boardgamegeek2tellico.xsl if(!m_coll->hasField(QStringLiteral("bggid"))) { m_coll->addField(Data::FieldPtr(new Data::Field(*c->fieldByName(QStringLiteral("bggid"))))); m_coll->addField(Data::FieldPtr(new Data::Field(*c->fieldByName(QStringLiteral("boardgamegeek-link"))))); Data::FieldPtr f(new Data::Field(*c->fieldByName(QStringLiteral("artist")))); // also, let's assume that the artist field title should be illustrator instead of musician f->setTitle(i18nc("Comic Book Illustrator", "Artist")); m_coll->addField(f); } m_coll->addEntries(c->entries()); setStatusMessage(imp.statusMessage()); if(showProgress) { emit signalProgress(this, 10 + 100*maxSize/idList.size()); qApp->processEvents(); } } KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("ImportOptions - BoardGameGeek")); config.writeEntry("User ID", m_user); config.writeEntry("Owned", m_ownedOnly); if(m_cancelled) { m_coll = Data::CollPtr(); } return m_coll; } QWidget* BoardGameGeekImporter::widget(QWidget* parent_) { if(m_widget) { return m_widget; } m_widget = new QWidget(parent_); QVBoxLayout* l = new QVBoxLayout(m_widget); QGroupBox* gbox = new QGroupBox(i18n("BoardGameGeek Options"), m_widget); QFormLayout* lay = new QFormLayout(gbox); m_userEdit = new QLineEdit(gbox); m_userEdit->setText(m_user); m_checkOwned = new QCheckBox(i18n("Import owned items only"), gbox); m_checkOwned->setChecked(m_ownedOnly); lay->addRow(i18n("User ID:"), m_userEdit); lay->addRow(m_checkOwned); l->addWidget(gbox); l->addStretch(1); return m_widget; } QString BoardGameGeekImporter::text(const QStringList& idList_) const { // myDebug() << idList_; QUrl u(QString::fromLatin1(BGG_THING_URL)); QUrlQuery q; q.addQueryItem(QStringLiteral("id"), idList_.join(QLatin1String(","))); q.addQueryItem(QStringLiteral("type"), QStringLiteral("boardgame,boardgameexpansion")); u.setQuery(q); // myDebug() << u; return FileHandler::readTextFile(u, true, true); } void BoardGameGeekImporter::slotCancel() { m_cancelled = true; } diff --git a/src/translators/csvexporter.cpp b/src/translators/csvexporter.cpp index 9698662d..06bc77f6 100644 --- a/src/translators/csvexporter.cpp +++ b/src/translators/csvexporter.cpp @@ -1,254 +1,254 @@ /*************************************************************************** Copyright (C) 2003-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 "csvexporter.h" #include "../collection.h" #include "../core/filehandler.h" #include #include #include #include #include #include #include #include #include using namespace Tellico; using Tellico::Export::CSVExporter; CSVExporter::CSVExporter(Data::CollPtr coll_) : Tellico::Export::Exporter(coll_), m_includeTitles(true), m_delimiter(QStringLiteral(",")), m_colDelimiter(QStringLiteral(":")), m_rowDelimiter(QStringLiteral("|")), m_widget(nullptr), m_checkIncludeTitles(nullptr), m_radioComma(nullptr), m_radioSemicolon(nullptr), m_radioTab(nullptr), m_radioOther(nullptr), m_editOther(nullptr), m_colDelimiterEdit(nullptr), m_rowDelimiterEdit(nullptr) { } QString CSVExporter::formatString() const { return i18n("CSV"); } QString CSVExporter::fileFilter() const { return i18n("CSV Files") + QLatin1String(" (*.csv)") + QLatin1String(";;") + i18n("All Files") + QLatin1String(" (*)"); } QString& CSVExporter::escapeText(QString& text_) const { bool quotes = false; if(text_.contains(QLatin1Char('"'))) { quotes = true; // quotation marks will be escaped by using a double pair text_.replace(QLatin1Char('"'), QLatin1String("\"\"")); } // if the text contains quotes or the delimiter, it needs to be surrounded by quotes if(quotes || text_.contains(m_delimiter) || text_.contains(QLatin1Char('\n'))) { text_.prepend(QLatin1Char('"')); text_.append(QLatin1Char('"')); } return text_; } bool CSVExporter::exec() { if(!collection()) { return false; } return FileHandler::writeTextURL(url(), text(), options() & ExportUTF8, options() & Export::ExportForce); } QString CSVExporter::text() const { QString text; if(m_includeTitles) { foreach(Data::FieldPtr fIt, fields()) { QString title = fIt->title(); - // because of Microsoft Excel bug, http://support.microsoft.com/kb/323626 + // because of Microsoft Excel bug, https://support.microsoft.com/kb/323626 if(text.isEmpty() && title == QLatin1String("ID")) { title = QStringLiteral("Id"); } text += escapeText(title) + m_delimiter; } // remove last delimiter text.truncate(text.length() - m_delimiter.length()); text += QLatin1Char('\n'); } FieldFormat::Request format = (options() & Export::ExportFormatted ? FieldFormat::ForceFormat : FieldFormat::AsIsFormat); const bool replaceColDelimiter = (m_colDelimiter != FieldFormat::columnDelimiterString()); const bool replaceRowDelimiter = (m_rowDelimiter != FieldFormat::rowDelimiterString()); foreach(Data::EntryPtr entryIt, entries()) { QStringList values; foreach(Data::FieldPtr fIt, fields()) { QString value = entryIt->formattedField(fIt->name(), format); if(replaceColDelimiter) { value.replace(FieldFormat::columnDelimiterString(), m_colDelimiter); } if(replaceRowDelimiter) { value.replace(FieldFormat::rowDelimiterString(), m_rowDelimiter); } values += escapeText(value); } text += values.join(m_delimiter) + QLatin1Char('\n'); } return text; } QWidget* CSVExporter::widget(QWidget* parent_) { if(m_widget && m_widget->parent() == parent_) { return m_widget; } m_widget = new QWidget(parent_); QVBoxLayout* l = new QVBoxLayout(m_widget); QGroupBox* gbox = new QGroupBox(i18n("CSV Options"), m_widget); QVBoxLayout* vlay = new QVBoxLayout(gbox); m_checkIncludeTitles = new QCheckBox(i18n("Include field titles as column headers"), gbox); m_checkIncludeTitles->setChecked(m_includeTitles); m_checkIncludeTitles->setWhatsThis(i18n("If checked, a header row will be added with the " "field titles.")); QGroupBox* delimiterGroup = new QGroupBox(i18n("Delimiter"), gbox); QGridLayout* m_delimiterGroupLayout = new QGridLayout(delimiterGroup); m_delimiterGroupLayout->setAlignment(Qt::AlignTop); delimiterGroup->setWhatsThis(i18n("In addition to a comma, other characters may be used as " "a delimiter, separating each value in the file.")); m_radioComma = new QRadioButton(delimiterGroup); m_radioComma->setText(i18n("Comma")); m_radioComma->setChecked(true); m_radioComma->setWhatsThis(i18n("Use a comma as the delimiter.")); m_delimiterGroupLayout->addWidget(m_radioComma, 0, 0); m_radioSemicolon = new QRadioButton( delimiterGroup); m_radioSemicolon->setText(i18n("Semicolon")); m_radioSemicolon->setWhatsThis(i18n("Use a semi-colon as the delimiter.")); m_delimiterGroupLayout->addWidget(m_radioSemicolon, 0, 1); m_radioTab = new QRadioButton(delimiterGroup); m_radioTab->setText(i18n("Tab")); m_radioTab->setWhatsThis(i18n("Use a tab as the delimiter.")); m_delimiterGroupLayout->addWidget(m_radioTab, 1, 0); m_radioOther = new QRadioButton(delimiterGroup); m_radioOther->setText(i18n("Other")); m_radioOther->setWhatsThis(i18n("Use a custom string as the delimiter.")); m_delimiterGroupLayout->addWidget(m_radioOther, 1, 1); m_editOther = new QLineEdit(delimiterGroup); m_editOther->setEnabled(m_radioOther->isChecked()); m_editOther->setWhatsThis(i18n("A custom string, such as a colon, may be used as a delimiter.")); m_delimiterGroupLayout->addWidget(m_editOther, 1, 2); QObject::connect(m_radioOther, &QAbstractButton::toggled, m_editOther, &QWidget::setEnabled); if(m_delimiter == QLatin1String(",")) { m_radioComma->setChecked(true); } else if(m_delimiter == QLatin1String(";")) { m_radioSemicolon->setChecked(true); } else if(m_delimiter == QLatin1String("\t")) { m_radioTab->setChecked(true); } else if(!m_delimiter.isEmpty()) { m_radioOther->setChecked(true); m_editOther->setEnabled(true); m_editOther->setText(m_delimiter); } QLabel* label = new QLabel(i18n("Table column delimiter:"), gbox); m_colDelimiterEdit = new QLineEdit(gbox); m_colDelimiterEdit->setText(m_colDelimiter); m_delimiterGroupLayout->addWidget(label, 2, 0, 1, 2); m_delimiterGroupLayout->addWidget(m_colDelimiterEdit, 2, 2); QString w = i18n("The column delimiter separates values in each column of a Table field."); label->setWhatsThis(w); m_colDelimiterEdit->setWhatsThis(w); label = new QLabel(i18n("Table row delimiter:"), gbox); m_rowDelimiterEdit = new QLineEdit(gbox); m_rowDelimiterEdit->setText(m_rowDelimiter); m_delimiterGroupLayout->addWidget(label, 3, 0, 1, 2); m_delimiterGroupLayout->addWidget(m_rowDelimiterEdit, 3, 2); w = i18n("The row delimiter separates values in each row of a Table field."); label->setWhatsThis(w); m_rowDelimiterEdit->setWhatsThis(w); vlay->addWidget(m_checkIncludeTitles); vlay->addWidget(delimiterGroup); l->addWidget(gbox); l->addStretch(1); return m_widget; } void CSVExporter::readOptions(KSharedConfigPtr config_) { KConfigGroup group(config_, QStringLiteral("ExportOptions - %1").arg(formatString())); m_includeTitles = group.readEntry("Include Titles", m_includeTitles); m_delimiter = group.readEntry("Delimiter", m_delimiter); m_rowDelimiter = group.readEntry("RowDelimiter", m_rowDelimiter); m_colDelimiter = group.readEntry("ColumnDelimiter", m_colDelimiter); } void CSVExporter::saveOptions(KSharedConfigPtr config_) { m_includeTitles = m_checkIncludeTitles->isChecked(); if(m_radioComma->isChecked()) { m_delimiter = QLatin1Char(','); } else if(m_radioSemicolon->isChecked()) { m_delimiter = QLatin1Char(';'); } else if(m_radioTab->isChecked()) { m_delimiter = QLatin1Char('\t'); } else { m_delimiter = m_editOther->text(); } QString s = m_colDelimiterEdit->text(); if(!s.isEmpty()) { m_colDelimiter = s; } s = m_rowDelimiterEdit->text(); if(!s.isEmpty()) { m_rowDelimiter = s; } KConfigGroup group(config_, QStringLiteral("ExportOptions - %1").arg(formatString())); group.writeEntry("Include Titles", m_includeTitles); group.writeEntry("Delimiter", m_delimiter); group.writeEntry("RowDelimiter", m_rowDelimiter); group.writeEntry("ColumnDelimiter", m_colDelimiter); } diff --git a/src/translators/freedbimporter.cpp b/src/translators/freedbimporter.cpp index f76e667d..0e983295 100644 --- a/src/translators/freedbimporter.cpp +++ b/src/translators/freedbimporter.cpp @@ -1,575 +1,575 @@ /*************************************************************************** Copyright (C) 2004-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 . * * * ***************************************************************************/ /* * We could use KCompactDisc now, but it's very buggy, * see https://bugs.kde.org/show_bug.cgi?id=183520 * https://bugs.kde.org/show_bug.cgi?id=183521 - * http://lists.kde.org/?l=kde-multimedia&m=123397778013726&w=2 + * https://lists.kde.org/?l=kde-multimedia&m=123397778013726&w=2 */ #include #include "freedbimporter.h" #include "../collections/musiccollection.h" #include "../entry.h" #include "../field.h" #include "../fieldformat.h" #include "../utils/tellico_utils.h" #include "../utils/string_utils.h" #include "../utils/guiproxy.h" #include "../progressmanager.h" #include "../utils/cursorsaver.h" #include "../tellico_debug.h" #if defined HAVE_KF5KCDDB #include #elif defined HAVE_KCDDB #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using Tellico::Import::FreeDBImporter; FreeDBImporter::FreeDBImporter() : Tellico::Import::Importer() , m_widget(nullptr) , m_buttonGroup(nullptr) , m_radioCDROM(nullptr) , m_radioCache(nullptr) , m_driveCombo(nullptr) , m_cancelled(false) { } bool FreeDBImporter::canImport(int type) const { return type == Data::Collection::Album; } Tellico::Data::CollPtr FreeDBImporter::collection() { if(m_coll) { return m_coll; } m_cancelled = false; if(m_radioCDROM->isChecked()) { readCDROM(); } else { readCache(); } if(m_cancelled) { m_coll = Data::CollPtr(); } return m_coll; } void FreeDBImporter::readCDROM() { #if defined (HAVE_KCDDB) || defined (HAVE_KF5KCDDB) QString drivePath = m_driveCombo->currentText(); if(drivePath.isEmpty()) { setStatusMessage(i18n("Tellico was unable to access the CD-ROM device - %1.", drivePath)); myDebug() << "no drive!"; return; } // now it's ok to add device to saved list m_driveCombo->addItem(drivePath); QStringList drives; for(int i = 0; i < m_driveCombo->count(); ++i) { if(drives.indexOf(m_driveCombo->itemText(i)) == -1) { drives += m_driveCombo->itemText(i); } } { KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("ImportOptions - FreeDB")); config.writeEntry("CD-ROM Devices", drives); config.writeEntry("Last Device", drivePath); config.writeEntry("Cache Files Only", false); } QByteArray drive = QFile::encodeName(drivePath); QList lengths; KCDDB::TrackOffsetList list; #if 0 // a1107d0a - Kruder & Dorfmeister - The K&D Sessions - Disc One. /* list << 150 // First track start. << 29462 << 66983 << 96785 << 135628 << 168676 << 194147 << 222158 << 247076 << 278203 // Last track start. << 316732; // Disc end. */ /* list << 150 // First track start. << 3296 << 14437 << 41279 << 51362 << 56253 << 59755 << 61324 << 66059 << 69073 << 77790 << 83214 << 89726 << 92078 << 106325 << 113117 << 116040 << 119877 << 124377 << 145466 << 157583 << 167208 << 173486 << 180120 << 185279 << 193270 << 206451 << 217303 // Last track start. << 224925; // Disc end. list << 150 << 106965 << 127220 << 151925 << 176085 << 234500; */ #else list = offsetList(drive, lengths); #endif if(list.isEmpty()) { setStatusMessage(i18n("Tellico was unable to access the CD-ROM device - %1.", drivePath)); return; } // myDebug() << list; // the result info, could be multiple ones KCDDB::CDInfo info; KCDDB::Client client; client.setBlockingMode(true); KCDDB::Result r = client.lookup(list); const KCDDB::CDInfoList responseList = client.lookupResponse(); // KCDDB sometimes doesn't return MultipleRecordFound properly, so check length of response, too if(r == KCDDB::MultipleRecordFound || responseList.count() > 1) { QStringList list; foreach(const KCDDB::CDInfo& info, responseList) { list.append(QStringLiteral("%1, %2, %3").arg(info.get(KCDDB::Artist).toString(), info.get(KCDDB::Title).toString(), info.get(KCDDB::Genre).toString())); } // switch back to pointer cursor GUI::CursorSaver cs(Qt::ArrowCursor); bool ok; QString res = QInputDialog::getItem(GUI::Proxy::widget(), i18n("Select CDDB Entry"), i18n("Select a CDDB entry:"), list, 0, false, &ok); if(ok) { int i = 0; foreach(const QString& listValue, list) { if(listValue == res) { break; } ++i; } if(i < responseList.size()) { info = responseList.at(i); } } else { // cancelled dialog m_cancelled = true; } } else if(r == KCDDB::Success && !responseList.isEmpty()) { info = responseList.first(); } else { myDebug() << "no success! Return value = " << r; QString s; switch(r) { case KCDDB::NoRecordFound: s = i18n("No records were found to match the CD."); break; case KCDDB::ServerError: myDebug() << "Server Error"; break; case KCDDB::HostNotFound: myDebug() << "Host Not Found"; break; case KCDDB::NoResponse: myDebug() << "No Response"; break; case KCDDB::UnknownError: myDebug() << "Unknown Error"; break; default: break; } if(s.isEmpty()) { s = i18n("Tellico was unable to complete the CD lookup."); } setStatusMessage(s); return; } if(!info.isValid()) { // go ahead and try to read cd-text if we weren't cancelled // could be the case we don't have net access if(!m_cancelled) { readCDText(drive); } return; } m_coll = new Data::MusicCollection(true); Data::EntryPtr entry(new Data::Entry(m_coll)); // obviously a CD entry->setField(QStringLiteral("medium"), i18n("Compact Disc")); entry->setField(QStringLiteral("title"), info.get(KCDDB::Title).toString()); entry->setField(QStringLiteral("artist"), info.get(KCDDB::Artist).toString()); entry->setField(QStringLiteral("genre"), info.get(KCDDB::Genre).toString()); if(!info.get(KCDDB::Year).isNull()) { entry->setField(QStringLiteral("year"), info.get(KCDDB::Year).toString()); } entry->setField(QStringLiteral("keyword"), info.get(KCDDB::Category).toString()); QString extd = info.get(QStringLiteral("EXTD")).toString(); extd.replace(QLatin1Char('\n'), QLatin1String("
")); entry->setField(QStringLiteral("comments"), extd); QStringList trackList; trackList.reserve(info.numberOfTracks()); for(int i = 0; i < info.numberOfTracks(); ++i) { QString s = info.track(i).get(KCDDB::Title).toString() + FieldFormat::columnDelimiterString(); const QString trackArtist = info.track(i).get(KCDDB::Artist).toString().trimmed(); s += trackArtist.isEmpty() ? info.get(KCDDB::Artist).toString() : trackArtist; if(i < lengths.count()) { s += FieldFormat::columnDelimiterString() + Tellico::minutes(lengths[i]); } trackList << s; } entry->setField(QStringLiteral("track"), trackList.join(FieldFormat::rowDelimiterString())); m_coll->addEntries(entry); readCDText(drive); #endif } void FreeDBImporter::readCache() { #if defined (HAVE_KCDDB) || defined (HAVE_KF5KCDDB) { // remember the import options KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("ImportOptions - FreeDB")); config.writeEntry("Cache Files Only", true); } KCDDB::Config cfg; cfg.load(); QStringList dirs = cfg.cacheLocations(); foreach(const QString& dirName, dirs) { dirs += Tellico::findAllSubDirs(dirName); } // using a QMap is a lazy man's way of getting unique keys // the cddb info may be in multiple files, all with the same filename, the cddb id QMap files; foreach(const QString& dirName, dirs) { if(dirName.isEmpty()) { continue; } QDir dir(dirName); dir.setFilter(QDir::Files | QDir::Readable | QDir::Hidden); // hidden since I want directory files const QStringList list = dir.entryList(); foreach(const QString& listEntry, list) { files.insert(listEntry, dir.absoluteFilePath(listEntry)); } // qApp->processEvents(); // really needed ? } const QString title = QStringLiteral("title"); const QString artist = QStringLiteral("artist"); const QString year = QStringLiteral("year"); const QString genre = QStringLiteral("genre"); const QString medium = QStringLiteral("medium"); const QString keyword = QStringLiteral("keyword"); const QString track = QStringLiteral("track"); const QString comments = QStringLiteral("comments"); int numFiles = files.count(); if(numFiles == 0) { myDebug() << "no files found"; return; } m_coll = new Data::MusicCollection(true); const uint stepSize = qMax(1, numFiles / 100); const bool showProgress = options() & ImportProgress; ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true); item.setTotalSteps(numFiles); connect(&item, &Tellico::ProgressItem::signalCancelled, this, &FreeDBImporter::slotCancel); ProgressItem::Done done(this); uint step = 1; KCDDB::CDInfo info; for(QMap::ConstIterator it = files.constBegin(); !m_cancelled && it != files.constEnd(); ++it, ++step) { // open file and read content QFileInfo fileinfo(it.value()); // skip files larger than 10 kB if(!fileinfo.exists() || !fileinfo.isReadable() || fileinfo.size() > 10*1024) { myDebug() << "skipping " << it.value(); continue; } QFile file(it.value()); if(!file.open(QIODevice::ReadOnly)) { continue; } QTextStream ts(&file); // libkcddb always writes the cache files in utf-8 ts.setCodec(QTextCodec::codecForName("UTF-8")); QString cddbData = ts.readAll(); file.close(); if(cddbData.isEmpty() || !info.load(cddbData) || !info.isValid()) { myDebug() << "Error - CDDB record is not valid"; myDebug() << "File = " << it.value(); continue; } // create a new entry and set fields Data::EntryPtr entry(new Data::Entry(m_coll)); // obviously a CD entry->setField(medium, i18n("Compact Disc")); entry->setField(title, info.get(KCDDB::Title).toString()); entry->setField(artist, info.get(KCDDB::Artist).toString()); entry->setField(genre, info.get(KCDDB::Genre).toString()); if(!info.get(KCDDB::Year).isNull()) { entry->setField(year, info.get(KCDDB::Year).toString()); } entry->setField(keyword, info.get(KCDDB::Category).toString()); QString extd = info.get(QStringLiteral("EXTD")).toString(); extd.replace(QLatin1Char('\n'), QLatin1String("
")); entry->setField(comments, extd); // step through trackList QStringList trackList; trackList.reserve(info.numberOfTracks()); for(int i = 0; i < info.numberOfTracks(); ++i) { trackList << info.track(i).get(KCDDB::Title).toString(); } entry->setField(track, trackList.join(FieldFormat::rowDelimiterString())); #if 0 // add CDDB info const QString br = QLatin1String("
"); QString comment; if(!info.extd.isEmpty()) { comment.append(info.extd + br); } if(!info.id.isEmpty()) { comment.append(QLatin1String("CDDB-ID: ") + info.id + br); } if(info.length > 0) { comment.append("Length: " + QString::number(info.length) + br); } if(info.revision > 0) { comment.append("Revision: " + QString::number(info.revision) + br); } entry->setField(comments, comment); #endif // add this entry to the music collection m_coll->addEntries(entry); if(showProgress && step%stepSize == 0) { ProgressManager::self()->setProgress(this, step); qApp->processEvents(); } } #endif } #define SETFIELD(name,value) \ if(entry->field(QStringLiteral(name)).isEmpty()) { \ entry->setField(QStringLiteral(name), value); \ } void FreeDBImporter::readCDText(const QByteArray& drive_) { #ifdef ENABLE_CDTEXT Data::EntryPtr entry; if(m_coll) { if(m_coll->entryCount() > 0) { entry = m_coll->entries().front(); } } else { m_coll = new Data::MusicCollection(true); } if(!entry) { entry = new Data::Entry(m_coll); entry->setField(QStringLiteral("medium"), i18n("Compact Disc")); m_coll->addEntries(entry); } CDText cdtext = getCDText(drive_); /* myDebug() << "CDText - title: " << cdtext.title; myDebug() << "CDText - title: " << cdtext.artist; for(int i = 0; i < cdtext.trackTitles.size(); ++i) { myDebug() << i << "--" << cdtext.trackTitles[i] << " - " << cdtext.trackArtists[i]; } */ QString artist = cdtext.artist; SETFIELD("title", cdtext.title); SETFIELD("artist", artist); SETFIELD("comments", cdtext.message); QStringList tracks; tracks.reserve(cdtext.trackTitles.size()); for(int i = 0; i < cdtext.trackTitles.size(); ++i) { tracks << cdtext.trackTitles[i] + FieldFormat::columnDelimiterString() + cdtext.trackArtists[i]; if(artist.isEmpty()) { artist = cdtext.trackArtists[i]; } if(!artist.isEmpty() && artist.toLower() != cdtext.trackArtists[i].toLower()) { artist = i18n("Various"); } } SETFIELD("track", tracks.join(FieldFormat::rowDelimiterString())); // something special for compilations and such SETFIELD("artist", artist); #else Q_UNUSED(drive_); #endif } #undef SETFIELD QWidget* FreeDBImporter::widget(QWidget* parent_) { if(m_widget) { return m_widget; } m_widget = new QWidget(parent_); QVBoxLayout* l = new QVBoxLayout(m_widget); QGroupBox* gbox = new QGroupBox(i18n("Audio CD Options"), m_widget); QVBoxLayout* vlay = new QVBoxLayout(gbox); // cdrom stuff QHBoxLayout* hlay = new QHBoxLayout(); vlay->addLayout(hlay); m_radioCDROM = new QRadioButton(i18n("Read data from CD-ROM device"), gbox); m_driveCombo = new KComboBox(true, gbox); m_driveCombo->setDuplicatesEnabled(false); QString w = i18n("Select or input the CD-ROM device location."); m_radioCDROM->setWhatsThis(w); m_driveCombo->setWhatsThis(w); hlay->addWidget(m_radioCDROM); hlay->addWidget(m_driveCombo); /********************************************************************************/ m_radioCache = new QRadioButton(i18n("Read all CDDB cache files only"), gbox); m_radioCache->setWhatsThis(i18n("Read data recursively from all the CDDB cache files " "contained in the default cache folders.")); vlay->addWidget(m_radioCache); // cddb cache stuff m_buttonGroup = new QButtonGroup(gbox); m_buttonGroup->addButton(m_radioCDROM); m_buttonGroup->addButton(m_radioCache); void (QButtonGroup::* buttonClickedInt)(int) = &QButtonGroup::buttonClicked; connect(m_buttonGroup, buttonClickedInt, this, &FreeDBImporter::slotClicked); l->addWidget(gbox); l->addStretch(1); // now read config options KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("ImportOptions - FreeDB")); QStringList devices = config.readEntry("CD-ROM Devices", QStringList()); if(devices.isEmpty()) { #if defined(__OpenBSD__) devices += QLatin1String("/dev/rcd0c"); #endif devices += QStringLiteral("/dev/cdrom"); devices += QStringLiteral("/dev/dvd"); } m_driveCombo->addItems(devices); QString device = config.readEntry("Last Device"); if(!device.isEmpty()) { m_driveCombo->setEditText(device); } if(config.readEntry("Cache Files Only", false)) { m_radioCache->setChecked(true); } else { m_radioCDROM->setChecked(true); } // set enabled widgets slotClicked(m_buttonGroup->checkedId()); return m_widget; } void FreeDBImporter::slotClicked(int id_) { QAbstractButton* button = m_buttonGroup->button(id_); if(!button) { return; } m_driveCombo->setEnabled(button == m_radioCDROM); } void FreeDBImporter::slotCancel() { m_cancelled = true; } diff --git a/src/utils/isbnvalidator.h b/src/utils/isbnvalidator.h index d2afb408..c1f1116c 100644 --- a/src/utils/isbnvalidator.h +++ b/src/utils/isbnvalidator.h @@ -1,173 +1,173 @@ /*************************************************************************** Copyright (C) 2002-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 . * * * ***************************************************************************/ #ifndef TELLICO_ISBNVALIDATOR_H #define TELLICO_ISBNVALIDATOR_H #include #include namespace Tellico { /** * @author Robby Stephenson * - * @see http://www.isbn.org/standards/home/isbn/international/hyphenation-instructions.asp - * @see http://www.eblong.com/zarf/bookscan/ - * @see http://doc.trolltech.com/qq/qq01-seriously-weird-qregexp.html + * @see https://web.archive.org/web/20130126042049/http://www.isbn.org/standards/home/isbn/international/hyphenation-instructions.asp + * @see https://www.eblong.com/zarf/bookscan/ + * @see https://doc.qt.io/archives/qq/qq01-seriously-weird-qregexp.html */ class ISBNValidator : public QValidator { Q_OBJECT public: ISBNValidator(QObject* parent = nullptr); /** * Certain conditions are checked. Character, length and position * restrictions are checked. Certain cases where the user is deleting * characters are caught and compensated for. The string is then passed to * @ref fixup. Finally, the text is @ref Valid if it is a certain length and * @ref Intermediate if not. * * @param input The text to validate * @param pos The position of the cursor * @return The condition of the text */ virtual QValidator::State validate(QString& input, int& pos) const Q_DECL_OVERRIDE; /** * The input string is examined. Hyphens are inserted appropriately, * and the checksum is calculated. * * For correct presentation, the 10 digits of an ISBN must * be divided, by hyphens, into four parts: * @li Part 1: The country or group of countries identifier * @li Part 2: The publisher identifier * @li Part 3: The title identifier * @li Part 4: The check digit * For details - * @see http://www.isbn.org/standards/home/isbn/international/hyphenation-instructions.asp + * @see https://web.archive.org/web/20130126042049/http://www.isbn.org/standards/home/isbn/international/hyphenation-instructions.asp * For details on ranges - * @see http://www.isbn-international.org/page/ranges + * @see https://www.isbn-international.org/range_file_generation * For info on group codes - * @see http://www.isbn.spk-berlin.de/html/prefix/allpref.htm + * @see https://web.archive.org/web/20030609050408/http://www.isbn.spk-berlin.de/html/prefix/allpref.htm * For info on French language publisher codes - * @see http://www.afnil.org + * @see https://www.afnil.org/ *
    *  Group Identifiers    First Hyphen After
    *  -----------------------------------------
    *  0........7              1st digit
    *  80.......94             2nd   "
    *  950......993            3rd   "
    *  9940.....9989           4th   "
    *  99900....99999          5th   "
    *
    *  Group                   Insert Hyphens
    *  Identifier "0"            After
    *  -----------------------------------------
    *  00.......19          1st  3rd  9th digit
    *  200......699          "   4th       "
    *  7000.....8499         "   5th       "
    *  85000....89999        "   6th       "
    *  900000...949999       "   7th       "
    *  9500000..9999999      "   8th       "
    *
    *
    *  Group                  Insert Hyphens
    *  Identifier "1"           After
    *  ----------------------------------------
    *  0........54999           illegal
    *  55000....86979      1st  6th  9th digit
    *  869800...998999      "   7th       "
    *  9990000..9999999     "   8th       "
    *
    *
    *  Group                   Insert Hyphens
    *  Identifier "2"            After
    *  -----------------------------------------
    *  00.......19          1st  3rd  9th digit
    *  200......349          "   4th       "
    *  34000....39999        "   6th       "
    *  400......699          "   4th       "
    *  7000.....8399         "   5th       "
    *  84000....89999        "   6th       "
    *  900000...949999       "   7th       "
    *  9500000..9999999      "   8th       "
    *
    *  The position of the hyphens are determined by the publisher
    *  prefix range established by each national agency in accordance
    *  with the industry needs. The knowledge of the prefix ranges for
    *  each country or group of countries is necessary to develop the
    *  hyphenation output program. For groups 3 through 99999, the hyphenation
    *  rules are currently unknown. So just leave out the hyphen between
    *  the publisher and title for now, but allow it if the user inserts it.
    * 
* * @param input The raw string, hyphens included */ virtual void fixup(QString& input) const Q_DECL_OVERRIDE; static void staticFixup(QString& input); static void fixup10(QString& input); static void fixup13(QString& input); static QString isbn10(QString isbn13); static QString isbn13(QString isbn10); static QString cleanValue(QString isbn); // returns the values in list1 that are not in list2 static QStringList listDifference(const QStringList& list1, const QStringList& list2); private: static struct isbn_band { unsigned long MaxValue; int First; int Mid; int Last; } bands[]; QValidator::State validate10(QString& input, int& pos) const; QValidator::State validate13(QString& input, int& pos) const; /** * This function calculates and returns the ISBN checksum. The * algorithm is based on some code by Andrew Plotkin, available at - * http://www.eblong.com/zarf/bookscan/ + * https://www.eblong.com/zarf/bookscan/ * - * @see http://www.eblong.com/zarf/bookscan/ + * @see https://www.eblong.com/zarf/bookscan/ * * @param input The raw string, with no hyphens */ static QChar checkSum10(const QString& input); static QChar checkSum13(const QString& input); }; class ISBNComparison : public std::binary_function { public: ISBNComparison() {} bool operator()(const QString& value1, const QString& value2) const; }; } // end namespace #endif diff --git a/src/utils/iso5426converter.cpp b/src/utils/iso5426converter.cpp index 3220d7c9..187505ab 100644 --- a/src/utils/iso5426converter.cpp +++ b/src/utils/iso5426converter.cpp @@ -1,895 +1,895 @@ /*************************************************************************** Copyright (C) 2006-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 . * * * ***************************************************************************/ // This class is adapted from Iso5426ToUnicode from the MARC4J project, available -// from http://marc4j.tigris.org, with the following notice: +// from https://github.com/marc4j/marc4j, with the following notice: // * Copyright (C) 2002 Bas Peters (mail@bpeters.com) // * Copyright (C) 2002 Yves Pratter (ypratter@club-internet.fr) // // That source was released under the terms of the GNU Lesser General Public // License, version 2.1. In accordance with Condition 3 of that license, // I am applying the terms of the GNU General Public License to the source // code, and including a large portion of it here #include "iso5426converter.h" #include "../tellico_debug.h" #include #include using Tellico::Iso5426Converter; QString Iso5426Converter::toUtf8(const QByteArray& text_) { const uint len = text_.length(); QString result; result.reserve(len); uint pos = 0; for(uint i = 0; i < len; ++i) { uchar c = text_[i]; if(isAscii(c)) { result[pos++] = c; } else if(isCombining(c) && hasNext(i, len)) { // this is a hack // use the diaeresis instead of umlaut // works for SUDOC if(c == 0xC9) { c = 0xC8; } QChar d = getCombiningChar(c * 256 + text_[i + 1]); if(!d.isNull()) { result[pos++] = d; ++i; } else { result[pos++] = getChar(c); } } else { result[pos++] = getChar(c); } } result.squeeze(); return result; } inline bool Iso5426Converter::hasNext(uint pos, uint len) { return pos < (len - 1); } inline bool Iso5426Converter::isAscii(uchar c) { return c <= 0x7F; } inline bool Iso5426Converter::isCombining(uchar c) { return c >= 0xC0 && c <= 0xDF; } -// Source : http://www.itscj.ipsj.or.jp/ISO-IR/053.pdf +// Source : https://www.itscj.ipsj.or.jp/iso-ir/053.pdf QChar Iso5426Converter::getChar(uchar c) { switch(c) { case 0xA1: return 0x00A1; // 2/1 inverted exclamation mark case 0xA2: return 0x201C; // 2/2 left low double quotation mark case 0xA3: return 0x00A3; // 2/3 pound sign case 0xA4: return 0x0024; // 2/4 dollar sign case 0xA5: return 0x00A5; // 2/5 yen sign case 0xA6: return 0x2020; // 2/6 single dagger case 0xA7: return 0x00A7; // 2/7 paragraph (section) case 0xA8: return 0x2032; // 2/8 prime case 0xA9: return 0x2018; // 2/9 left high single quotation mark case 0xAA: return 0x201C; // 2/10 left high double quotation mark case 0xAB: return 0x00AB; // 2/11 left angle quotation mark case 0xAC: return 0x266D; // 2/12 music flat case 0xAD: return 0x00A9; // 2/13 copyright sign case 0xAE: return 0x2117; // 2/14 sound recording copyright sign case 0xAF: return 0x00AE; // 2/15 trade mark sign case 0xB0: return 0x0639; // 3/0 ayn [ain] case 0xB1: return 0x0623; // 3/1 alif/hamzah [alef with hamza above] case 0xB2: return 0x2018; // 3/2 left low single quotation mark // 3/3 (this position shall not be used) // 3/4 (this position shall not be used) // 3/5 (this position shall not be used) case 0xB6: return 0x2021; // 3/6 double dagger case 0xB7: return 0x00B7; // 3/7 middle dot case 0xB8: return 0x2033; // 3/8 double prime case 0xB9: return 0x2019; // 3/9 right high single quotation mark case 0xBA: return 0x201D; // 3/10 right high double quotation mark case 0xBB: return 0x00BB; // 3/11 right angle quotation mark case 0xBC: return 0x266F; // 3/12 musical sharp case 0xBD: return 0x02B9; // 3/13 mjagkij znak case 0xBE: return 0x02BA; // 3/14 tverdyj znak case 0xBF: return 0x00BF; // 3/15 inverted question mark // 4/0 to 5/15 diacritic characters // 6/0 (this position shall not be used) case 0xE1: return 0x00C6; // 6/1 CAPITAL DIPHTHONG A WITH E case 0xE2: return 0x0110; // 6/2 CAPITAL LETTER D WITH STROKE // 6/3 (this position shall not be used) // 6/4 (this position shall not be used) // 6/5 (this position shall not be used) case 0xE6: return 0x0132; // 6/6 CAPITAL LETTER IJ // 6/7 (this position shall not be used) case 0xE8: return 0x0141; // 6/8 CAPITAL LETTER L WITH STROKE case 0xE9: return 0x00D8; // 6/9 CAPITAL LETTER O WITH SOLIDUS [oblique stroke] case 0xEA: return 0x0152; // 6/10 CAPITAL DIPHTONG OE // 6/11 (this position shall not be used) case 0xEC: return 0x00DE; // 6/12 CAPITAL LETTER THORN // 6/13 (this position shall not be used) // 6/14 (this position shall not be used) // 6/15 (this position shall not be used) // 7/0 (this position shall not be used) case 0xF1: return 0x00E6; // 7/1 small diphthong a with e // 7/4 (this position shall not be used) case 0xF5: return 0x0131; // 7/5 small letter i without dot case 0xF6: return 0x0133; // 7/6 small letter ij // 7/7 (this position shall not be used) case 0xF8: return 0x0142; // 7/8 small letter l with stroke case 0xF9: return 0x00F8; // 7/9 small letter o with solidus (oblique stroke) case 0xFA: return 0x0153; // 7/10 small diphtong oe case 0xFB: return 0x00DF; // 7/11 small letter sharp s case 0xFC: return 0x00FE; // 7/12 small letter thorn // 7/13 (this position shall not be used) // 7/14 (this position shall not be used) default: return QLatin1Char(c); } } QChar Iso5426Converter::getCombiningChar(uint c) { switch(c) { // 4/0 low rising tone mark case 0xC041: return 0x1EA2; // CAPITAL A WITH HOOK ABOVE case 0xC045: return 0x1EBA; // CAPITAL E WITH HOOK ABOVE case 0xC049: return 0x1EC8; // CAPITAL I WITH HOOK ABOVE case 0xC04F: return 0x1ECE; // CAPITAL O WITH HOOK ABOVE case 0xC055: return 0x1EE6; // CAPITAL U WITH HOOK ABOVE case 0xC059: return 0x1EF6; // CAPITAL Y WITH HOOK ABOVE case 0xC061: return 0x1EA3; // small a with hook above case 0xC065: return 0x1EBB; // small e with hook above case 0xC069: return 0x1EC9; // small i with hook above case 0xC06F: return 0x1ECF; // small o with hook above case 0xC075: return 0x1EE7; // small u with hook above case 0xC079: return 0x1EF7; // small y with hook above // 4/1 grave accent case 0xC141: return 0x00C0; // CAPITAL A WITH GRAVE ACCENT case 0xC145: return 0x00C8; // CAPITAL E WITH GRAVE ACCENT case 0xC149: return 0x00CC; // CAPITAL I WITH GRAVE ACCENT case 0xC14F: return 0x00D2; // CAPITAL O WITH GRAVE ACCENT case 0xC155: return 0x00D9; // CAPITAL U WITH GRAVE ACCENT case 0xC157: return 0x1E80; // CAPITAL W WITH GRAVE case 0xC159: return 0x1EF2; // CAPITAL Y WITH GRAVE case 0xC161: return 0x00E0; // small a with grave accent case 0xC165: return 0x00E8; // small e with grave accent case 0xC169: return 0x00EC; // small i with grave accent case 0xC16F: return 0x00F2; // small o with grave accent case 0xC175: return 0x00F9; // small u with grave accent case 0xC177: return 0x1E81; // small w with grave case 0xC179: return 0x1EF3; // small y with grave // 4/2 acute accent case 0xC241: return 0x00C1; // CAPITAL A WITH ACUTE ACCENT case 0xC243: return 0x0106; // CAPITAL C WITH ACUTE ACCENT case 0xC245: return 0x00C9; // CAPITAL E WITH ACUTE ACCENT case 0xC247: return 0x01F4; // CAPITAL G WITH ACUTE case 0xC249: return 0x00CD; // CAPITAL I WITH ACUTE ACCENT case 0xC24B: return 0x1E30; // CAPITAL K WITH ACUTE case 0xC24C: return 0x0139; // CAPITAL L WITH ACUTE ACCENT case 0xC24D: return 0x1E3E; // CAPITAL M WITH ACUTE case 0xC24E: return 0x0143; // CAPITAL N WITH ACUTE ACCENT case 0xC24F: return 0x00D3; // CAPITAL O WITH ACUTE ACCENT case 0xC250: return 0x1E54; // CAPITAL P WITH ACUTE case 0xC252: return 0x0154; // CAPITAL R WITH ACUTE ACCENT case 0xC253: return 0x015A; // CAPITAL S WITH ACUTE ACCENT case 0xC255: return 0x00DA; // CAPITAL U WITH ACUTE ACCENT case 0xC257: return 0x1E82; // CAPITAL W WITH ACUTE case 0xC259: return 0x00DD; // CAPITAL Y WITH ACUTE ACCENT case 0xC25A: return 0x0179; // CAPITAL Z WITH ACUTE ACCENT case 0xC261: return 0x00E1; // small a with acute accent case 0xC263: return 0x0107; // small c with acute accent case 0xC265: return 0x00E9; // small e with acute accent case 0xC267: return 0x01F5; // small g with acute case 0xC269: return 0x00ED; // small i with acute accent case 0xC26B: return 0x1E31; // small k with acute case 0xC26C: return 0x013A; // small l with acute accent case 0xC26D: return 0x1E3F; // small m with acute case 0xC26E: return 0x0144; // small n with acute accent case 0xC26F: return 0x00F3; // small o with acute accent case 0xC270: return 0x1E55; // small p with acute case 0xC272: return 0x0155; // small r with acute accent case 0xC273: return 0x015B; // small s with acute accent case 0xC275: return 0x00FA; // small u with acute accent case 0xC277: return 0x1E83; // small w with acute case 0xC279: return 0x00FD; // small y with acute accent case 0xC27A: return 0x017A; // small z with acute accent case 0xC2E1: return 0x01FC; // CAPITAL AE WITH ACUTE case 0xC2F1: return 0x01FD; // small ae with acute // 4/3 circumflex accent case 0xC341: return 0x00C2; // CAPITAL A WITH CIRCUMFLEX ACCENT case 0xC343: return 0x0108; // CAPITAL C WITH CIRCUMFLEX case 0xC345: return 0x00CA; // CAPITAL E WITH CIRCUMFLEX ACCENT case 0xC347: return 0x011C; // CAPITAL G WITH CIRCUMFLEX case 0xC348: return 0x0124; // CAPITAL H WITH CIRCUMFLEX case 0xC349: return 0x00CE; // CAPITAL I WITH CIRCUMFLEX ACCENT case 0xC34A: return 0x0134; // CAPITAL J WITH CIRCUMFLEX case 0xC34F: return 0x00D4; // CAPITAL O WITH CIRCUMFLEX ACCENT case 0xC353: return 0x015C; // CAPITAL S WITH CIRCUMFLEX case 0xC355: return 0x00DB; // CAPITAL U WITH CIRCUMFLEX case 0xC357: return 0x0174; // CAPITAL W WITH CIRCUMFLEX case 0xC359: return 0x0176; // CAPITAL Y WITH CIRCUMFLEX case 0xC35A: return 0x1E90; // CAPITAL Z WITH CIRCUMFLEX case 0xC361: return 0x00E2; // small a with circumflex accent case 0xC363: return 0x0109; // small c with circumflex case 0xC365: return 0x00EA; // small e with circumflex accent case 0xC367: return 0x011D; // small g with circumflex case 0xC368: return 0x0125; // small h with circumflex case 0xC369: return 0x00EE; // small i with circumflex accent case 0xC36A: return 0x0135; // small j with circumflex case 0xC36F: return 0x00F4; // small o with circumflex accent case 0xC373: return 0x015D; // small s with circumflex case 0xC375: return 0x00FB; // small u with circumflex case 0xC377: return 0x0175; // small w with circumflex case 0xC379: return 0x0177; // small y with circumflex case 0xC37A: return 0x1E91; // small z with circumflex // 4/4 tilde case 0xC441: return 0x00C3; // CAPITAL A WITH TILDE case 0xC445: return 0x1EBC; // CAPITAL E WITH TILDE case 0xC449: return 0x0128; // CAPITAL I WITH TILDE case 0xC44E: return 0x00D1; // CAPITAL N WITH TILDE case 0xC44F: return 0x00D5; // CAPITAL O WITH TILDE case 0xC455: return 0x0168; // CAPITAL U WITH TILDE case 0xC456: return 0x1E7C; // CAPITAL V WITH TILDE case 0xC459: return 0x1EF8; // CAPITAL Y WITH TILDE case 0xC461: return 0x00E3; // small a with tilde case 0xC465: return 0x1EBD; // small e with tilde case 0xC469: return 0x0129; // small i with tilde case 0xC46E: return 0x00F1; // small n with tilde case 0xC46F: return 0x00F5; // small o with tilde case 0xC475: return 0x0169; // small u with tilde case 0xC476: return 0x1E7D; // small v with tilde case 0xC479: return 0x1EF9; // small y with tilde // 4/5 macron case 0xC541: return 0x0100; // CAPITAL A WITH MACRON case 0xC545: return 0x0112; // CAPITAL E WITH MACRON case 0xC547: return 0x1E20; // CAPITAL G WITH MACRON case 0xC549: return 0x012A; // CAPITAL I WITH MACRON case 0xC54F: return 0x014C; // CAPITAL O WITH MACRON case 0xC555: return 0x016A; // CAPITAL U WITH MACRON case 0xC561: return 0x0101; // small a with macron case 0xC565: return 0x0113; // small e with macron case 0xC567: return 0x1E21; // small g with macron case 0xC569: return 0x012B; // small i with macron case 0xC56F: return 0x014D; // small o with macron case 0xC575: return 0x016B; // small u with macron case 0xC5E1: return 0x01E2; // CAPITAL AE WITH MACRON case 0xC5F1: return 0x01E3; // small ae with macron // 4/6 breve case 0xC641: return 0x0102; // CAPITAL A WITH BREVE case 0xC645: return 0x0114; // CAPITAL E WITH BREVE case 0xC647: return 0x011E; // CAPITAL G WITH BREVE case 0xC649: return 0x012C; // CAPITAL I WITH BREVE case 0xC64F: return 0x014E; // CAPITAL O WITH BREVE case 0xC655: return 0x016C; // CAPITAL U WITH BREVE case 0xC661: return 0x0103; // small a with breve case 0xC665: return 0x0115; // small e with breve case 0xC667: return 0x011F; // small g with breve case 0xC669: return 0x012D; // small i with breve case 0xC66F: return 0x014F; // small o with breve case 0xC675: return 0x016D; // small u with breve // 4/7 dot above case 0xC742: return 0x1E02; // CAPITAL B WITH DOT ABOVE case 0xC743: return 0x010A; // CAPITAL C WITH DOT ABOVE case 0xC744: return 0x1E0A; // CAPITAL D WITH DOT ABOVE case 0xC745: return 0x0116; // CAPITAL E WITH DOT ABOVE case 0xC746: return 0x1E1E; // CAPITAL F WITH DOT ABOVE case 0xC747: return 0x0120; // CAPITAL G WITH DOT ABOVE case 0xC748: return 0x1E22; // CAPITAL H WITH DOT ABOVE case 0xC749: return 0x0130; // CAPITAL I WITH DOT ABOVE case 0xC74D: return 0x1E40; // CAPITAL M WITH DOT ABOVE case 0xC74E: return 0x1E44; // CAPITAL N WITH DOT ABOVE case 0xC750: return 0x1E56; // CAPITAL P WITH DOT ABOVE case 0xC752: return 0x1E58; // CAPITAL R WITH DOT ABOVE case 0xC753: return 0x1E60; // CAPITAL S WITH DOT ABOVE case 0xC754: return 0x1E6A; // CAPITAL T WITH DOT ABOVE case 0xC757: return 0x1E86; // CAPITAL W WITH DOT ABOVE case 0xC758: return 0x1E8A; // CAPITAL X WITH DOT ABOVE case 0xC759: return 0x1E8E; // CAPITAL Y WITH DOT ABOVE case 0xC75A: return 0x017B; // CAPITAL Z WITH DOT ABOVE case 0xC762: return 0x1E03; // small b with dot above case 0xC763: return 0x010B; // small c with dot above case 0xC764: return 0x1E0B; // small d with dot above case 0xC765: return 0x0117; // small e with dot above case 0xC766: return 0x1E1F; // small f with dot above case 0xC767: return 0x0121; // small g with dot above case 0xC768: return 0x1E23; // small h with dot above case 0xC76D: return 0x1E41; // small m with dot above case 0xC76E: return 0x1E45; // small n with dot above case 0xC770: return 0x1E57; // small p with dot above case 0xC772: return 0x1E59; // small r with dot above case 0xC773: return 0x1E61; // small s with dot above case 0xC774: return 0x1E6B; // small t with dot above case 0xC777: return 0x1E87; // small w with dot above case 0xC778: return 0x1E8B; // small x with dot above case 0xC779: return 0x1E8F; // small y with dot above case 0xC77A: return 0x017C; // small z with dot above // 4/8 trema, diaresis case 0xC820: return 0x00A8; // diaeresis case 0xC841: return 0x00C4; // CAPITAL A WITH DIAERESIS case 0xC845: return 0x00CB; // CAPITAL E WITH DIAERESIS case 0xC848: return 0x1E26; // CAPITAL H WITH DIAERESIS case 0xC849: return 0x00CF; // CAPITAL I WITH DIAERESIS case 0xC84F: return 0x00D6; // CAPITAL O WITH DIAERESIS case 0xC855: return 0x00DC; // CAPITAL U WITH DIAERESIS case 0xC857: return 0x1E84; // CAPITAL W WITH DIAERESIS case 0xC858: return 0x1E8C; // CAPITAL X WITH DIAERESIS case 0xC859: return 0x0178; // CAPITAL Y WITH DIAERESIS case 0xC861: return 0x00E4; // small a with diaeresis case 0xC865: return 0x00EB; // small e with diaeresis case 0xC868: return 0x1E27; // small h with diaeresis case 0xC869: return 0x00EF; // small i with diaeresis case 0xC86F: return 0x00F6; // small o with diaeresis case 0xC874: return 0x1E97; // small t with diaeresis case 0xC875: return 0x00FC; // small u with diaeresis case 0xC877: return 0x1E85; // small w with diaeresis case 0xC878: return 0x1E8D; // small x with diaeresis case 0xC879: return 0x00FF; // small y with diaeresis // 4/9 umlaut case 0xC920: return 0x00A8; // [diaeresis] // 4/10 circle above case 0xCA41: return 0x00C5; // CAPITAL A WITH RING ABOVE case 0xCAAD: return 0x016E; // CAPITAL U WITH RING ABOVE case 0xCA61: return 0x00E5; // small a with ring above case 0xCA75: return 0x016F; // small u with ring above case 0xCA77: return 0x1E98; // small w with ring above case 0xCA79: return 0x1E99; // small y with ring above // 4/11 high comma off centre // 4/12 inverted high comma centred // 4/13 double acute accent case 0xCD4F: return 0x0150; // CAPITAL O WITH DOUBLE ACUTE case 0xCD55: return 0x0170; // CAPITAL U WITH DOUBLE ACUTE case 0xCD6F: return 0x0151; // small o with double acute case 0xCD75: return 0x0171; // small u with double acute // 4/14 horn case 0xCE54: return 0x01A0; // LATIN CAPITAL LETTER O WITH HORN case 0xCE55: return 0x01AF; // LATIN CAPITAL LETTER U WITH HORN case 0xCE74: return 0x01A1; // latin small letter o with horn case 0xCE75: return 0x01B0; // latin small letter u with horn // 4/15 caron (hacek) case 0xCF41: return 0x01CD; // CAPITAL A WITH CARON case 0xCF43: return 0x010C; // CAPITAL C WITH CARON case 0xCF44: return 0x010E; // CAPITAL D WITH CARON case 0xCF45: return 0x011A; // CAPITAL E WITH CARON case 0xCF47: return 0x01E6; // CAPITAL G WITH CARON case 0xCF49: return 0x01CF; // CAPITAL I WITH CARON case 0xCF4B: return 0x01E8; // CAPITAL K WITH CARON case 0xCF4C: return 0x013D; // CAPITAL L WITH CARON case 0xCF4E: return 0x0147; // CAPITAL N WITH CARON case 0xCF4F: return 0x01D1; // CAPITAL O WITH CARON case 0xCF52: return 0x0158; // CAPITAL R WITH CARON case 0xCF53: return 0x0160; // CAPITAL S WITH CARON case 0xCF54: return 0x0164; // CAPITAL T WITH CARON case 0xCF55: return 0x01D3; // CAPITAL U WITH CARON case 0xCF5A: return 0x017D; // CAPITAL Z WITH CARON case 0xCF61: return 0x01CE; // small a with caron case 0xCF63: return 0x010D; // small c with caron case 0xCF64: return 0x010F; // small d with caron case 0xCF65: return 0x011B; // small e with caron case 0xCF67: return 0x01E7; // small g with caron case 0xCF69: return 0x01D0; // small i with caron case 0xCF6A: return 0x01F0; // small j with caron case 0xCF6B: return 0x01E9; // small k with caron case 0xCF6C: return 0x013E; // small l with caron case 0xCF6E: return 0x0148; // small n with caron case 0xCF6F: return 0x01D2; // small o with caron case 0xCF72: return 0x0159; // small r with caron case 0xCF73: return 0x0161; // small s with caron case 0xCF74: return 0x0165; // small t with caron case 0xCF75: return 0x01D4; // small u with caron case 0xCF7A: return 0x017E; // small z with caron // 5/0 cedilla case 0xD020: return 0x00B8; // cedilla case 0xD043: return 0x00C7; // CAPITAL C WITH CEDILLA case 0xD044: return 0x1E10; // CAPITAL D WITH CEDILLA case 0xD047: return 0x0122; // CAPITAL G WITH CEDILLA case 0xD048: return 0x1E28; // CAPITAL H WITH CEDILLA case 0xD04B: return 0x0136; // CAPITAL K WITH CEDILLA case 0xD04C: return 0x013B; // CAPITAL L WITH CEDILLA case 0xD04E: return 0x0145; // CAPITAL N WITH CEDILLA case 0xD052: return 0x0156; // CAPITAL R WITH CEDILLA case 0xD053: return 0x015E; // CAPITAL S WITH CEDILLA case 0xD054: return 0x0162; // CAPITAL T WITH CEDILLA case 0xD063: return 0x00E7; // small c with cedilla case 0xD064: return 0x1E11; // small d with cedilla case 0xD067: return 0x0123; // small g with cedilla case 0xD068: return 0x1E29; // small h with cedilla case 0xD06B: return 0x0137; // small k with cedilla case 0xD06C: return 0x013C; // small l with cedilla case 0xD06E: return 0x0146; // small n with cedilla case 0xD072: return 0x0157; // small r with cedilla case 0xD073: return 0x015F; // small s with cedilla case 0xD074: return 0x0163; // small t with cedilla // 5/1 rude // 5/2 hook to left // 5/3 ogonek (hook to right) case 0xD320: return 0x02DB; // ogonek case 0xD341: return 0x0104; // CAPITAL A WITH OGONEK case 0xD345: return 0x0118; // CAPITAL E WITH OGONEK case 0xD349: return 0x012E; // CAPITAL I WITH OGONEK case 0xD34F: return 0x01EA; // CAPITAL O WITH OGONEK case 0xD355: return 0x0172; // CAPITAL U WITH OGONEK case 0xD361: return 0x0105; // small a with ogonek case 0xD365: return 0x0119; // small e with ogonek case 0xD369: return 0x012F; // small i with ogonek case 0xD36F: return 0x01EB; // small o with ogonek case 0xD375: return 0x0173; // small u with ogonek // 5/4 circle below case 0xD441: return 0x1E00; // CAPITAL A WITH RING BELOW case 0xD461: return 0x1E01; // small a with ring below // 5/5 half circle below case 0xF948: return 0x1E2A; // CAPITAL H WITH BREVE BELOW case 0xF968: return 0x1E2B; // small h with breve below // 5/6 dot below case 0xD641: return 0x1EA0; // CAPITAL A WITH DOT BELOW case 0xD642: return 0x1E04; // CAPITAL B WITH DOT BELOW case 0xD644: return 0x1E0C; // CAPITAL D WITH DOT BELOW case 0xD645: return 0x1EB8; // CAPITAL E WITH DOT BELOW case 0xD648: return 0x1E24; // CAPITAL H WITH DOT BELOW case 0xD649: return 0x1ECA; // CAPITAL I WITH DOT BELOW case 0xD64B: return 0x1E32; // CAPITAL K WITH DOT BELOW case 0xD64C: return 0x1E36; // CAPITAL L WITH DOT BELOW case 0xD64D: return 0x1E42; // CAPITAL M WITH DOT BELOW case 0xD64E: return 0x1E46; // CAPITAL N WITH DOT BELOW case 0xD64F: return 0x1ECC; // CAPITAL O WITH DOT BELOW case 0xD652: return 0x1E5A; // CAPITAL R WITH DOT BELOW case 0xD653: return 0x1E62; // CAPITAL S WITH DOT BELOW case 0xD654: return 0x1E6C; // CAPITAL T WITH DOT BELOW case 0xD655: return 0x1EE4; // CAPITAL U WITH DOT BELOW case 0xD656: return 0x1E7E; // CAPITAL V WITH DOT BELOW case 0xD657: return 0x1E88; // CAPITAL W WITH DOT BELOW case 0xD659: return 0x1EF4; // CAPITAL Y WITH DOT BELOW case 0xD65A: return 0x1E92; // CAPITAL Z WITH DOT BELOW case 0xD661: return 0x1EA1; // small a with dot below case 0xD662: return 0x1E05; // small b with dot below case 0xD664: return 0x1E0D; // small d with dot below case 0xD665: return 0x1EB9; // small e with dot below case 0xD668: return 0x1E25; // small h with dot below case 0xD669: return 0x1ECB; // small i with dot below case 0xD66B: return 0x1E33; // small k with dot below case 0xD66C: return 0x1E37; // small l with dot below case 0xD66D: return 0x1E43; // small m with dot below case 0xD66E: return 0x1E47; // small n with dot below case 0xD66F: return 0x1ECD; // small o with dot below case 0xD672: return 0x1E5B; // small r with dot below case 0xD673: return 0x1E63; // small s with dot below case 0xD674: return 0x1E6D; // small t with dot below case 0xD675: return 0x1EE5; // small u with dot below case 0xD676: return 0x1E7F; // small v with dot below case 0xD677: return 0x1E89; // small w with dot below case 0xD679: return 0x1EF5; // small y with dot below case 0xD67A: return 0x1E93; // small z with dot below // 5/7 double dot below case 0xD755: return 0x1E72; // CAPITAL U WITH DIAERESIS BELOW case 0xD775: return 0x1E73; // small u with diaeresis below // 5/8 underline case 0xD820: return 0x005F; // underline // 5/9 double underline case 0xD920: return 0x2017; // double underline // 5/10 small low vertical bar case 0xDA20: return 0x02CC; // // 5/11 circumflex below // 5/12 (this position shall not be used) // 5/13 left half of ligature sign and of double tilde // 5/14 right half of ligature sign // 5/15 right half of double tilde default: myDebug() << "no match for " << c; return QChar(); } } diff --git a/src/utils/iso6937converter.cpp b/src/utils/iso6937converter.cpp index 93af2c58..0f06007e 100644 --- a/src/utils/iso6937converter.cpp +++ b/src/utils/iso6937converter.cpp @@ -1,610 +1,610 @@ /*************************************************************************** Copyright (C) 2006-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 . * * * ***************************************************************************/ // This class is adapted from Iso6937ToUnicode from the MARC4J project, available -// from http://marc4j.tigris.org, with the following notice: +// from https://github.com/marc4j/marc4j, with the following notice: // * Copyright (C) 2002 Bas Peters (mail@bpeters.com) // * Copyright (C) 2002 Yves Pratter (ypratter@club-internet.fr) // // That source was released under the terms of the GNU Lesser General Public // License, version 2.1. In accordance with Condition 3 of that license, // I am applying the terms of the GNU General Public License to the source // code, and including a large portion of it here #include "iso6937converter.h" #include "../tellico_debug.h" #include #include using Tellico::Iso6937Converter; QString Iso6937Converter::toUtf8(const QByteArray& text_) { const uint len = text_.length(); QString result; result.reserve(len); uint pos = 0; for(uint i = 0; i < len; ++i) { uchar c = text_[i]; if(isAscii(c)) { result[pos++] = c; } else if(isCombining(c) && hasNext(i, len)) { QChar d = getCombiningChar(c * 256 + text_[i + 1]); if(!d.isNull()) { result[pos++] = d; ++i; } else { result[pos++] = getChar(c); } } else { result[pos++] = getChar(c); } } result.squeeze(); return result; } inline bool Iso6937Converter::hasNext(uint pos, uint len) { return pos < (len - 1); } inline bool Iso6937Converter::isAscii(uchar c) { return c <= 0x7F; } inline bool Iso6937Converter::isCombining(uchar c) { return c >= 0xC0 && c <= 0xDF; } -// Source : http://anubis.dkuug.dk/JTC1/SC2/WG3/docs/6937cd.pdf +// Source : http://www.open-std.org/jtc1/sc2/wg3/docs/6937cd.pdf QChar Iso6937Converter::getChar(uchar c) { switch(c) { case 0xA0: return 0x00A0; // 10/00 NO-BREAK SPACE case 0xA1: return 0x00A1; // 10/01 INVERTED EXCLAMATION MARK case 0xA2: return 0x00A2; // 10/02 CENT SIGN case 0xA3: return 0x00A3; // 10/03 POUND SIGN // 10/04 (This position shall not be used) case 0xA5: return 0x00A5; // 10/05 YEN SIGN // 10/06 (This position shall not be used) case 0xA7: return 0x00A7; // 10/07 SECTION SIGN case 0xA8: return 0x00A4; // 10/08 CURRENCY SIGN case 0xA9: return 0x2018; // 10/09 LEFT SINGLE QUOTATION MARK case 0xAA: return 0x201C; // 10/10 LEFT DOUBLE QUOTATION MARK case 0xAB: return 0x00AB; // 10/11 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK case 0xAC: return 0x2190; // 10/12 LEFTWARDS ARROW case 0xAD: return 0x2191; // 10/13 UPWARDS ARROW case 0xAE: return 0x2192; // 10/14 RIGHTWARDS ARROW case 0xAF: return 0x2193; // 10/15 DOWNWARDS ARROW case 0xB0: return 0x00B0; // 11/00 DEGREE SIGN case 0xB1: return 0x00B1; // 11/01 PLUS-MINUS SIGN case 0xB2: return 0x00B2; // 11/02 SUPERSCRIPT TWO case 0xB3: return 0x00B3; // 11/03 SUPERSCRIPT THREE case 0xB4: return 0x00D7; // 11/04 MULTIPLICATION SIGN case 0xB5: return 0x00B5; // 11/05 MICRO SIGN case 0xB6: return 0x00B6; // 11/06 PILCROW SIGN case 0xB7: return 0x00B7; // 11/07 MIDDLE DOT case 0xB8: return 0x00F7; // 11/08 DIVISION SIGN case 0xB9: return 0x2019; // 11/09 RIGHT SINGLE QUOTATION MARK case 0xBA: return 0x201D; // 11/10 RIGHT DOUBLE QUOTATION MARK case 0xBB: return 0x00BB; // 11/11 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK case 0xBC: return 0x00BC; // 11/12 VULGAR FRACTION ONE QUARTER case 0xBD: return 0x00BD; // 11/13 VULGAR FRACTION ONE HALF case 0xBE: return 0x00BE; // 11/14 VULGAR FRACTION THREE QUARTERS case 0xBF: return 0x00BF; // 11/15 INVERTED QUESTION MARK // 4/0 to 5/15 diacritic characters case 0xD0: return 0x2015; // 13/00 HORIZONTAL BAR case 0xD1: return 0x00B9; // 13/01 SUPERSCRIPT ONE case 0xD2: return 0x00AE; // 13/02 REGISTERED SIGN case 0xD3: return 0x00A9; // 13/03 COPYRIGHT SIGN case 0xD4: return 0x00AE; // 13/04 TRADE MARK SIGN case 0xD5: return 0x266A; // 13/05 EIGHTH NOTE case 0xD6: return 0x00AC; // 13/06 NOT SIGN case 0xD7: return 0x00A6; // 13/07 BROKEN BAR // 13/08 (This position shall not be used) // 13/09 (This position shall not be used) // 13/10 (This position shall not be used) // 13/11 (This position shall not be used) case 0xDC: return 0x215B; // 13/12 VULGAR FRACTION ONE EIGHTH case 0xDF: return 0x215E; // 13/15 VULGAR FRACTION SEVEN EIGHTHS case 0xE0: return 0x2126; // 14/00 OHM SIGN case 0xE1: return 0x00C6; // 14/01 LATIN CAPITAL LETTER AE case 0xE2: return 0x0110; // 14/02 LATIN CAPITAL LETTER D WITH STROKE case 0xE3: return 0x00AA; // 14/03 FEMININE ORDINAL INDICATOR case 0xE4: return 0x0126; // 14/04 LATIN CAPITAL LETTER H WITH STROKE // 14/05 (This position shall not be used) case 0xE6: return 0x0132; // 14/06 LATIN CAPITAL LIGATURE IJ case 0xE7: return 0x013F; // 14/07 LATIN CAPITAL LETTER L WITH MIDDLE DOT case 0xE8: return 0x0141; // 14/08 LATIN CAPITAL LETTER L WITH STROKE case 0xE9: return 0x00D8; // 14/09 LATIN CAPITAL LETTER O WITH STROKE case 0xEA: return 0x0152; // 14/10 LATIN CAPITAL LIGATURE OE case 0xEB: return 0x00BA; // 14/11 MASCULINE ORDINAL INDICATOR case 0xEC: return 0x00DE; // 14/12 LATIN CAPITAL LETTER THORN case 0xED: return 0x0166; // 14/13 LATIN CAPITAL LETTER T WITH STROKE case 0xEE: return 0x014A; // 14/14 LATIN CAPITAL LETTER ENG case 0xEF: return 0x0149; // 14/15 LATIN SMALL LETTER N PRECEDED BY APOSTROPHE case 0xF0: return 0x0138; // 15/00 LATIN SMALL LETTER KRA case 0xF1: return 0x00E6; // 15/01 LATIN SMALL LETTER AE case 0xF2: return 0x0111; // 15/02 LATIN SMALL LETTER D WITH STROKE case 0xF3: return 0x00F0; // 15/03 LATIN SMALL LETTER ETH case 0xF4: return 0x0127; // 15/04 LATIN SMALL LETTER H WITH STROKE case 0xF5: return 0x0131; // 15/05 LATIN SMALL LETTER DOTLESS I case 0xF6: return 0x0133; // 15/06 LATIN SMALL LIGATURE IJ case 0xF7: return 0x0140; // 15/07 LATIN SMALL LETTER L WITH MIDDLE DOT case 0xF8: return 0x0142; // 15/08 LATIN SMALL LETTER L WITH STROKE case 0xF9: return 0x00F8; // 15/09 LATIN SMALL LETTER O WITH STROKE case 0xFA: return 0x0153; // 15/10 LATIN SMALL LIGATURE OE case 0xFB: return 0x00DF; // 15/11 LATIN SMALL LETTER SHARP S case 0xFC: return 0x00FE; // 15/12 LATIN SMALL LETTER THORN case 0xFD: return 0x0167; // 15/13 LATIN SMALL LETTER T WITH STROKE case 0xFE: return 0x014B; // 15/14 LATIN SMALL LETTER ENG case 0xFF: return 0x00AD; // 15/15 SOFT HYPHEN$ default: return QLatin1Char(c); } } QChar Iso6937Converter::getCombiningChar(uint c) { switch(c) { // 12/00 (This position shall not be used) // 12/01 non-spacing grave accent case 0xC141: return 0x00C0; // LATIN CAPITAL LETTER A WITH GRAVE case 0xC145: return 0x00C8; // LATIN CAPITAL LETTER E WITH GRAVE case 0xC149: return 0x00CC; // LATIN CAPITAL LETTER I WITH GRAVE case 0xC14F: return 0x00D2; // LATIN CAPITAL LETTER O WITH GRAVE case 0xC155: return 0x00D9; // LATIN CAPITAL LETTER U WITH GRAVE case 0xC161: return 0x00E0; // LATIN SMALL LETTER A WITH GRAVE case 0xC165: return 0x00E8; // LATIN SMALL LETTER E WITH GRAVE case 0xC169: return 0x00EC; // LATIN SMALL LETTER I WITH GRAVE case 0xC16F: return 0x00F2; // LATIN SMALL LETTER O WITH GRAVE case 0xC175: return 0x00F9; // LATIN SMALL LETTER U WITH GRAVE // 12/02 non-spacing acute accent case 0xC220: return 0x00B4; // ACUTE ACCENT case 0xC241: return 0x00C1; // LATIN CAPITAL LETTER A WITH ACUTE case 0xC243: return 0x0106; // LATIN CAPITAL LETTER C WITH ACUTE case 0xC245: return 0x00C9; // LATIN CAPITAL LETTER E WITH ACUTE case 0xC249: return 0x00CD; // LATIN CAPITAL LETTER I WITH ACUTE case 0xC24C: return 0x0139; // LATIN CAPITAL LETTER L WITH ACUTE case 0xC24E: return 0x0143; // LATIN CAPITAL LETTER N WITH ACUTE case 0xC24F: return 0x00D3; // LATIN CAPITAL LETTER O WITH ACUTE case 0xC252: return 0x0154; // LATIN CAPITAL LETTER R WITH ACUTE case 0xC253: return 0x015A; // LATIN CAPITAL LETTER S WITH ACUTE case 0xC255: return 0x00DA; // LATIN CAPITAL LETTER U WITH ACUTE case 0xC259: return 0x00DD; // LATIN CAPITAL LETTER Y WITH ACUTE case 0xC25A: return 0x0179; // LATIN CAPITAL LETTER Z WITH ACUTE case 0xC261: return 0x00E1; // LATIN SMALL LETTER A WITH ACUTE case 0xC263: return 0x0107; // LATIN SMALL LETTER C WITH ACUTE case 0xC265: return 0x00E9; // LATIN SMALL LETTER E WITH ACUTE case 0xC267: return 0x01F5; // LATIN SMALL LETTER G WITH CEDILLA(4) case 0xC269: return 0x00ED; // LATIN SMALL LETTER I WITH ACUTE case 0xC26C: return 0x013A; // LATIN SMALL LETTER L WITH ACUTE case 0xC26E: return 0x0144; // LATIN SMALL LETTER N WITH ACUTE case 0xC26F: return 0x00F3; // LATIN SMALL LETTER O WITH ACUTE case 0xC272: return 0x0155; // LATIN SMALL LETTER R WITH ACUTE case 0xC273: return 0x015B; // LATIN SMALL LETTER S WITH ACUTE case 0xC275: return 0x00FA; // LATIN SMALL LETTER U WITH ACUTE case 0xC279: return 0x00FD; // LATIN SMALL LETTER Y WITH ACUTE case 0xC27A: return 0x017A; // LATIN SMALL LETTER Z WITH ACUTE // 12/03 non-spacing circumflex accent case 0xC341: return 0x00C2; // LATIN CAPITAL LETTER A WITH CIRCUMFLEX case 0xC343: return 0x0108; // LATIN CAPITAL LETTER C WITH CIRCUMFLEX case 0xC345: return 0x00CA; // LATIN CAPITAL LETTER E WITH CIRCUMFLEX case 0xC347: return 0x011C; // LATIN CAPITAL LETTER G WITH CIRCUMFLEX case 0xC348: return 0x0124; // LATIN CAPITAL LETTER H WITH CIRCUMFLEX case 0xC349: return 0x00CE; // LATIN CAPITAL LETTER I WITH CIRCUMFLEX case 0xC34A: return 0x0134; // LATIN CAPITAL LETTER J WITH CIRCUMFLEX case 0xC34F: return 0x00D4; // LATIN CAPITAL LETTER O WITH CIRCUMFLEX case 0xC353: return 0x015C; // LATIN CAPITAL LETTER S WITH CIRCUMFLEX case 0xC355: return 0x00DB; // LATIN CAPITAL LETTER U WITH CIRCUMFLEX case 0xC357: return 0x0174; // LATIN CAPITAL LETTER W WITH CIRCUMFLEX case 0xC359: return 0x0176; // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX case 0xC361: return 0x00E2; // LATIN SMALL LETTER A WITH CIRCUMFLEX case 0xC363: return 0x0109; // LATIN SMALL LETTER C WITH CIRCUMFLEX case 0xC365: return 0x00EA; // LATIN SMALL LETTER E WITH CIRCUMFLEX case 0xC367: return 0x011D; // LATIN SMALL LETTER G WITH CIRCUMFLEX case 0xC368: return 0x0125; // LATIN SMALL LETTER H WITH CIRCUMFLEX case 0xC369: return 0x00EE; // LATIN SMALL LETTER I WITH CIRCUMFLEX case 0xC36A: return 0x0135; // LATIN SMALL LETTER J WITH CIRCUMFLEX case 0xC36F: return 0x00F4; // LATIN SMALL LETTER O WITH CIRCUMFLEX case 0xC373: return 0x015D; // LATIN SMALL LETTER S WITH CIRCUMFLEX case 0xC375: return 0x00FB; // LATIN SMALL LETTER U WITH CIRCUMFLEX case 0xC377: return 0x0175; // LATIN SMALL LETTER W WITH CIRCUMFLEX case 0xC379: return 0x0177; // LATIN SMALL LETTER Y WITH CIRCUMFLEX // 12/04 non-spacing tilde case 0xC441: return 0x00C3; // LATIN CAPITAL LETTER A WITH TILDE case 0xC449: return 0x0128; // LATIN CAPITAL LETTER I WITH TILDE case 0xC44E: return 0x00D1; // LATIN CAPITAL LETTER N WITH TILDE case 0xC44F: return 0x00D5; // LATIN CAPITAL LETTER O WITH TILDE case 0xC455: return 0x0168; // LATIN CAPITAL LETTER U WITH TILDE case 0xC461: return 0x00E3; // LATIN SMALL LETTER A WITH TILDE case 0xC469: return 0x0129; // LATIN SMALL LETTER I WITH TILDE case 0xC46E: return 0x00F1; // LATIN SMALL LETTER N WITH TILDE case 0xC46F: return 0x00F5; // LATIN SMALL LETTER O WITH TILDE case 0xC475: return 0x0169; // LATIN SMALL LETTER U WITH TILDE // 12/05 non-spacing macron case 0xC541: return 0x0100; // LATIN CAPITAL LETTER A WITH MACRON case 0xC545: return 0x0112; // LATIN CAPITAL LETTER E WITH MACRON case 0xC549: return 0x012A; // LATIN CAPITAL LETTER I WITH MACRON case 0xC54F: return 0x014C; // LATIN CAPITAL LETTER O WITH MACRON case 0xC555: return 0x016A; // LATIN CAPITAL LETTER U WITH MACRON case 0xC561: return 0x0101; // LATIN SMALL LETTER A WITH MACRON case 0xC565: return 0x0113; // LATIN SMALL LETTER E WITH MACRON case 0xC569: return 0x012B; // LATIN SMALL LETTER I WITH MACRON case 0xC56F: return 0x014D; // LATIN SMALL LETTER O WITH MACRON case 0xC575: return 0x016B; // LATIN SMALL LETTER U WITH MACRON // 12/06 non-spacing breve case 0xC620: return 0x02D8; // BREVE case 0xC641: return 0x0102; // LATIN CAPITAL LETTER A WITH BREVE case 0xC647: return 0x011E; // LATIN CAPITAL LETTER G WITH BREVE case 0xC655: return 0x016C; // LATIN CAPITAL LETTER U WITH BREVE case 0xC661: return 0x0103; // LATIN SMALL LETTER A WITH BREVE case 0xC667: return 0x011F; // LATIN SMALL LETTER G WITH BREVE case 0xC675: return 0x016D; // LATIN SMALL LETTER U WITH BREVE // 12/07 non-spacing dot above case 0xC743: return 0x010A; // LATIN CAPITAL LETTER C WITH DOT ABOVE case 0xC745: return 0x0116; // LATIN CAPITAL LETTER E WITH DOT ABOVE case 0xC747: return 0x0120; // LATIN CAPITAL LETTER G WITH DOT ABOVE case 0xC749: return 0x0130; // LATIN CAPITAL LETTER I WITH DOT ABOVE case 0xC75A: return 0x017B; // LATIN CAPITAL LETTER Z WITH DOT ABOVE case 0xC763: return 0x010B; // LATIN SMALL LETTER C WITH DOT ABOVE case 0xC765: return 0x0117; // LATIN SMALL LETTER E WITH DOT ABOVE case 0xC767: return 0x0121; // LATIN SMALL LETTER G WITH DOT ABOVE case 0xC77A: return 0x017C; // LATIN SMALL LETTER Z WITH DOT ABOVE // 12/08 non-spacing diaeresis case 0xC820: return 0x00A8; // DIAERESIS case 0xC841: return 0x00C4; // LATIN CAPITAL LETTER A WITH DIAERESIS case 0xC845: return 0x00CB; // LATIN CAPITAL LETTER E WITH DIAERESIS case 0xC849: return 0x00CF; // LATIN CAPITAL LETTER I WITH DIAERESIS case 0xC84F: return 0x00D6; // LATIN CAPITAL LETTER O WITH DIAERESIS case 0xC855: return 0x00DC; // LATIN CAPITAL LETTER U WITH DIAERESIS case 0xC859: return 0x0178; // LATIN CAPITAL LETTER Y WITH DIAERESIS case 0xC861: return 0x00E4; // LATIN SMALL LETTER A WITH DIAERESIS case 0xC865: return 0x00EB; // LATIN SMALL LETTER E WITH DIAERESIS case 0xC869: return 0x00EF; // LATIN SMALL LETTER I WITH DIAERESIS case 0xC86F: return 0x00F6; // LATIN SMALL LETTER O WITH DIAERESIS case 0xC875: return 0x00FC; // LATIN SMALL LETTER U WITH DIAERESIS case 0xC879: return 0x00FF; // LATIN SMALL LETTER Y WITH DIAERESIS // 12/09 (This position shall not be used) // 12/10 non-spacing ring above case 0xCA20: return 0x02DA; // RING ABOVE case 0xCA41: return 0x00C5; // LATIN CAPITAL LETTER A WITH RING ABOVE case 0xCA55: return 0x016E; // LATIN CAPITAL LETTER U WITH RING ABOVE case 0xCA61: return 0x00E5; // LATIN SMALL LETTER A WITH RING ABOVE case 0xCA75: return 0x016F; // LATIN SMALL LETTER U WITH RING ABOVE // 12/11 non-spacing cedilla case 0xCB20: return 0x00B8; // CEDILLA case 0xCB43: return 0x00C7; // LATIN CAPITAL LETTER C WITH CEDILLA case 0xCB47: return 0x0122; // LATIN CAPITAL LETTER G WITH CEDILLA case 0xCB4B: return 0x0136; // LATIN CAPITAL LETTER K WITH CEDILLA case 0xCB4C: return 0x013B; // LATIN CAPITAL LETTER L WITH CEDILLA case 0xCB4E: return 0x0145; // LATIN CAPITAL LETTER N WITH CEDILLA case 0xCB52: return 0x0156; // LATIN CAPITAL LETTER R WITH CEDILLA case 0xCB53: return 0x015E; // LATIN CAPITAL LETTER S WITH CEDILLA case 0xCB54: return 0x0162; // LATIN CAPITAL LETTER T WITH CEDILLA case 0xCB63: return 0x00E7; // LATIN SMALL LETTER C WITH CEDILLA case 0xCB67: return 0x0123; // small g with cedilla case 0xCB6B: return 0x0137; // LATIN SMALL LETTER K WITH CEDILLA case 0xCB6C: return 0x013C; // LATIN SMALL LETTER L WITH CEDILLA case 0xCB6E: return 0x0146; // LATIN SMALL LETTER N WITH CEDILLA case 0xCB72: return 0x0157; // LATIN SMALL LETTER R WITH CEDILLA case 0xCB73: return 0x015F; // LATIN SMALL LETTER S WITH CEDILLA case 0xCB74: return 0x0163; // LATIN SMALL LETTER T WITH CEDILLA // 12/12 (This position shall not be used) // 12/13 non-spacing double acute accent case 0xCD4F: return 0x0150; // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE case 0xCD55: return 0x0170; // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE case 0xCD6F: return 0x0151; // LATIN SMALL LETTER O WITH DOUBLE ACUTE case 0xCD75: return 0x0171; // LATIN SMALL LETTER U WITH DOUBLE ACUTE // 12/14 non-spacing ogonek case 0xCE20: return 0x02DB; // ogonek case 0xCE41: return 0x0104; // LATIN CAPITAL LETTER A WITH OGONEK case 0xCE45: return 0x0118; // LATIN CAPITAL LETTER E WITH OGONEK case 0xCE49: return 0x012E; // LATIN CAPITAL LETTER I WITH OGONEK case 0xCE55: return 0x0172; // LATIN CAPITAL LETTER U WITH OGONEK case 0xCE61: return 0x0105; // LATIN SMALL LETTER A WITH OGONEK case 0xCE65: return 0x0119; // LATIN SMALL LETTER E WITH OGONEK case 0xCE69: return 0x012F; // LATIN SMALL LETTER I WITH OGONEK case 0xCE75: return 0x0173; // LATIN SMALL LETTER U WITH OGONEK // 12/15 non-spacing caron case 0xCF20: return 0x02C7; // CARON case 0xCF43: return 0x010C; // LATIN CAPITAL LETTER C WITH CARON case 0xCF44: return 0x010E; // LATIN CAPITAL LETTER D WITH CARON case 0xCF45: return 0x011A; // LATIN CAPITAL LETTER E WITH CARON case 0xCF4C: return 0x013D; // LATIN CAPITAL LETTER L WITH CARON case 0xCF4E: return 0x0147; // LATIN CAPITAL LETTER N WITH CARON case 0xCF52: return 0x0158; // LATIN CAPITAL LETTER R WITH CARON case 0xCF53: return 0x0160; // LATIN CAPITAL LETTER S WITH CARON case 0xCF54: return 0x0164; // LATIN CAPITAL LETTER T WITH CARON case 0xCF5A: return 0x017D; // LATIN CAPITAL LETTER Z WITH CARON case 0xCF63: return 0x010D; // LATIN SMALL LETTER C WITH CARON case 0xCF64: return 0x010F; // LATIN SMALL LETTER D WITH CARON case 0xCF65: return 0x011B; // LATIN SMALL LETTER E WITH CARON case 0xCF6C: return 0x013E; // LATIN SMALL LETTER L WITH CARON case 0xCF6E: return 0x0148; // LATIN SMALL LETTER N WITH CARON case 0xCF72: return 0x0159; // LATIN SMALL LETTER R WITH CARON case 0xCF73: return 0x0161; // LATIN SMALL LETTER S WITH CARON case 0xCF74: return 0x0165; // LATIN SMALL LETTER T WITH CARON case 0xCF7A: return 0x017E; // LATIN SMALL LETTER Z WITH CARON default: myDebug() << "no match for " << c; return QChar(); } } diff --git a/src/utils/lccnvalidator.h b/src/utils/lccnvalidator.h index 9c9898d2..45c00d0c 100644 --- a/src/utils/lccnvalidator.h +++ b/src/utils/lccnvalidator.h @@ -1,56 +1,56 @@ /*************************************************************************** Copyright (C) 2008-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 . * * * ***************************************************************************/ #ifndef TELLICO_LCCNVALIDATOR_H #define TELLICO_LCCNVALIDATOR_H #include namespace Tellico { /** * Library of Congress Control Number validator * - * see http://www.loc.gov/marc/lccn_structure.html + * see https://www.loc.gov/marc/lccn_structure.html * * These are all valid * - 89-456 * - 2001-1114 * - gm 71-2450 */ class LCCNValidator : public QRegExpValidator { Q_OBJECT public: LCCNValidator(QObject* parent = nullptr); /** * Returns the formalized version as dictated by LOC search - * http://catalog.loc.gov/help/number.htm + * https://catalog.loc.gov/vwebv/ui/en_US/htdocs/help/searchBrowse.html */ static QString formalize(const QString& value); }; } #endif