diff --git a/mobile/sailfishos/bibsearch.pro b/mobile/sailfishos/bibsearch.pro index ac591f33..3028efe3 100644 --- a/mobile/sailfishos/bibsearch.pro +++ b/mobile/sailfishos/bibsearch.pro @@ -1,109 +1,109 @@ TARGET = harbour-bibsearch CONFIG += sailfishapp SOURCES += src/main.cpp src/searchenginelist.cpp \ src/bibliographymodel.cpp ../../src/data/value.cpp \ ../../src/data/entry.cpp ../../src/data/macro.cpp \ ../../src/data/comment.cpp ../../src/data/file.cpp \ ../../src/data/preamble.cpp ../../src/data/element.cpp \ ../../src/networking/internalnetworkaccessmanager.cpp \ ../../src/networking/onlinesearch/onlinesearchabstract.cpp \ ../../src/networking/onlinesearch/onlinesearchbibsonomy.cpp \ ../../src/networking/onlinesearch/onlinesearchacmportal.cpp \ ../../src/networking/onlinesearch/onlinesearchsciencedirect.cpp \ ../../src/networking/onlinesearch/onlinesearchgooglescholar.cpp \ ../../src/networking/onlinesearch/onlinesearchjstor.cpp \ ../../src/networking/onlinesearch/onlinesearchspringerlink.cpp \ ../../src/networking/onlinesearch/onlinesearchieeexplore.cpp \ ../../src/networking/onlinesearch/onlinesearcharxiv.cpp \ ../../src/networking/onlinesearch/onlinesearchingentaconnect.cpp \ ../../src/networking/onlinesearch/onlinesearchpubmed.cpp \ ../../src/global/kbibtex.cpp \ ../../src/io/encoderxml.cpp ../../src/io/encoder.cpp \ ../../src/io/encoderlatex.cpp \ ../../src/io/fileimporter.cpp \ ../../src/io/fileimporterbibtex.cpp \ ../../src/io/textencoder.cpp ../../src/io/xsltransform.cpp \ ../../src/config/preferences.cpp \ ../../src/config/bibtexfields.cpp \ ../../src/config/bibtexentries.cpp \ ../../src/config/logging_config.cpp \ ../../src/networking/logging_networking.cpp \ ../../src/data/logging_data.cpp ../../src/io/logging_io.cpp HEADERS += src/bibliographymodel.h src/searchenginelist.h \ src/kbibtexnamespace.h ../../src/data/entry.h \ ../../src/data/macro.h ../../src/data/comment.h \ ../../src/data/file.h ../../src/data/preamble.h \ ../../src/data/value.h ../../src/data/element.h \ ../../src/networking/internalnetworkaccessmanager.h \ ../../src/networking/onlinesearch/onlinesearchabstract.h \ ../../src/networking/onlinesearch/onlinesearchbibsonomy.h \ ../../src/networking/onlinesearch/onlinesearchacmportal.h \ ../../src/networking/onlinesearch/onlinesearchsciencedirect.h \ ../../src/networking/onlinesearch/onlinesearchgooglescholar.h \ ../../src/networking/onlinesearch/onlinesearchjstor.h \ ../../src/networking/onlinesearch/onlinesearcharxiv.h \ ../../src/networking/onlinesearch/onlinesearchingentaconnect.h \ ../../src/networking/onlinesearch/onlinesearchspringerlink.h \ ../../src/networking/onlinesearch/onlinesearchieeexplore.h \ ../../src/networking/onlinesearch/onlinesearchpubmed.h \ ../../src/global/kbibtex.h \ ../../src/io/encoderxml.h ../../src/io/encoder.h \ ../../src/io/encoderlatex.h ../../src/io/fileimporter.h \ ../../src/io/fileimporterbibtex.h \ ../../src/io/textencoder.h ../../src/io/xsltransform.h \ ../../src/config/preferences.h \ ../../src/config/bibtexfields.h ../../src/config/bibtexentries.h OTHER_FILES += qml/pages/SearchForm.qml qml/pages/EntryView.qml \ qml/pages/AboutPage.qml qml/pages/BibliographyListView.qml \ qml/cover/CoverPage.qml qml/BibSearch.qml \ qml/pages/AboutPage.qml qml/pages/SearchEngineListView.qml \ rpm/$${TARGET}.spec \ rpm/$${TARGET}.yaml \ translations/*.ts \ $${TARGET}.desktop RESOURCES += sailfishos_res.qrc QT += xmlpatterns DEFINES += KBIBTEXGLOBAL_EXPORT= KBIBTEXCONFIG_EXPORT= KBIBTEXDATA_EXPORT= KBIBTEXIO_EXPORT= KBIBTEXNETWORKING_EXPORT= -INCLUDEPATH += ../../src/global $${OUT_PWD}/src/global ../../src/config $${OUT_PWD}/src/config ../../src/data $${OUT_PWD}/src/data ../../src/networking ../../src/networking/onlinesearch ../../src/io +INCLUDEPATH += ../../src/global $${OUT_PWD}/src/global ../../src/config $${OUT_PWD}/src/config ../../src/data $${OUT_PWD}/src/data ../../src/io $${OUT_PWD}/src/io ../../src/networking ../../src/networking/onlinesearch CONFIG += sailfishapp_i18n sailfishapp_i18n_idbased TRANSLATIONS += \ translations/$${TARGET}-de.ts \ translations/$${TARGET}-en.ts DISTFILES += \ qml/pages/BibliographyListView.qml \ qml/pages/EntryView.qml \ qml/pages/SearchForm.qml \ qml/pages/SettingsPage.qml \ qml/pages/AboutPage.qml xslt.files = ../../xslt/pam2bibtex.xsl ../../xslt/ieeexploreapiv1-to-bibtex.xsl \ ../../xslt/arxiv2bibtex.xsl ../../xslt/pubmed2bibtex.xsl xslt.path = /usr/share/$${TARGET} INSTALLS += xslt icon86.files = icons/86/$${TARGET}.png icon86.path = /usr/share/icons/hicolor/86x86/apps/ INSTALLS += icon86 icon108.files = icons/108/$${TARGET}.png icon108.path = /usr/share/icons/hicolor/108x108/apps/ INSTALLS += icon108 icon128.files = icons/128/$${TARGET}.png icon128.path = /usr/share/icons/hicolor/128x128/apps/ INSTALLS += icon128 icon172.files = icons/172/$${TARGET}.png icon172.path = /usr/share/icons/hicolor/172x172/apps/ INSTALLS += icon172 icon256.files = icons/256/$${TARGET}.png icon256.path = /usr/share/icons/hicolor/256x256/apps/ INSTALLS += icon256 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e0931379..e06a1be0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,85 +1,83 @@ # "Unity build" found at # https://cheind.wordpress.com/2009/12/10/reducing-compilation-time-unity-builds/ function(enable_unity_build UB_SUFFIX SOURCE_VARIABLE_NAME) set(files ${${SOURCE_VARIABLE_NAME}}) # Generate a unique filename for the unity build translation unit set(unit_build_file ${CMAKE_CURRENT_BINARY_DIR}/ub_${UB_SUFFIX}.cpp) # Exclude all translation units from compilation set_source_files_properties(${files} PROPERTIES HEADER_FILE_ONLY true) # Open the ub file file(WRITE ${unit_build_file} "// Unity Build generated by CMake\n") # Add include statement for each translation unit foreach(source_file ${files}) file(APPEND ${unit_build_file} "#include <${source_file}>\n") endforeach(source_file) # Complement list of translation units with the name of ub set(${SOURCE_VARIABLE_NAME} ${${SOURCE_VARIABLE_NAME}} ${unit_build_file} PARENT_SCOPE) endfunction(enable_unity_build) if(Qt5WebEngineWidgets_FOUND) add_definitions( -DHAVE_WEBENGINEWIDGETS ) endif(Qt5WebEngineWidgets_FOUND) if(Qt5WebKitWidgets_FOUND) add_definitions( -DHAVE_WEBKITWIDGETS ) endif(Qt5WebKitWidgets_FOUND) if(Qt5WebEngineWidgets_FOUND) if(Qt5WebKitWidgets_FOUND) message(STATUS "Found both QtWebEngine and QtWebKit, preferring to use QtWebEngine") else(Qt5WebKitWidgets_FOUND) message(STATUS "Found QtWebEngine, but not QtWebKit, therefore going to use QtWebEngine") endif(Qt5WebKitWidgets_FOUND) else(Qt5WebEngineWidgets_FOUND) if(Qt5WebKitWidgets_FOUND) message(STATUS "Found QtWebKit, but not QtWebEngine, therefore going to use QtWebKit") else(Qt5WebKitWidgets_FOUND) message(STATUS "Found neither QtWebEngine nor QtWebKit, therefore trying to locate a KPart for HTML data") endif(Qt5WebKitWidgets_FOUND) endif(Qt5WebEngineWidgets_FOUND) add_subdirectory(global) add_subdirectory(config) add_subdirectory(data) -add_subdirectory( - io -) +add_subdirectory(io) add_subdirectory( processing ) add_subdirectory( networking ) add_subdirectory( gui ) add_subdirectory( program ) add_subdirectory( parts ) if( BUILD_TESTING ) add_subdirectory( test ) endif( BUILD_TESTING ) # install( # FILES # kbibtexnamespace.h # DESTINATION # ${INCLUDE_INSTALL_DIR}/kbibtex # COMPONENT # development # ) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 54ce3429..b482a49b 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,132 +1,129 @@ # KBibTeX GUI library set( kbibtexgui_LIB_SRCS field/fieldinput.cpp field/fieldlineedit.cpp field/fieldlistedit.cpp field/colorlabelwidget.cpp file/fileview.cpp file/filedelegate.cpp file/partwidget.cpp file/findduplicatesui.cpp file/clipboard.cpp file/basicfileview.cpp file/sortfilterfilemodel.cpp element/elementeditor.cpp element/elementwidgets.cpp element/findpdfui.cpp element/associatedfilesui.cpp widgets/menulineedit.cpp widgets/filesettingswidget.cpp widgets/filterbar.cpp widgets/radiobuttontreeview.cpp widgets/hidingtabwidget.cpp widgets/starrating.cpp widgets/rangewidget.cpp config/entrylayout.cpp preferences/kbibtexpreferencesdialog.cpp preferences/settingsgeneralwidget.cpp preferences/settingsglobalkeywordswidget.cpp preferences/settingscolorlabelwidget.cpp preferences/settingsuserinterfacewidget.cpp preferences/settingsfileexporterpdfpswidget.cpp preferences/settingsfileexporterwidget.cpp preferences/settingsabstractwidget.cpp preferences/settingsidsuggestionswidget.cpp preferences/settingsidsuggestionseditor.cpp guihelper.cpp italictextitemmodel.cpp valuelistmodel.cpp delayedexecutiontimer.cpp logging_gui.cpp ) set( kbibtexgui_HDRS field/fieldinput.h field/colorlabelwidget.h field/fieldlineedit.h widgets/filterbar.h preferences/settingsuserinterfacewidget.h preferences/kbibtexpreferencesdialog.h preferences/settingsglobalkeywordswidget.h preferences/settingsfileexporterwidget.h preferences/settingsgeneralwidget.h preferences/settingsabstractwidget.h preferences/settingscolorlabelwidget.h preferences/settingsfileexporterpdfpswidget.h preferences/settingsidsuggestionswidget.h preferences/settingsidsuggestionseditor.h guihelper.h valuelistmodel.h italictextitemmodel.h delayedexecutiontimer.h file/findduplicatesui.h file/basicfileview.h file/clipboard.h file/fileview.h file/filedelegate.h file/sortfilterfilemodel.h file/partwidget.h element/findpdfui.h element/elementeditor.h element/associatedfilesui.h ) if(UNITY_BUILD) enable_unity_build(kbibtexgui kbibtexgui_LIB_SRCS) endif(UNITY_BUILD) include_directories( - ${CMAKE_SOURCE_DIR}/src/io - ${CMAKE_BINARY_DIR}/src/io - ${CMAKE_SOURCE_DIR}/src/io/config ${CMAKE_SOURCE_DIR}/src/networking ${CMAKE_BINARY_DIR}/src/networking ${CMAKE_SOURCE_DIR}/src/processing ${CMAKE_BINARY_DIR}/src/processing ${CMAKE_SOURCE_DIR}/src/gui ${CMAKE_BINARY_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui/file ${CMAKE_SOURCE_DIR}/src/gui/dialogs ${CMAKE_SOURCE_DIR}/src/gui/element ${CMAKE_SOURCE_DIR}/src/gui/field ${CMAKE_SOURCE_DIR}/src/gui/widgets ${CMAKE_SOURCE_DIR}/src/gui/config ) add_library( kbibtexgui SHARED ${kbibtexgui_LIB_SRCS} ) target_link_libraries( kbibtexgui Qt5::Core KF5::IconThemes KF5::ItemViews KF5::Completion KF5::TextEditor kbibtexconfig kbibtexdata kbibtexio kbibtexnetworking kbibtexproc ) set_target_properties( kbibtexgui PROPERTIES EXPORT_NAME "kbibtexgui" VERSION ${KBIBTEX_RELEASE_VERSION} SOVERSION ${KBIBTEX_SOVERSION} ) install( TARGETS kbibtexgui ${INSTALL_TARGETS_DEFAULT_ARGS} ) generate_export_header( kbibtexgui ) diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index 123443dc..1123c16f 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -1,97 +1,131 @@ -# KBibTeXIO library - set( kbibtexio_LIB_SRCS encoder.cpp encoderlatex.cpp encoderxml.cpp fileexporterbibtex2html.cpp fileexporterbibtex.cpp fileexporterbibutils.cpp fileexporterbibtexoutput.cpp fileexporter.cpp fileexporterpdf.cpp fileexporterps.cpp fileexporterris.cpp fileexporterrtf.cpp fileexportertoolchain.cpp fileexporterxml.cpp fileexporterxslt.cpp fileimporterbibtex.cpp fileimporterbibutils.cpp fileimporter.cpp fileimporterpdf.cpp fileimporterris.cpp fileinfo.cpp textencoder.cpp bibutils.cpp xsltransform.cpp logging_io.cpp ) -set( - kbibtexio_HDRS - encoder.h - encoderlatex.h - encoderxml.h - fileexporterbibtex2html.h - fileexporterbibtex.h - fileexporterbibutils.h - fileexporterbibtexoutput.h - fileexporter.h - fileexporterpdf.h - fileexporterps.h - fileexporterris.h - fileexporterrtf.h - fileexportertoolchain.h - fileexporterxml.h - fileexporterxslt.h - fileimporterbibtex.h - fileimporterbibutils.h - fileimporter.h - fileimporterpdf.h - fileimporterris.h - fileinfo.h - textencoder.h - bibutils.h - xsltransform.h -) - if(UNITY_BUILD) enable_unity_build(kbibtexio kbibtexio_LIB_SRCS) endif(UNITY_BUILD) -add_library( - kbibtexio +add_library(kbibtexio SHARED ${kbibtexio_LIB_SRCS} ) +generate_export_header(kbibtexio) +add_library(KBibTeX::IO ALIAS kbibtexio) -target_link_libraries( kbibtexio - Qt5::Core - Qt5::Widgets - Qt5::XmlPatterns - Qt5::Concurrent - KF5::I18n - KF5::XmlGui - Poppler::Qt5 - ${ICU_LIBRARIES} - kbibtexconfig - kbibtexdata -) - -set_target_properties( - kbibtexio +set_target_properties(kbibtexio PROPERTIES EXPORT_NAME "kbibtexio" VERSION ${KBIBTEX_RELEASE_VERSION} SOVERSION ${KBIBTEX_SOVERSION} ) +target_include_directories(kbibtexio + INTERFACE + $ +) + +target_link_libraries(kbibtexio + PUBLIC + Qt5::Core + Qt5::Gui + KBibTeX::Data + KBibTeX::Global + PRIVATE + ICU::uc + ICU::i18n + Poppler::Qt5 + Qt5::Concurrent + Qt5::XmlPatterns + KF5::I18n + KBibTeX::Config +) + install( - TARGETS - kbibtexio - ${INSTALL_TARGETS_DEFAULT_ARGS} + TARGETS kbibtexio + EXPORT kbibtexio-targets + ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) -generate_export_header( kbibtexio ) +set_target_properties(kbibtexio PROPERTIES + EXPORT_NAME "IO" +) + +ecm_generate_headers(kbibtexio_HEADERS + HEADER_NAMES + BibUtils + Encoder + EncoderLaTeX + EncoderXML + FileExporter + FileExporterBibTeX + FileExporterBibTeX2HTML + FileExporterBibTeXOutput + FileExporterBibUtils + FileExporterPDF + FileExporterPS + FileExporterRIS + FileExporterRTF + FileExporterToolchain + FileExporterXML + FileExporterXSLT + FileImporter + FileImporterBibTeX + FileImporterBibUtils + FileImporterPDF + FileImporterRIS + FileInfo + TextEncoder + XSLTransform + REQUIRED_HEADERS kbibtexio_HEADERS +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/kbibtexio_export.h + ${kbibtexio_HEADERS} + DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KBibTeX/io + COMPONENT Devel +) + +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/KBibTeXIO-configVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY ExactVersion +) + +configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/cmake/KBibTeXIO-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/KBibTeXIO-config.cmake + INSTALL_DESTINATION ${KDE_INSTALL_LIBDIR}/cmake/KBibTeX +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/KBibTeXIO-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/KBibTeXIO-configVersion.cmake + DESTINATION ${KDE_INSTALL_LIBDIR}/cmake/KBibTeX +) diff --git a/src/io/bibutils.h b/src/io/bibutils.h index 5203f9aa..670f4c79 100644 --- a/src/io/bibutils.h +++ b/src/io/bibutils.h @@ -1,67 +1,69 @@ /*************************************************************************** * Copyright (C) 2004-2014 by Thomas Fischer * * * * 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 KBIBTEX_IO_BIBUTILS_H #define KBIBTEX_IO_BIBUTILS_H #include +#ifdef HAVE_KF5 #include "kbibtexio_export.h" +#endif // HAVE_KF5 /** * This class encapsulates calling the various binary programs of the BibUtils program set. * BibUtils is available at http://sourceforge.net/projects/bibutils/ * * This class is inherited by @see FileImporterBibUtils and @see FileExporterBibUtils, * which make use of its protected functions. * Using this class directly should only happen to call its public static functions. * * @author Thomas Fischer */ class KBIBTEXIO_EXPORT BibUtils { public: BibUtils(const BibUtils &other) = delete; BibUtils &operator= (const BibUtils &other) = delete; ~BibUtils(); enum Format { MODS = 0, BibTeX = 1, BibLaTeX = 2, ISI = 5, RIS = 6, EndNote = 10, EndNoteXML = 11, ADS = 15, WordBib = 16, Copac = 17, Med = 18 }; BibUtils::Format format() const; void setFormat(const BibUtils::Format format); /** * Test if BibUtils is installed. This test checks if a number of known * BibUtils binaries are available (i.e. found in PATH). If any binary * is missing, it is assumed that BibUtils is not available. The test is * performed only once and the result cached for future calls to this function. * @return true if BibUtils is correctly installed, false otherwise */ static bool available(); protected: explicit BibUtils(); // TODO migrate to KJob or KCompositeJob bool convert(QIODevice &source, const BibUtils::Format sourceFormat, QIODevice &destination, const BibUtils::Format destinationFormat) const; private: class Private; Private *const d; }; #endif // KBIBTEX_IO_BIBUTILS_H diff --git a/src/io/cmake/KBibTeXIO-config.cmake.in b/src/io/cmake/KBibTeXIO-config.cmake.in new file mode 100644 index 00000000..15564134 --- /dev/null +++ b/src/io/cmake/KBibTeXIO-config.cmake.in @@ -0,0 +1,31 @@ +@PACKAGE_INIT@ + +find_package( + Qt5 @QT_MIN_VERSION@ + CONFIG + REQUIRED + Core + Gui +) + +find_package( + KF5 @KF5_MIN_VERSION@ + CONFIG + REQUIRED + I18n +) + +find_package( + KBibTeX @PROJECT_VERSION@ EXACT + CONFIG + REQUIRED + Config + Data + Global +) + +if(NOT TARGET KBibTeX::IO) + include("${KBibTeXIO_CMAKE_DIR}/KBibTeXIO-targets.cmake") +endif() + +set(KBibTeXIO_LIBRARIES KBibTeX::IO) diff --git a/src/io/encoder.cpp b/src/io/encoder.cpp index 8c7c07a5..cc354471 100644 --- a/src/io/encoder.cpp +++ b/src/io/encoder.cpp @@ -1,99 +1,131 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 "encoder.h" +#ifdef HAVE_ICU +#include +#endif // HAVE_ICU + #include "logging_io.h" +#ifdef HAVE_ICU +class Encoder::Private +{ +public: + icu::Transliterator *translit; + + Private() + : translit(nullptr) + { + /// Create an ICU Transliterator, configured to + /// transliterate virtually anything into plain ASCII + UErrorCode uec = U_ZERO_ERROR; + translit = icu::Transliterator::createInstance("Any-Latin;Latin-ASCII", UTRANS_FORWARD, uec); + if (U_FAILURE(uec)) { + qCWarning(LOG_KBIBTEX_IO) << "Error creating an ICU Transliterator instance: " << u_errorName(uec); + if (translit != nullptr) delete translit; + translit = nullptr; + } + } + + ~Private() + { + if (translit != nullptr) + delete translit; + } +}; +#endif // HAVE_ICU + + Encoder::Encoder() +#ifdef HAVE_ICU + : d(new Encoder::Private()) +#endif // HAVE_ICU +{ + /// nothing +} + +Encoder::~Encoder() { #ifdef HAVE_ICU - /// Create an ICU Transliterator, configured to - /// transliterate virtually anything into plain ASCII - UErrorCode uec = U_ZERO_ERROR; - m_trans = icu::Transliterator::createInstance("Any-Latin;Latin-ASCII", UTRANS_FORWARD, uec); - if (U_FAILURE(uec)) { - qCWarning(LOG_KBIBTEX_IO) << "Error creating an ICU Transliterator instance: " << u_errorName(uec); - if (m_trans != nullptr) delete m_trans; - m_trans = nullptr; - } + delete d; #endif // HAVE_ICU } const Encoder &Encoder::instance() { static const Encoder self; return self; } QString Encoder::decode(const QString &text) const { return text; } QString Encoder::encode(const QString &text, const TargetEncoding) const { return text; } #ifdef HAVE_ICU QString Encoder::convertToPlainAscii(const QString &ninput) const { /// Previously, iconv's //TRANSLIT feature had been used here. /// However, the transliteration is locale-specific as discussed /// here: /// http://taschenorakel.de/mathias/2007/11/06/iconv-transliterations/ /// Therefore, iconv is not an acceptable solution. /// /// Instead, "International Components for Unicode" (ICU) is used. /// It is already a dependency for Qt, so there is no "cost" involved /// in using it. /// Preprocessing where ICU may give unexpected results otherwise QString input = ninput; input = input.replace(QChar(0x2013), QStringLiteral("--")).replace(QChar(0x2014), QStringLiteral("---")); const int inputLen = input.length(); /// Make a copy of the input string into an array of UChar UChar *uChars = new UChar[inputLen]; for (int i = 0; i < inputLen; ++i) uChars[i] = input.at(i).unicode(); /// Create an ICU-specific unicode string icu::UnicodeString uString = icu::UnicodeString(uChars, inputLen); /// Perform the actual transliteration, modifying Unicode string - if (m_trans != nullptr) m_trans->transliterate(uString); + if (d->translit != nullptr) d->translit->transliterate(uString); /// Create regular C++ string from Unicode string std::string cppString; uString.toUTF8String(cppString); /// Clean up any mess delete[] uChars; /// Convert regular C++ to Qt-specific QString, /// should work as cppString contains only ASCII text return QString::fromStdString(cppString); } #endif // HAVE_ICU bool Encoder::containsOnlyAscii(const QString &ntext) { /// Perform Canonical Decomposition followed by Canonical Composition const QString text = ntext.normalized(QString::NormalizationForm_C); for (const QChar &c : text) if (c.unicode() > 127) return false; return true; } - diff --git a/src/io/encoder.h b/src/io/encoder.h index 1b71a3cd..ff40839c 100644 --- a/src/io/encoder.h +++ b/src/io/encoder.h @@ -1,88 +1,80 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 KBIBTEX_IO_ENCODER_H #define KBIBTEX_IO_ENCODER_H +#include + #ifdef HAVE_KF5 #include "kbibtexio_export.h" #endif // HAVE_KF5 -#ifdef HAVE_ICU -#include -#endif // HAVE_ICU - -#include - /** * Base class for that convert between different textual representations * for non-ASCII characters. Examples for external textual representations * are \"a in LaTeX and ä in XML. * @author Thomas Fischer */ class KBIBTEXIO_EXPORT Encoder { public: enum TargetEncoding {TargetEncodingASCII = 0, TargetEncodingUTF8 = 1}; static const Encoder &instance(); - virtual ~Encoder() { -#ifdef HAVE_ICU - if (m_trans != nullptr) - delete m_trans; -#endif // HAVE_ICU - } + virtual ~Encoder(); /** * Decode from external textual representation to internal (UTF-8) representation. * @param text text in external textual representation * @return text in internal (UTF-8) representation */ virtual QString decode(const QString &text) const; /** * Encode from internal (UTF-8) representation to external textual representation. * Output may be restricted to ASCII (non-ASCII characters will be rewritten depending * on concrete Encoder class, for example as 'ä' as XML or '\"a' for LaTeX) * or UTF-8 (all characters allowed, only 'special ones' rewritten, for example * '&' for XML and '\&' for LaTeX). * @param text in internal (UTF-8) representation * @param targetEncoding allow either only ASCII output or UTF-8 output. * @return text text in external textual representation */ virtual QString encode(const QString &text, const TargetEncoding targetEncoding) const; #ifdef HAVE_ICU QString convertToPlainAscii(const QString &input) const; #else // HAVE_ICU /// Dummy implementation without ICU inline QString convertToPlainAscii(const QString &input) const { return input; } #endif // HAVE_ICU static bool containsOnlyAscii(const QString &text); protected: Encoder(); private: #ifdef HAVE_ICU - icu::Transliterator *m_trans; + class Private; + Private *const d; #endif // HAVE_ICU }; #endif // KBIBTEX_IO_ENCODER_H diff --git a/src/io/encoderlatex.h b/src/io/encoderlatex.h index 00fab28b..d2a717c0 100644 --- a/src/io/encoderlatex.h +++ b/src/io/encoderlatex.h @@ -1,90 +1,90 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 KBIBTEX_IO_ENCODERLATEX_H #define KBIBTEX_IO_ENCODERLATEX_H +#include + +#include + #ifdef HAVE_KF5 #include "kbibtexio_export.h" #endif // HAVE_KF5 -#include - -#include "encoder.h" - /** * Base class for that convert between different textual representations * for non-ASCII characters, specialized for LaTeX. For example, this * class can "translate" between \"a and its UTF-8 representation. * @author Thomas Fischer */ -class KBIBTEXIO_EXPORT EncoderLaTeX: public Encoder +class KBIBTEXIO_EXPORT EncoderLaTeX : public Encoder { public: QString decode(const QString &text) const override; QString encode(const QString &text, const TargetEncoding targetEncoding) const override; static const EncoderLaTeX &instance(); ~EncoderLaTeX() override; protected: EncoderLaTeX(); /** * This data structure keeps individual characters that have * a special purpose in LaTeX and therefore needs to be escaped * both in text and in math mode by prefixing with a backlash. */ static const QChar encoderLaTeXProtectedSymbols[]; /** * This data structure keeps individual characters that have * a special purpose in LaTeX in text mode and therefore needs * to be escaped by prefixing with a backlash. In math mode, * those have a different purpose and may not be escaped there. */ static const QChar encoderLaTeXProtectedTextOnlySymbols[]; /** * Check if input data contains a verbatim command like \url{...}, * append it to output, and update the position to point to the next * character after the verbatim command. * @return 'true' if a verbatim command has been copied, otherwise 'false'. */ bool testAndCopyVerbatimCommands(const QString &input, int &pos, QString &output) const; private: Q_DISABLE_COPY(EncoderLaTeX) /** * Check if character c represents a modifier like * a quotation mark in \"a. Return the modifier's * index in the lookup table or -1 if not a known * modifier. */ int modifierInLookupTable(const QChar modifier) const; /** * Return a string that represents the part of * the base string starting from startFrom contains * only alpha characters (a-z,A-Z). * Return value may be an empty string. */ QString readAlphaCharacters(const QString &base, int startFrom) const; }; #endif // KBIBTEX_IO_ENCODERLATEX_H diff --git a/src/io/encoderxml.h b/src/io/encoderxml.h index cbe5980b..660aaaf1 100644 --- a/src/io/encoderxml.h +++ b/src/io/encoderxml.h @@ -1,55 +1,55 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * 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 KBIBTEX_IO_ENCODERXML_H #define KBIBTEX_IO_ENCODERXML_H +#include + #ifdef HAVE_KF5 #include "kbibtexio_export.h" #endif // HAVE_KF5 -#include "encoder.h" - class QString; /** * Base class for that convert between different textual representations * for non-ASCII characters, specialized for XML. * Example for a character to convert is ä. * @author Thomas Fischer */ class KBIBTEXIO_EXPORT EncoderXML : public Encoder { public: ~EncoderXML() override; QString decode(const QString &text) const override; QString encode(const QString &text, const TargetEncoding targetEncoding) const override; static const EncoderXML &instance(); protected: EncoderXML(); private: Q_DISABLE_COPY(EncoderXML) class EncoderXMLPrivate; EncoderXMLPrivate *const d; }; #endif // KBIBTEX_IO_ENCODERXML_H diff --git a/src/io/fileexporter.cpp b/src/io/fileexporter.cpp index d933854e..8a55d11b 100644 --- a/src/io/fileexporter.cpp +++ b/src/io/fileexporter.cpp @@ -1,64 +1,66 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 "fileexporter.h" #include #include +#include + FileExporter::FileExporter(QObject *parent) : QObject(parent) { /// nothing } FileExporter::~FileExporter() { /// nothing } QString FileExporter::toString(const QSharedPointer element, const File *bibtexfile, QStringList *errorLog) { QBuffer buffer; buffer.open(QBuffer::WriteOnly); if (save(&buffer, element, bibtexfile, errorLog)) { buffer.close(); if (buffer.open(QBuffer::ReadOnly)) { QTextStream ts(&buffer); ts.setCodec("UTF-8"); return ts.readAll(); } } return QString(); } QString FileExporter::toString(const File *bibtexfile, QStringList *errorLog) { QBuffer buffer; buffer.open(QBuffer::WriteOnly); if (save(&buffer, bibtexfile, errorLog)) { buffer.close(); if (buffer.open(QBuffer::ReadOnly)) { QTextStream ts(&buffer); ts.setCodec("utf-8"); return ts.readAll(); } } return QString(); } diff --git a/src/io/fileexporter.h b/src/io/fileexporter.h index 8c28b007..bebd2655 100644 --- a/src/io/fileexporter.h +++ b/src/io/fileexporter.h @@ -1,58 +1,58 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTER_H #define KBIBTEX_IO_FILEEXPORTER_H #include +#ifdef HAVE_KF5 #include "kbibtexio_export.h" - -#include "file.h" +#endif // HAVE_KF5 class QIODevice; class File; class Element; /** * @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporter : public QObject { Q_OBJECT public: explicit FileExporter(QObject *parent); ~FileExporter() override; QString toString(const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr); QString toString(const File *bibtexfile, QStringList *errorLog = nullptr); virtual bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) = 0; virtual bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) = 0; signals: void progress(int current, int total); public slots: virtual void cancel() { // nothing } }; #endif // KBIBTEX_IO_FILEEXPORTER_H diff --git a/src/io/fileexporterbibtex.cpp b/src/io/fileexporterbibtex.cpp index ecb181c7..f7a6e704 100644 --- a/src/io/fileexporterbibtex.cpp +++ b/src/io/fileexporterbibtex.cpp @@ -1,676 +1,676 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 "fileexporterbibtex.h" #include #include #include #include #include -#include "preferences.h" -#include "file.h" -#include "element.h" -#include "entry.h" -#include "macro.h" -#include "preamble.h" -#include "value.h" -#include "comment.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "encoderlatex.h" -#include "bibtexentries.h" -#include "bibtexfields.h" #include "textencoder.h" #include "logging_io.h" FileExporterBibTeX *FileExporterBibTeX::staticFileExporterBibTeX = nullptr; class FileExporterBibTeX::FileExporterBibTeXPrivate { private: FileExporterBibTeX *p; public: QChar stringOpenDelimiter; QChar stringCloseDelimiter; KBibTeX::Casing keywordCasing; Preferences::QuoteComment quoteComment; QString encoding, forcedEncoding; Qt::CheckState protectCasing; QString personNameFormatting; QString listSeparator; bool cancelFlag; QTextCodec *destinationCodec; FileExporterBibTeXPrivate(FileExporterBibTeX *parent) : p(parent), keywordCasing(KBibTeX::cLowerCase), quoteComment(Preferences::qcNone), protectCasing(Qt::PartiallyChecked), cancelFlag(false), destinationCodec(nullptr) { /// nothing } void loadState() { #ifdef HAVE_KF5 encoding = Preferences::instance().bibTeXEncoding(); QString stringDelimiter = Preferences::instance().bibTeXStringDelimiter(); if (stringDelimiter.length() != 2) stringDelimiter = Preferences::defaultBibTeXStringDelimiter; #else // HAVE_KF5 encoding = QStringLiteral("LaTeX"); const QString stringDelimiter = QStringLiteral("{}"); #endif // HAVE_KF5 stringOpenDelimiter = stringDelimiter[0]; stringCloseDelimiter = stringDelimiter[1]; #ifdef HAVE_KF5 keywordCasing = Preferences::instance().bibTeXKeywordCasing(); quoteComment = Preferences::instance().bibTeXQuoteComment(); protectCasing = Preferences::instance().bibTeXProtectCasing() ? Qt::Checked : Qt::Unchecked; listSeparator = Preferences::instance().bibTeXListSeparator(); #else // HAVE_KF5 keywordCasing = KBibTeX::cLowerCase; quoteComment = qcNone; protectCasing = Qt::PartiallyChecked; listSeparator = QStringLiteral("; "); #endif // HAVE_KF5 personNameFormatting = Preferences::instance().personNameFormat(); } void loadStateFromFile(const File *bibtexfile) { if (bibtexfile == nullptr) return; if (bibtexfile->hasProperty(File::Encoding)) encoding = bibtexfile->property(File::Encoding).toString(); if (!forcedEncoding.isEmpty()) encoding = forcedEncoding; applyEncoding(encoding); if (bibtexfile->hasProperty(File::StringDelimiter)) { QString stringDelimiter = bibtexfile->property(File::StringDelimiter).toString(); if (stringDelimiter.length() != 2) stringDelimiter = Preferences::defaultBibTeXStringDelimiter; stringOpenDelimiter = stringDelimiter[0]; stringCloseDelimiter = stringDelimiter[1]; } if (bibtexfile->hasProperty(File::QuoteComment)) quoteComment = static_cast(bibtexfile->property(File::QuoteComment).toInt()); if (bibtexfile->hasProperty(File::KeywordCasing)) keywordCasing = static_cast(bibtexfile->property(File::KeywordCasing).toInt()); if (bibtexfile->hasProperty(File::ProtectCasing)) protectCasing = static_cast(bibtexfile->property(File::ProtectCasing).toInt()); if (bibtexfile->hasProperty(File::NameFormatting)) { /// if the user set "use global default", this property is an empty string /// in this case, keep default value const QString buffer = bibtexfile->property(File::NameFormatting).toString(); personNameFormatting = buffer.isEmpty() ? personNameFormatting : buffer; } if (bibtexfile->hasProperty(File::ListSeparator)) listSeparator = bibtexfile->property(File::ListSeparator).toString(); } bool writeEntry(QIODevice *iodevice, const Entry &entry) { /// write start of a entry (entry type and id) in plain ASCII iodevice->putChar('@'); iodevice->write(BibTeXEntries::instance().format(entry.type(), keywordCasing).toLatin1().data()); iodevice->putChar('{'); iodevice->write(Encoder::instance().convertToPlainAscii(entry.id()).toLatin1()); for (Entry::ConstIterator it = entry.constBegin(); it != entry.constEnd(); ++it) { const QString key = it.key(); Value value = it.value(); if (value.isEmpty()) continue; ///< ignore empty key-value pairs QString text = p->internalValueToBibTeX(value, key, leUTF8); if (text.isEmpty()) { /// ignore empty key-value pairs qCWarning(LOG_KBIBTEX_IO) << "Value for field " << key << " is empty" << endl; continue; } // FIXME hack! const QSharedPointer first = *value.constBegin(); if (PlainText::isPlainText(*first) && (key == Entry::ftTitle || key == Entry::ftBookTitle || key == Entry::ftSeries)) { if (protectCasing == Qt::Checked) addProtectiveCasing(text); else if (protectCasing == Qt::Unchecked) removeProtectiveCasing(text); } iodevice->putChar(','); iodevice->putChar('\n'); iodevice->putChar('\t'); iodevice->write(Encoder::instance().convertToPlainAscii(BibTeXFields::instance().format(key, keywordCasing)).toLatin1()); iodevice->putChar(' '); iodevice->putChar('='); iodevice->putChar(' '); iodevice->write(TextEncoder::encode(text, destinationCodec)); } iodevice->putChar('\n'); iodevice->putChar('}'); iodevice->putChar('\n'); iodevice->putChar('\n'); return true; } bool writeMacro(QIODevice *iodevice, const Macro ¯o) { QString text = p->internalValueToBibTeX(macro.value(), QString(), leUTF8); if (protectCasing == Qt::Checked) addProtectiveCasing(text); else if (protectCasing == Qt::Unchecked) removeProtectiveCasing(text); iodevice->putChar('@'); iodevice->write(BibTeXEntries::instance().format(QStringLiteral("String"), keywordCasing).toLatin1().data()); iodevice->putChar('{'); iodevice->write(TextEncoder::encode(macro.key(), destinationCodec)); iodevice->putChar(' '); iodevice->putChar('='); iodevice->putChar(' '); iodevice->write(TextEncoder::encode(text, destinationCodec)); iodevice->putChar('}'); iodevice->putChar('\n'); iodevice->putChar('\n'); return true; } bool writeComment(QIODevice *iodevice, const Comment &comment) { QString text = comment.text() ; if (comment.useCommand() || quoteComment == Preferences::qcCommand) { iodevice->putChar('@'); iodevice->write(BibTeXEntries::instance().format(QStringLiteral("Comment"), keywordCasing).toLatin1().data()); iodevice->putChar('{'); iodevice->write(TextEncoder::encode(text, destinationCodec)); iodevice->putChar('}'); iodevice->putChar('\n'); iodevice->putChar('\n'); } else if (quoteComment == Preferences::qcPercentSign) { QStringList commentLines = text.split('\n', QString::SkipEmptyParts); for (QStringList::Iterator it = commentLines.begin(); it != commentLines.end(); ++it) { const QByteArray line = TextEncoder::encode(*it, destinationCodec); if (line.length() == 0 || line[0] != QLatin1Char('%')) { /// Guarantee that every line starts with /// a percent sign iodevice->putChar('%'); } iodevice->write(line); iodevice->putChar('\n'); } iodevice->putChar('\n'); } else { iodevice->write(TextEncoder::encode(text, destinationCodec)); iodevice->putChar('\n'); iodevice->putChar('\n'); } return true; } bool writePreamble(QIODevice *iodevice, const Preamble &preamble) { iodevice->putChar('@'); iodevice->write(BibTeXEntries::instance().format(QStringLiteral("Preamble"), keywordCasing).toLatin1().data()); iodevice->putChar('{'); /// Remember: strings from preamble do not get encoded, /// may contain raw LaTeX commands and code iodevice->write(TextEncoder::encode(p->internalValueToBibTeX(preamble.value(), QString(), leRaw), destinationCodec)); iodevice->putChar('}'); iodevice->putChar('\n'); iodevice->putChar('\n'); return true; } QString addProtectiveCasing(QString &text) { /// Check if either /// - text is too short (less than two characters) or /// - text neither starts/stops with double quotation marks /// nor starts with { and stops with } if (text.length() < 2 || ((text[0] != QLatin1Char('"') || text[text.length() - 1] != QLatin1Char('"')) && (text[0] != QLatin1Char('{') || text[text.length() - 1] != QLatin1Char('}')))) { /// Nothing to protect, as this is no text string return text; } bool addBrackets = true; if (text[1] == QLatin1Char('{') && text[text.length() - 2] == QLatin1Char('}')) { /// If the given text looks like this: {{...}} or "{...}" /// still check that it is not like this: {{..}..{..}} addBrackets = false; for (int i = text.length() - 2, count = 0; !addBrackets && i > 1; --i) { if (text[i] == QLatin1Char('{')) ++count; else if (text[i] == QLatin1Char('}')) --count; if (count == 0) addBrackets = true; } } if (addBrackets) text.insert(1, QStringLiteral("{")).insert(text.length() - 1, QStringLiteral("}")); return text; } QString removeProtectiveCasing(QString &text) { /// Check if either /// - text is too short (less than two characters) or /// - text neither starts/stops with double quotation marks /// nor starts with { and stops with } if (text.length() < 2 || ((text[0] != QLatin1Char('"') || text[text.length() - 1] != QLatin1Char('"')) && (text[0] != QLatin1Char('{') || text[text.length() - 1] != QLatin1Char('}')))) { /// Nothing to protect, as this is no text string return text; } if (text[1] != QLatin1Char('{') || text[text.length() - 2] != QLatin1Char('}')) /// Nothing to remove return text; /// If the given text looks like this: {{...}} or "{...}" /// still check that it is not like this: {{..}..{..}} bool removeBrackets = true; for (int i = text.length() - 2, count = 0; removeBrackets && i > 1; --i) { if (text[i] == QLatin1Char('{')) ++count; else if (text[i] == QLatin1Char('}')) --count; if (count == 0) removeBrackets = false; } if (removeBrackets) text.remove(text.length() - 2, 1).remove(1, 1); return text; } QString &protectQuotationMarks(QString &text) { int p = -1; while ((p = text.indexOf(QLatin1Char('"'), p + 1)) > 0) if (p == 0 || text[p - 1] != QLatin1Char('\\')) { text.insert(p + 1, QStringLiteral("}")).insert(p, QStringLiteral("{")); ++p; } return text; } void applyEncoding(QString &encoding) { encoding = encoding.isEmpty() ? QStringLiteral("latex") : encoding.toLower(); destinationCodec = QTextCodec::codecForName(encoding == QStringLiteral("latex") ? "us-ascii" : encoding.toLatin1()); } bool requiresPersonQuoting(const QString &text, bool isLastName) { if (isLastName && !text.contains(QChar(' '))) /** Last name contains NO spaces, no quoting necessary */ return false; else if (!isLastName && !text.contains(QStringLiteral(" and "))) /** First name contains no " and " no quoting necessary */ return false; else if (isLastName && !text.isEmpty() && text[0].isLower()) /** Last name starts with lower-case character (von, van, de, ...) */ // FIXME does not work yet return false; else if (text[0] != '{' || text[text.length() - 1] != '}') /** as either last name contains spaces or first name contains " and " and there is no protective quoting yet, there must be a protective quoting added */ return true; int bracketCounter = 0; for (int i = text.length() - 1; i >= 0; --i) { if (text[i] == '{') ++bracketCounter; else if (text[i] == '}') --bracketCounter; if (bracketCounter == 0 && i > 0) return true; } return false; } }; FileExporterBibTeX::FileExporterBibTeX(QObject *parent) : FileExporter(parent), d(new FileExporterBibTeXPrivate(this)) { /// nothing } FileExporterBibTeX::~FileExporterBibTeX() { delete d; } void FileExporterBibTeX::setEncoding(const QString &encoding) { d->forcedEncoding = encoding; } bool FileExporterBibTeX::save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog) { Q_UNUSED(errorLog) if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = true; const int totalElements = bibtexfile->count(); int currentPos = 0; d->loadState(); d->loadStateFromFile(bibtexfile); QBuffer outputBuffer; outputBuffer.open(QBuffer::WriteOnly); /// Memorize which entries are used in a crossref field QHash crossRefMap; for (File::ConstIterator it = bibtexfile->constBegin(); it != bibtexfile->constEnd() && result && !d->cancelFlag; ++it) { QSharedPointer entry = (*it).dynamicCast(); if (!entry.isNull()) { const QString crossRef = PlainTextValue::text(entry->value(Entry::ftCrossRef)); if (!crossRef.isEmpty()) { QStringList crossRefList = crossRefMap.value(crossRef, QStringList()); crossRefList.append(entry->id()); crossRefMap.insert(crossRef, crossRefList); } } } bool allPreamblesAndMacrosProcessed = false; QSet processedEntryIds; for (File::ConstIterator it = bibtexfile->constBegin(); it != bibtexfile->constEnd() && result && !d->cancelFlag; ++it) { QSharedPointer element = (*it); QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) { processedEntryIds.insert(entry->id()); /// Postpone entries that are crossref'ed QStringList crossRefList = crossRefMap.value(entry->id(), QStringList()); if (!crossRefList.isEmpty()) { bool allProcessed = true; for (const QString &origin : crossRefList) allProcessed &= processedEntryIds.contains(origin); if (allProcessed) crossRefMap.remove(entry->id()); else continue; } if (!allPreamblesAndMacrosProcessed) { /// Guarantee that all macros and the preamble are written /// before the first entry (@article, ...) is written for (File::ConstIterator msit = it + 1; msit != bibtexfile->constEnd() && result && !d->cancelFlag; ++msit) { QSharedPointer preamble = (*msit).dynamicCast(); if (!preamble.isNull()) { result &= d->writePreamble(&outputBuffer, *preamble); emit progress(++currentPos, totalElements); } else { QSharedPointer macro = (*msit).dynamicCast(); if (!macro.isNull()) { result &= d->writeMacro(&outputBuffer, *macro); emit progress(++currentPos, totalElements); } } } allPreamblesAndMacrosProcessed = true; } result &= d->writeEntry(&outputBuffer, *entry); emit progress(++currentPos, totalElements); } else { QSharedPointer comment = element.dynamicCast(); if (!comment.isNull() && !comment->text().startsWith(QStringLiteral("x-kbibtex-"))) { result &= d->writeComment(&outputBuffer, *comment); emit progress(++currentPos, totalElements); } else if (!allPreamblesAndMacrosProcessed) { QSharedPointer preamble = element.dynamicCast(); if (!preamble.isNull()) { result &= d->writePreamble(&outputBuffer, *preamble); emit progress(++currentPos, totalElements); } else { QSharedPointer macro = element.dynamicCast(); if (!macro.isNull()) { result &= d->writeMacro(&outputBuffer, *macro); emit progress(++currentPos, totalElements); } } } } } /// Crossref'ed entries are written last if (!crossRefMap.isEmpty()) for (File::ConstIterator it = bibtexfile->constBegin(); it != bibtexfile->constEnd() && result && !d->cancelFlag; ++it) { QSharedPointer entry = (*it).dynamicCast(); if (entry.isNull()) continue; if (!crossRefMap.contains(entry->id())) continue; result &= d->writeEntry(&outputBuffer, *entry); emit progress(++currentPos, totalElements); } outputBuffer.close(); ///< close writing operation outputBuffer.open(QBuffer::ReadOnly); const QByteArray outputData = outputBuffer.readAll(); outputBuffer.close(); bool hasNonAsciiCharacters = false; for (const unsigned char c : outputData) if ((c & 128) > 0) { hasNonAsciiCharacters = true; break; } if (hasNonAsciiCharacters) { const QString encoding = d->encoding.toLower() == QStringLiteral("latex") ? QStringLiteral("utf-8") : d->encoding; Comment encodingComment(QStringLiteral("x-kbibtex-encoding=") + encoding, true); result &= d->writeComment(iodevice, encodingComment); } iodevice->write(outputData); iodevice->close(); return result && !d->cancelFlag; } bool FileExporterBibTeX::save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog) { Q_UNUSED(errorLog) if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = false; d->loadState(); d->loadStateFromFile(bibtexfile); if (!d->forcedEncoding.isEmpty()) d->encoding = d->forcedEncoding; d->applyEncoding(d->encoding); const QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) result |= d->writeEntry(iodevice, *entry); else { const QSharedPointer macro = element.dynamicCast(); if (!macro.isNull()) result |= d->writeMacro(iodevice, *macro); else { const QSharedPointer comment = element.dynamicCast(); if (!comment.isNull()) result |= d->writeComment(iodevice, *comment); else { const QSharedPointer preamble = element.dynamicCast(); if (!preamble.isNull()) result |= d->writePreamble(iodevice, *preamble); } } } iodevice->close(); return result && !d->cancelFlag; } void FileExporterBibTeX::cancel() { d->cancelFlag = true; } QString FileExporterBibTeX::valueToBibTeX(const Value &value, const QString &key, UseLaTeXEncoding useLaTeXEncoding) { if (staticFileExporterBibTeX == nullptr) { staticFileExporterBibTeX = new FileExporterBibTeX(nullptr); staticFileExporterBibTeX->d->loadState(); } return staticFileExporterBibTeX->internalValueToBibTeX(value, key, useLaTeXEncoding); } QString FileExporterBibTeX::applyEncoder(const QString &input, UseLaTeXEncoding useLaTeXEncoding) const { switch (useLaTeXEncoding) { case leLaTeX: return EncoderLaTeX::instance().encode(input, Encoder::TargetEncodingASCII); case leUTF8: return EncoderLaTeX::instance().encode(input, Encoder::TargetEncodingUTF8); default: return input; } } QString FileExporterBibTeX::internalValueToBibTeX(const Value &value, const QString &key, UseLaTeXEncoding useLaTeXEncoding) { if (value.isEmpty()) return QString(); QString result; bool isOpen = false; QSharedPointer prev; for (const auto &valueItem : value) { QSharedPointer macroKey = valueItem.dynamicCast(); if (!macroKey.isNull()) { if (isOpen) result.append(d->stringCloseDelimiter); isOpen = false; if (!result.isEmpty()) result.append(" # "); result.append(macroKey->text()); prev = macroKey; } else { QSharedPointer plainText = valueItem.dynamicCast(); if (!plainText.isNull()) { QString textBody = applyEncoder(plainText->text(), useLaTeXEncoding); if (!isOpen) { if (!result.isEmpty()) result.append(" # "); result.append(d->stringOpenDelimiter); } else if (!prev.dynamicCast().isNull()) result.append(' '); else if (!prev.dynamicCast().isNull()) { /// handle "et al." i.e. "and others" result.append(" and "); } else { result.append(d->stringCloseDelimiter).append(" # ").append(d->stringOpenDelimiter); } isOpen = true; if (d->stringOpenDelimiter == QLatin1Char('"')) d->protectQuotationMarks(textBody); result.append(textBody); prev = plainText; } else { QSharedPointer verbatimText = valueItem.dynamicCast(); if (!verbatimText.isNull()) { QString textBody = verbatimText->text(); if (!isOpen) { if (!result.isEmpty()) result.append(" # "); result.append(d->stringOpenDelimiter); } else if (!prev.dynamicCast().isNull()) { const QString keyToLower(key.toLower()); if (keyToLower.startsWith(Entry::ftUrl) || keyToLower.startsWith(Entry::ftLocalFile) || keyToLower.startsWith(Entry::ftFile) || keyToLower.startsWith(Entry::ftDOI)) /// Filenames and alike have be separated by a semicolon, /// as a plain comma may be part of the filename or URL result.append(QStringLiteral("; ")); else result.append(' '); } else { result.append(d->stringCloseDelimiter).append(" # ").append(d->stringOpenDelimiter); } isOpen = true; if (d->stringOpenDelimiter == QLatin1Char('"')) d->protectQuotationMarks(textBody); result.append(textBody); prev = verbatimText; } else { QSharedPointer person = valueItem.dynamicCast(); if (!person.isNull()) { QString firstName = person->firstName(); if (!firstName.isEmpty() && d->requiresPersonQuoting(firstName, false)) firstName = firstName.prepend("{").append("}"); QString lastName = person->lastName(); if (!lastName.isEmpty() && d->requiresPersonQuoting(lastName, true)) lastName = lastName.prepend("{").append("}"); QString suffix = person->suffix(); /// Fall back and enforce comma-based name formatting /// if name contains a suffix like "Jr." /// Otherwise name could not be parsed again reliable const QString pnf = suffix.isEmpty() ? d->personNameFormatting : Preferences::personNameFormatLastFirst; QString thisName = applyEncoder(Person::transcribePersonName(pnf, firstName, lastName, suffix), useLaTeXEncoding); if (!isOpen) { if (!result.isEmpty()) result.append(" # "); result.append(d->stringOpenDelimiter); } else if (!prev.dynamicCast().isNull()) result.append(" and "); else { result.append(d->stringCloseDelimiter).append(" # ").append(d->stringOpenDelimiter); } isOpen = true; if (d->stringOpenDelimiter == QLatin1Char('"')) d->protectQuotationMarks(thisName); result.append(thisName); prev = person; } else { QSharedPointer keyword = valueItem.dynamicCast(); if (!keyword.isNull()) { QString textBody = applyEncoder(keyword->text(), useLaTeXEncoding); if (!isOpen) { if (!result.isEmpty()) result.append(" # "); result.append(d->stringOpenDelimiter); } else if (!prev.dynamicCast().isNull()) result.append(d->listSeparator); else { result.append(d->stringCloseDelimiter).append(" # ").append(d->stringOpenDelimiter); } isOpen = true; if (d->stringOpenDelimiter == QLatin1Char('"')) d->protectQuotationMarks(textBody); result.append(textBody); prev = keyword; } } } } } prev = valueItem; } if (isOpen) result.append(d->stringCloseDelimiter); return result; } bool FileExporterBibTeX::isFileExporterBibTeX(const FileExporter &other) { return typeid(other) == typeid(FileExporterBibTeX); } diff --git a/src/io/fileexporterbibtex.h b/src/io/fileexporterbibtex.h index c53c0154..9f37dda2 100644 --- a/src/io/fileexporterbibtex.h +++ b/src/io/fileexporterbibtex.h @@ -1,75 +1,78 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERBIBTEX_H #define KBIBTEX_IO_FILEEXPORTERBIBTEX_H #include -#include "kbibtex.h" -#include "element.h" -#include "value.h" -#include "fileexporter.h" +#include +#include +#include + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 class QChar; class Comment; class Preamble; class Macro; class Entry; /** * @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporterBibTeX : public FileExporter { Q_OBJECT public: enum UseLaTeXEncoding {leUTF8, leLaTeX, leRaw}; explicit FileExporterBibTeX(QObject *parent); ~FileExporterBibTeX() override; void setEncoding(const QString &encoding); bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) override; bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) override; static QString valueToBibTeX(const Value &value, const QString &fieldType = QString(), UseLaTeXEncoding useLaTeXEncoding = leLaTeX); /** * Cheap and fast test if another FileExporter is a FileExporterBibTeX object. * @param other another FileExporter object to test * @return true if FileExporter is actually a FileExporterBibTeX */ static bool isFileExporterBibTeX(const FileExporter &other); public slots: void cancel() override; private: class FileExporterBibTeXPrivate; FileExporterBibTeXPrivate *d; inline QString applyEncoder(const QString &input, UseLaTeXEncoding useLaTeXEncoding) const; QString internalValueToBibTeX(const Value &value, const QString &fieldType = QString(), UseLaTeXEncoding useLaTeXEncoding = leLaTeX); static FileExporterBibTeX *staticFileExporterBibTeX; }; #endif // KBIBTEX_IO_FILEEXPORTERBIBTEX_H diff --git a/src/io/fileexporterbibtex2html.h b/src/io/fileexporterbibtex2html.h index 78975633..2c96d1ad 100644 --- a/src/io/fileexporterbibtex2html.h +++ b/src/io/fileexporterbibtex2html.h @@ -1,44 +1,48 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERBIBTEX2HTML_H #define KBIBTEX_IO_FILEEXPORTERBIBTEX2HTML_H -#include "fileexportertoolchain.h" +#include + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 /** @author Thomas Fischer */ -class KBIBTEXIO_EXPORT FileExporterBibTeX2HTML: public FileExporterToolchain +class KBIBTEXIO_EXPORT FileExporterBibTeX2HTML : public FileExporterToolchain { Q_OBJECT public: explicit FileExporterBibTeX2HTML(QObject *parent); ~FileExporterBibTeX2HTML() override; bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) override; bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) override; void setLaTeXBibliographyStyle(const QString &bibStyle); private: class FileExporterBibTeX2HTMLPrivate; FileExporterBibTeX2HTMLPrivate *d; }; #endif // KBIBTEX_IO_FILEEXPORTERBIBTEX2HTML_H diff --git a/src/io/fileexporterbibtexoutput.cpp b/src/io/fileexporterbibtexoutput.cpp index 9cecb7b6..e822e85f 100644 --- a/src/io/fileexporterbibtexoutput.cpp +++ b/src/io/fileexporterbibtexoutput.cpp @@ -1,139 +1,139 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 "fileexporterbibtexoutput.h" #include #include #include #include #include #include -#include "element.h" -#include "entry.h" +#include +#include +#include +#include #include "fileexporterbibtex.h" -#include "kbibtex.h" -#include "preferences.h" #include "logging_io.h" FileExporterBibTeXOutput::FileExporterBibTeXOutput(OutputType outputType, QObject *parent) : FileExporterToolchain(parent), m_outputType(outputType) { m_fileBasename = QStringLiteral("bibtex-to-output"); m_fileStem = tempDir.path() + QDir::separator() + m_fileBasename; } FileExporterBibTeXOutput::~FileExporterBibTeXOutput() { /// nothing } bool FileExporterBibTeXOutput::save(QIODevice *ioDevice, const File *bibtexfile, QStringList *errorLog) { if (!ioDevice->isWritable() && !ioDevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = false; QFile bibTeXFile(m_fileStem + KBibTeX::extensionBibTeX); if (bibTeXFile.open(QIODevice::WriteOnly)) { FileExporterBibTeX bibtexExporter(this); bibtexExporter.setEncoding(QStringLiteral("utf-8")); result = bibtexExporter.save(&bibTeXFile, bibtexfile, errorLog); bibTeXFile.close(); } if (result) result = generateOutput(errorLog); if (result) result = writeFileToIODevice(m_fileStem + (m_outputType == BibTeXLogFile ? KBibTeX::extensionBLG : KBibTeX::extensionBBL), ioDevice, errorLog); ioDevice->close(); return result; } bool FileExporterBibTeXOutput::save(QIODevice *ioDevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog) { if (!ioDevice->isWritable() && !ioDevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = false; QFile bibTeXFile(m_fileStem + KBibTeX::extensionBibTeX); if (bibTeXFile.open(QIODevice::WriteOnly)) { FileExporterBibTeX bibtexExporter(this); bibtexExporter.setEncoding(QStringLiteral("utf-8")); result = bibtexExporter.save(&bibTeXFile, element, bibtexfile, errorLog); bibTeXFile.close(); } if (result) result = generateOutput(errorLog); if (result) result = writeFileToIODevice(m_fileStem + (m_outputType == BibTeXLogFile ? KBibTeX::extensionBLG : KBibTeX::extensionBBL), ioDevice, errorLog); ioDevice->close(); return result; } bool FileExporterBibTeXOutput::generateOutput(QStringList *errorLog) { QStringList cmdLines {QStringLiteral("pdflatex -halt-on-error ") + m_fileBasename + KBibTeX::extensionTeX, QStringLiteral("bibtex ") + m_fileBasename + KBibTeX::extensionAux}; if (writeLatexFile(m_fileStem + KBibTeX::extensionTeX) && runProcesses(cmdLines, errorLog)) return true; else { qCWarning(LOG_KBIBTEX_IO) << "Generating BibTeX output failed"; return false; } } bool FileExporterBibTeXOutput::writeLatexFile(const QString &filename) { QFile latexFile(filename); if (latexFile.open(QIODevice::WriteOnly)) { QTextStream ts(&latexFile); ts.setCodec("UTF-8"); ts << "\\documentclass{article}\n"; ts << "\\usepackage[T1]{fontenc}\n"; ts << "\\usepackage[utf8]{inputenc}\n"; if (kpsewhich(QStringLiteral("babel.sty"))) ts << "\\usepackage[" << Preferences::instance().laTeXBabelLanguage() << "]{babel}\n"; if (kpsewhich(QStringLiteral("hyperref.sty"))) ts << "\\usepackage[pdfproducer={KBibTeX: https://userbase.kde.org/KBibTeX},pdftex]{hyperref}\n"; else if (kpsewhich(QStringLiteral("url.sty"))) ts << "\\usepackage{url}\n"; const QString latexBibStyle = Preferences::instance().bibTeXBibliographyStyle(); if (latexBibStyle.startsWith(QStringLiteral("apacite")) && kpsewhich(QStringLiteral("apacite.sty"))) ts << "\\usepackage[bibnewpage]{apacite}\n"; ts << "\\bibliographystyle{" << latexBibStyle << "}\n"; ts << "\\begin{document}\n"; ts << "\\nocite{*}\n"; ts << QStringLiteral("\\bibliography{") + m_fileBasename + QStringLiteral("}\n"); ts << "\\end{document}\n"; latexFile.close(); return true; } else return false; } diff --git a/src/io/fileexporterbibtexoutput.h b/src/io/fileexporterbibtexoutput.h index a5b86f09..0ad873de 100644 --- a/src/io/fileexporterbibtexoutput.h +++ b/src/io/fileexporterbibtexoutput.h @@ -1,49 +1,53 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERBIBTEXOUTPUT_H #define KBIBTEX_IO_FILEEXPORTERBIBTEXOUTPUT_H #include -#include "fileexportertoolchain.h" +#include + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 /** @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporterBibTeXOutput : public FileExporterToolchain { Q_OBJECT public: enum OutputType {BibTeXLogFile, BibTeXBlockList}; explicit FileExporterBibTeXOutput(OutputType outputType, QObject *parent); ~FileExporterBibTeXOutput() override; bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) override; bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) override; private: OutputType m_outputType; QString m_fileBasename; QString m_fileStem; bool generateOutput(QStringList *errorLog); bool writeLatexFile(const QString &filename); }; #endif // KBIBTEX_IO_FILEEXPORTERBIBTEXOUTPUT_H diff --git a/src/io/fileexporterbibutils.h b/src/io/fileexporterbibutils.h index 77e54b5e..66abfcaf 100644 --- a/src/io/fileexporterbibutils.h +++ b/src/io/fileexporterbibutils.h @@ -1,43 +1,47 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERBIBUTILS_H #define KBIBTEX_IO_FILEEXPORTERBIBUTILS_H -#include "fileexporter.h" -#include "bibutils.h" +#include +#include + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 /** * @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporterBibUtils : public FileExporter, public BibUtils { Q_OBJECT public: explicit FileExporterBibUtils(QObject *parent); ~FileExporterBibUtils() override; bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) override; bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) override; private: class Private; Private *const d; }; #endif // KBIBTEX_IO_FILEEXPORTERBIBUTILS_H diff --git a/src/io/fileexporterpdf.cpp b/src/io/fileexporterpdf.cpp index f916bff7..8376c7f0 100644 --- a/src/io/fileexporterpdf.cpp +++ b/src/io/fileexporterpdf.cpp @@ -1,202 +1,202 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 "fileexporterpdf.h" #include #include #include #include #include +#include +#include +#include +#include #include "fileinfo.h" -#include "element.h" -#include "entry.h" #include "fileexporterbibtex.h" -#include "kbibtex.h" -#include "preferences.h" #include "logging_io.h" FileExporterPDF::FileExporterPDF(QObject *parent) : FileExporterToolchain(parent) { m_fileBasename = QStringLiteral("bibtex-to-pdf"); m_fileStem = tempDir.path() + QDir::separator() + m_fileBasename; setFileEmbedding(FileExporterPDF::EmbedBibTeXFileAndReferences); } FileExporterPDF::~FileExporterPDF() { /// nothing } bool FileExporterPDF::save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog) { if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = false; m_embeddedFileList.clear(); if (m_fileEmbedding & EmbedBibTeXFile) m_embeddedFileList.append(QString(QStringLiteral("%1|%2|%3")).arg(QStringLiteral("BibTeX source"), m_fileStem + KBibTeX::extensionBibTeX, m_fileBasename + KBibTeX::extensionBibTeX)); if (m_fileEmbedding & EmbedReferences) fillEmbeddedFileList(bibtexfile); QFile output(m_fileStem + KBibTeX::extensionBibTeX); if (output.open(QIODevice::WriteOnly)) { FileExporterBibTeX bibtexExporter(this); bibtexExporter.setEncoding(QStringLiteral("latex")); result = bibtexExporter.save(&output, bibtexfile, errorLog); output.close(); } if (result) result = generatePDF(iodevice, errorLog); if (errorLog != nullptr) qCDebug(LOG_KBIBTEX_IO) << "errorLog" << errorLog->join(QStringLiteral(";")); iodevice->close(); return result; } bool FileExporterPDF::save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog) { if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = false; m_embeddedFileList.clear(); //if (m_fileEmbedding & EmbedReferences) // FIXME need File object fillEmbeddedFileList(element); QFile output(m_fileStem + KBibTeX::extensionBibTeX); if (output.open(QIODevice::WriteOnly)) { FileExporterBibTeX bibtexExporter(this); bibtexExporter.setEncoding(QStringLiteral("latex")); result = bibtexExporter.save(&output, element, bibtexfile, errorLog); output.close(); } if (result) result = generatePDF(iodevice, errorLog); iodevice->close(); return result; } void FileExporterPDF::setDocumentSearchPaths(const QStringList &searchPaths) { m_searchPaths = searchPaths; } void FileExporterPDF::setFileEmbedding(FileEmbedding fileEmbedding) { /// If there is not embedfile.sty file, disable embedding /// irrespective of user's wishes if (!kpsewhich(QStringLiteral("embedfile.sty"))) m_fileEmbedding = NoFileEmbedding; else m_fileEmbedding = fileEmbedding; } bool FileExporterPDF::generatePDF(QIODevice *iodevice, QStringList *errorLog) { QStringList cmdLines {QStringLiteral("pdflatex -halt-on-error ") + m_fileStem + KBibTeX::extensionTeX, QStringLiteral("bibtex ") + m_fileStem + KBibTeX::extensionAux, QStringLiteral("pdflatex -halt-on-error ") + m_fileStem + KBibTeX::extensionTeX, QStringLiteral("pdflatex -halt-on-error ") + m_fileStem + KBibTeX::extensionTeX}; return writeLatexFile(m_fileStem + KBibTeX::extensionTeX) && runProcesses(cmdLines, errorLog) && writeFileToIODevice(m_fileStem + KBibTeX::extensionPDF, iodevice, errorLog); } bool FileExporterPDF::writeLatexFile(const QString &filename) { QFile latexFile(filename); if (latexFile.open(QIODevice::WriteOnly)) { QTextStream ts(&latexFile); ts.setCodec("UTF-8"); ts << "\\documentclass{article}" << endl; ts << "\\usepackage[T1]{fontenc}" << endl; ts << "\\usepackage[utf8]{inputenc}" << endl; if (kpsewhich(QStringLiteral("babel.sty"))) ts << "\\usepackage[" << Preferences::instance().laTeXBabelLanguage() << "]{babel}" << endl; if (kpsewhich(QStringLiteral("hyperref.sty"))) ts << "\\usepackage[pdfborder={0 0 0},pdfproducer={KBibTeX: https://userbase.kde.org/KBibTeX},pdftex]{hyperref}" << endl; else if (kpsewhich(QStringLiteral("url.sty"))) ts << "\\usepackage{url}" << endl; const QString bibliographyStyle = Preferences::instance().bibTeXBibliographyStyle(); if (bibliographyStyle.startsWith(QStringLiteral("apacite")) && kpsewhich(QStringLiteral("apacite.sty"))) ts << "\\usepackage[bibnewpage]{apacite}" << endl; if ((bibliographyStyle == QStringLiteral("agsm") || bibliographyStyle == QStringLiteral("dcu") || bibliographyStyle == QStringLiteral("jmr") || bibliographyStyle == QStringLiteral("jphysicsB") || bibliographyStyle == QStringLiteral("kluwer") || bibliographyStyle == QStringLiteral("nederlands") || bibliographyStyle == QStringLiteral("dcu") || bibliographyStyle == QStringLiteral("dcu")) && kpsewhich(QStringLiteral("harvard.sty")) && kpsewhich(QStringLiteral("html.sty"))) ts << "\\usepackage{html}" << endl << "\\usepackage[dcucite]{harvard}" << endl << "\\renewcommand{\\harvardurl}{URL: \\url}" << endl; if (kpsewhich(QStringLiteral("embedfile.sty"))) ts << "\\usepackage{embedfile}" << endl; if (kpsewhich(QStringLiteral("geometry.sty"))) ts << "\\usepackage[paper=" << pageSizeToLaTeXName(Preferences::instance().pageSize()) << "]{geometry}" << endl; ts << "\\bibliographystyle{" << bibliographyStyle << "}" << endl; ts << "\\begin{document}" << endl; if (!m_embeddedFileList.isEmpty()) for (const QString &embeddedFile : const_cast(m_embeddedFileList)) { const QStringList param = embeddedFile.split(QStringLiteral("|")); QFile file(param[1]); if (file.exists()) ts << "\\embedfile[desc={" << param[0] << "}"; ts << ",filespec={" << param[2] << "}"; if (param[2].endsWith(KBibTeX::extensionBibTeX)) ts << ",mimetype={text/x-bibtex}"; else if (param[2].endsWith(KBibTeX::extensionPDF)) ts << ",mimetype={application/pdf}"; ts << "]{" << param[1] << "}" << endl; } ts << "\\nocite{*}" << endl; ts << QStringLiteral("\\bibliography{") << m_fileBasename << QStringLiteral("}") << endl; ts << "\\end{document}" << endl; latexFile.close(); return true; } else return false; } void FileExporterPDF::fillEmbeddedFileList(const File *bibtexfile) { for (const auto &element : const_cast(*bibtexfile)) fillEmbeddedFileList(element, bibtexfile); } void FileExporterPDF::fillEmbeddedFileList(const QSharedPointer element, const File *bibtexfile) { if (bibtexfile == nullptr || !bibtexfile->hasProperty(File::Url)) { /// If no valid File was provided or File is not saved, do not append files return; } const QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) { const QString title = PlainTextValue::text(entry->value(Entry::ftTitle)); const auto urlList = FileInfo::entryUrls(entry, bibtexfile->property(File::Url).toUrl(), FileInfo::TestExistenceYes); for (const QUrl &url : urlList) { if (!url.isLocalFile()) continue; const QString filename = url.toLocalFile(); const QString basename = QFileInfo(filename).fileName(); m_embeddedFileList.append(QString(QStringLiteral("%1|%2|%3")).arg(title, filename, basename)); } } } diff --git a/src/io/fileexporterpdf.h b/src/io/fileexporterpdf.h index 963450c1..ca70d655 100644 --- a/src/io/fileexporterpdf.h +++ b/src/io/fileexporterpdf.h @@ -1,56 +1,60 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERPDF_H #define KBIBTEX_IO_FILEEXPORTERPDF_H #include -#include "fileexportertoolchain.h" +#include + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 /** @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporterPDF : public FileExporterToolchain { Q_OBJECT public: enum FileEmbedding { NoFileEmbedding = 0, EmbedBibTeXFile = 1, EmbedReferences = 2, EmbedBibTeXFileAndReferences = EmbedBibTeXFile | EmbedReferences}; explicit FileExporterPDF(QObject *parent); ~FileExporterPDF() override; bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) override; bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) override; void setDocumentSearchPaths(const QStringList &searchPaths); void setFileEmbedding(FileEmbedding fileEmbedding); private: QString m_fileBasename; QString m_fileStem; FileEmbedding m_fileEmbedding; QStringList m_embeddedFileList; QStringList m_searchPaths; bool generatePDF(QIODevice *iodevice, QStringList *errorLog); bool writeLatexFile(const QString &filename); void fillEmbeddedFileList(const File *bibtexfile); void fillEmbeddedFileList(const QSharedPointer element, const File *bibtexfile); }; #endif // KBIBTEX_IO_FILEEXPORTERPDF_H diff --git a/src/io/fileexporterps.cpp b/src/io/fileexporterps.cpp index 587287dd..57cb05d7 100644 --- a/src/io/fileexporterps.cpp +++ b/src/io/fileexporterps.cpp @@ -1,158 +1,158 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 "fileexporterps.h" #include #include #include #include -#include "element.h" +#include +#include +#include #include "fileexporterbibtex.h" -#include "kbibtex.h" -#include "preferences.h" #include "logging_io.h" FileExporterPS::FileExporterPS(QObject *parent) : FileExporterToolchain(parent) { m_fileBasename = QStringLiteral("bibtex-to-ps"); m_fileStem = tempDir.path() + QDir::separator() + m_fileBasename; } FileExporterPS::~FileExporterPS() { /// nothing } bool FileExporterPS::save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog) { if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = false; QFile output(m_fileStem + KBibTeX::extensionBibTeX); if (output.open(QIODevice::WriteOnly)) { FileExporterBibTeX bibtexExporter(this); bibtexExporter.setEncoding(QStringLiteral("latex")); result = bibtexExporter.save(&output, bibtexfile, errorLog); output.close(); } if (result) result = generatePS(iodevice, errorLog); iodevice->close(); return result; } bool FileExporterPS::save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog) { if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = false; QFile output(m_fileStem + KBibTeX::extensionBibTeX); if (output.open(QIODevice::WriteOnly)) { FileExporterBibTeX bibtexExporter(this); bibtexExporter.setEncoding(QStringLiteral("latex")); result = bibtexExporter.save(&output, element, bibtexfile, errorLog); output.close(); } if (result) result = generatePS(iodevice, errorLog); iodevice->close(); return result; } bool FileExporterPS::generatePS(QIODevice *iodevice, QStringList *errorLog) { QStringList cmdLines {QStringLiteral("latex -halt-on-error bibtex-to-ps.tex"), QStringLiteral("bibtex bibtex-to-ps"), QStringLiteral("latex -halt-on-error bibtex-to-ps.tex"), QStringLiteral("latex -halt-on-error bibtex-to-ps.tex"), QStringLiteral("dvips -R2 -o bibtex-to-ps.ps bibtex-to-ps.dvi")}; return writeLatexFile(m_fileStem + KBibTeX::extensionTeX) && runProcesses(cmdLines, errorLog) && beautifyPostscriptFile(m_fileStem + KBibTeX::extensionPostScript, QStringLiteral("Exported Bibliography")) && writeFileToIODevice(m_fileStem + KBibTeX::extensionPostScript, iodevice, errorLog); } bool FileExporterPS::writeLatexFile(const QString &filename) { QFile latexFile(filename); if (latexFile.open(QIODevice::WriteOnly)) { QTextStream ts(&latexFile); ts.setCodec("UTF-8"); ts << "\\documentclass{article}" << endl; ts << "\\usepackage[T1]{fontenc}" << endl; ts << "\\usepackage[utf8]{inputenc}" << endl; if (kpsewhich(QStringLiteral("babel.sty"))) ts << "\\usepackage[" << Preferences::instance().laTeXBabelLanguage() << "]{babel}" << endl; if (kpsewhich(QStringLiteral("url.sty"))) ts << "\\usepackage{url}" << endl; const QString bibliographyStyle = Preferences::instance().bibTeXBibliographyStyle(); if (bibliographyStyle.startsWith(QStringLiteral("apacite")) && kpsewhich(QStringLiteral("apacite.sty"))) ts << "\\usepackage[bibnewpage]{apacite}" << endl; if ((bibliographyStyle == QStringLiteral("agsm") || bibliographyStyle == QStringLiteral("dcu") || bibliographyStyle == QStringLiteral("jmr") || bibliographyStyle == QStringLiteral("jphysicsB") || bibliographyStyle == QStringLiteral("kluwer") || bibliographyStyle == QStringLiteral("nederlands") || bibliographyStyle == QStringLiteral("dcu") || bibliographyStyle == QStringLiteral("dcu")) && kpsewhich(QStringLiteral("harvard.sty")) && kpsewhich(QStringLiteral("html.sty"))) ts << "\\usepackage{html}" << endl << "\\usepackage[dcucite]{harvard}" << endl << "\\renewcommand{\\harvardurl}{URL: \\url}" << endl; if (kpsewhich(QStringLiteral("geometry.sty"))) ts << "\\usepackage[paper=" << pageSizeToLaTeXName(Preferences::instance().pageSize()) << "]{geometry}" << endl; ts << "\\bibliographystyle{" << bibliographyStyle << "}" << endl; ts << "\\begin{document}" << endl; ts << "\\nocite{*}" << endl; ts << "\\bibliography{bibtex-to-ps}" << endl; ts << "\\end{document}" << endl; latexFile.close(); return true; } else return false; } bool FileExporterPS::beautifyPostscriptFile(const QString &filename, const QString &title) { QFile postscriptFile(filename); if (postscriptFile.open(QFile::ReadOnly)) { QTextStream ts(&postscriptFile); QStringList lines; QString line; int i = 0; while (!(line = ts.readLine()).isNull()) { if (i < 32 && line.startsWith(QStringLiteral("%%Title:"))) line = "%%Title: " + title; else if (i < 32 && line.startsWith(QStringLiteral("%%Creator:"))) line += QStringLiteral("; exported from within KBibTeX: https://userbase.kde.org/KBibTeX"); lines += line; ++i; } postscriptFile.close(); if (postscriptFile.open(QFile::WriteOnly)) { QTextStream ts(&postscriptFile); for (const QString &line : const_cast(lines)) ts << line << endl; postscriptFile.close(); } else return false; } else return false; return true; } diff --git a/src/io/fileexporterps.h b/src/io/fileexporterps.h index e7360513..374dfb4c 100644 --- a/src/io/fileexporterps.h +++ b/src/io/fileexporterps.h @@ -1,48 +1,52 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERPS_H #define KBIBTEX_IO_FILEEXPORTERPS_H -#include "fileexportertoolchain.h" +#include + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 class QStringList; /** @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporterPS : public FileExporterToolchain { Q_OBJECT public: explicit FileExporterPS(QObject *parent); ~FileExporterPS() override; bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) override; bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) override; private: QString m_fileBasename; QString m_fileStem; bool generatePS(QIODevice *iodevice, QStringList *errorLog); bool writeLatexFile(const QString &filename); bool beautifyPostscriptFile(const QString &filename, const QString &title); }; #endif // KBIBTEX_IO_FILEEXPORTERPS_H diff --git a/src/io/fileexporterris.cpp b/src/io/fileexporterris.cpp index 89c6e756..53f2154c 100644 --- a/src/io/fileexporterris.cpp +++ b/src/io/fileexporterris.cpp @@ -1,209 +1,209 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * 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 "fileexporterris.h" #include #include -#include "entry.h" +#include #include "logging_io.h" FileExporterRIS::FileExporterRIS(QObject *parent) : FileExporter(parent), m_cancelFlag(false) { /// nothing } FileExporterRIS::~FileExporterRIS() { /// nothing } bool FileExporterRIS::save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog) { Q_UNUSED(bibtexfile) Q_UNUSED(errorLog) if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCDebug(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = false; m_cancelFlag = false; QTextStream stream(iodevice); const QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) result = writeEntry(stream, entry.data()); iodevice->close(); return result && !m_cancelFlag; } bool FileExporterRIS::save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog) { Q_UNUSED(errorLog) if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCDebug(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = true; m_cancelFlag = false; QTextStream stream(iodevice); for (File::ConstIterator it = bibtexfile->constBegin(); it != bibtexfile->constEnd() && result && !m_cancelFlag; ++it) { const QSharedPointer entry = (*it).dynamicCast(); if (!entry.isNull()) { // FIXME Entry *myEntry = bibtexfile->completeReferencedFieldsConst( entry ); //Entry *myEntry = new Entry(*entry); result &= writeEntry(stream, entry.data()); //delete myEntry; } } iodevice->close(); return result && !m_cancelFlag; } void FileExporterRIS::cancel() { m_cancelFlag = true; } bool FileExporterRIS::writeEntry(QTextStream &stream, const Entry *entry) { bool result = true; QString type = entry->type(); if (type == Entry::etBook) writeKeyValue(stream, QStringLiteral("TY"), QStringLiteral("BOOK")); else if (type == Entry::etInBook) writeKeyValue(stream, QStringLiteral("TY"), QStringLiteral("CHAP")); else if (type == Entry::etInProceedings) writeKeyValue(stream, QStringLiteral("TY"), QStringLiteral("CONF")); else if (type == Entry::etArticle) writeKeyValue(stream, QStringLiteral("TY"), QStringLiteral("JOUR")); else if (type == Entry::etTechReport) writeKeyValue(stream, QStringLiteral("TY"), QStringLiteral("RPRT")); else if (type == Entry::etPhDThesis || type == Entry::etMastersThesis) writeKeyValue(stream, QStringLiteral("TY"), QStringLiteral("THES")); else if (type == Entry::etUnpublished) writeKeyValue(stream, QStringLiteral("TY"), QStringLiteral("UNPB")); else writeKeyValue(stream, QStringLiteral("TY"), QStringLiteral("GEN")); writeKeyValue(stream, QStringLiteral("ID"), entry->id()); QString year, month; for (Entry::ConstIterator it = entry->constBegin(); result && it != entry->constEnd(); ++it) { const QString key = it.key(); const Value value = it.value(); if (key.startsWith(QStringLiteral("RISfield_"))) result &= writeKeyValue(stream, key.right(2), PlainTextValue::text(value)); else if (key == Entry::ftAuthor) { for (Value::ConstIterator it = value.constBegin(); result && it != value.constEnd(); ++it) { QSharedPointer person = (*it).dynamicCast(); if (!person.isNull()) result &= writeKeyValue(stream, QStringLiteral("AU"), PlainTextValue::text(**it)); else qCWarning(LOG_KBIBTEX_IO) << "Cannot write value " << PlainTextValue::text(**it) << " for field AU (author), not supported by RIS format" << endl; } } else if (key.toLower() == Entry::ftEditor) { for (Value::ConstIterator it = value.constBegin(); result && it != value.constEnd(); ++it) { QSharedPointer person = (*it).dynamicCast(); if (!person.isNull()) result &= writeKeyValue(stream, QStringLiteral("ED"), PlainTextValue::text(**it)); else qCWarning(LOG_KBIBTEX_IO) << "Cannot write value " << PlainTextValue::text(**it) << " for field ED (editor), not supported by RIS format" << endl; } } else if (key == Entry::ftTitle) result &= writeKeyValue(stream, QStringLiteral("TI"), PlainTextValue::text(value)); else if (key == Entry::ftBookTitle) result &= writeKeyValue(stream, QStringLiteral("BT"), PlainTextValue::text(value)); else if (key == Entry::ftSeries) result &= writeKeyValue(stream, QStringLiteral("T3"), PlainTextValue::text(value)); else if (key == Entry::ftJournal) result &= writeKeyValue(stream, QStringLiteral("JO"), PlainTextValue::text(value)); ///< "JF" instead? else if (key == Entry::ftChapter) result &= writeKeyValue(stream, QStringLiteral("CP"), PlainTextValue::text(value)); else if (key == Entry::ftISSN) result &= writeKeyValue(stream, QStringLiteral("SN"), PlainTextValue::text(value)); else if (key == Entry::ftISBN) result &= writeKeyValue(stream, QStringLiteral("SN"), PlainTextValue::text(value)); else if (key == Entry::ftSchool) /// == "institution" result &= writeKeyValue(stream, QStringLiteral("IN"), PlainTextValue::text(value)); else if (key == Entry::ftVolume) result &= writeKeyValue(stream, QStringLiteral("VL"), PlainTextValue::text(value)); else if (key == Entry::ftNumber) /// == "issue" result &= writeKeyValue(stream, QStringLiteral("IS"), PlainTextValue::text(value)); else if (key == Entry::ftNote) result &= writeKeyValue(stream, QStringLiteral("N1"), PlainTextValue::text(value)); else if (key == Entry::ftAbstract) result &= writeKeyValue(stream, QStringLiteral("N2"), PlainTextValue::text(value)); ///< "AB" instead? else if (key == Entry::ftPublisher) result &= writeKeyValue(stream, QStringLiteral("PB"), PlainTextValue::text(value)); else if (key == Entry::ftLocation) result &= writeKeyValue(stream, QStringLiteral("CY"), PlainTextValue::text(value)); else if (key == Entry::ftDOI) result &= writeKeyValue(stream, QStringLiteral("DO"), PlainTextValue::text(value)); else if (key == Entry::ftKeywords) result &= writeKeyValue(stream, QStringLiteral("KW"), PlainTextValue::text(value)); else if (key == Entry::ftYear) year = PlainTextValue::text(value); else if (key == Entry::ftMonth) month = PlainTextValue::text(value); else if (key == Entry::ftAddress) result &= writeKeyValue(stream, QStringLiteral("AD"), PlainTextValue::text(value)); else if (key == Entry::ftUrl) { // FIXME one "UR" line per URL // FIXME for local files, use "L1" result &= writeKeyValue(stream, QStringLiteral("UR"), PlainTextValue::text(value)); } else if (key == Entry::ftPages) { static const QRegularExpression splitRegExp(QString(QStringLiteral("-{1,2}|%1")).arg(QChar(0x2013))); QStringList pageRange = PlainTextValue::text(value).split(splitRegExp); if (pageRange.count() == 2) { result &= writeKeyValue(stream, QStringLiteral("SP"), pageRange[ 0 ]); result &= writeKeyValue(stream, QStringLiteral("EP"), pageRange[ 1 ]); } } } if (!year.isEmpty() || !month.isEmpty()) { result &= writeKeyValue(stream, QStringLiteral("PY"), QString(QStringLiteral("%1/%2//")).arg(year, month)); } result &= writeKeyValue(stream, QStringLiteral("ER"), QString()); stream << endl; return result; } bool FileExporterRIS::writeKeyValue(QTextStream &stream, const QString &key, const QString &value) { stream << key << " - "; if (!value.isEmpty()) stream << value; stream << endl; return true; } diff --git a/src/io/fileexporterris.h b/src/io/fileexporterris.h index 1aea1f21..707abed7 100644 --- a/src/io/fileexporterris.h +++ b/src/io/fileexporterris.h @@ -1,50 +1,54 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERRIS_H #define KBIBTEX_IO_FILEEXPORTERRIS_H #include -#include "fileexporter.h" +#include + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 class Element; class File; class Entry; class KBIBTEXIO_EXPORT FileExporterRIS : public FileExporter { Q_OBJECT public: explicit FileExporterRIS(QObject *parent); ~FileExporterRIS() override; bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) override; bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) override; public slots: void cancel() override; private: bool m_cancelFlag; bool writeEntry(QTextStream &stream, const Entry *entry); bool writeKeyValue(QTextStream &stream, const QString &key, const QString &value); }; #endif // KBIBTEX_IO_FILEEXPORTERRIS_H diff --git a/src/io/fileexporterrtf.cpp b/src/io/fileexporterrtf.cpp index e97046c8..4def9dc5 100644 --- a/src/io/fileexporterrtf.cpp +++ b/src/io/fileexporterrtf.cpp @@ -1,128 +1,128 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 "fileexporterrtf.h" #include #include #include #include -#include "element.h" +#include +#include +#include #include "fileexporterbibtex.h" -#include "kbibtex.h" -#include "preferences.h" #include "logging_io.h" FileExporterRTF::FileExporterRTF(QObject *parent) : FileExporterToolchain(parent) { m_fileBasename = QStringLiteral("bibtex-to-rtf"); m_fileStem = tempDir.path() + QDir::separator() + m_fileBasename; } FileExporterRTF::~FileExporterRTF() { /// nothing } bool FileExporterRTF::save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog) { if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = false; QFile output(m_fileStem + KBibTeX::extensionBibTeX); if (output.open(QIODevice::WriteOnly)) { FileExporterBibTeX bibtexExporter(this); bibtexExporter.setEncoding(QStringLiteral("latex")); result = bibtexExporter.save(&output, bibtexfile, errorLog); output.close(); } if (result) result = generateRTF(iodevice, errorLog); iodevice->close(); return result; } bool FileExporterRTF::save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog) { if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = false; QFile output(m_fileStem + KBibTeX::extensionBibTeX); if (output.open(QIODevice::WriteOnly)) { FileExporterBibTeX bibtexExporter(this); bibtexExporter.setEncoding(QStringLiteral("latex")); result = bibtexExporter.save(&output, element, bibtexfile, errorLog); output.close(); } if (result) result = generateRTF(iodevice, errorLog); iodevice->close(); return result; } bool FileExporterRTF::generateRTF(QIODevice *iodevice, QStringList *errorLog) { QStringList cmdLines {QStringLiteral("latex -halt-on-error bibtex-to-rtf.tex"), QStringLiteral("bibtex bibtex-to-rtf"), QStringLiteral("latex -halt-on-error bibtex-to-rtf.tex"), QString(QStringLiteral("latex2rtf -i %1 bibtex-to-rtf.tex")).arg(Preferences::instance().laTeXBabelLanguage())}; return writeLatexFile(m_fileStem + KBibTeX::extensionTeX) && runProcesses(cmdLines, errorLog) && writeFileToIODevice(m_fileStem + KBibTeX::extensionRTF, iodevice, errorLog); } bool FileExporterRTF::writeLatexFile(const QString &filename) { QFile latexFile(filename); if (latexFile.open(QIODevice::WriteOnly)) { QTextStream ts(&latexFile); ts.setCodec("UTF-8"); ts << "\\documentclass{article}" << endl; ts << "\\usepackage[T1]{fontenc}" << endl; ts << "\\usepackage[utf8]{inputenc}" << endl; if (kpsewhich(QStringLiteral("babel.sty"))) ts << "\\usepackage[" << Preferences::instance().laTeXBabelLanguage() << "]{babel}" << endl; if (kpsewhich(QStringLiteral("url.sty"))) ts << "\\usepackage{url}" << endl; const QString bibliographyStyle = Preferences::instance().bibTeXBibliographyStyle(); if (bibliographyStyle.startsWith(QStringLiteral("apacite")) && kpsewhich(QStringLiteral("apacite.sty"))) ts << "\\usepackage[bibnewpage]{apacite}" << endl; if (bibliographyStyle == QStringLiteral("dcu") && kpsewhich(QStringLiteral("harvard.sty")) && kpsewhich(QStringLiteral("html.sty"))) ts << "\\usepackage{html}" << endl << "\\usepackage[dcucite]{harvard}" << endl << "\\renewcommand{\\harvardurl}{URL: \\url}" << endl; if (kpsewhich(QStringLiteral("geometry.sty"))) ts << "\\usepackage[paper=" << pageSizeToLaTeXName(Preferences::instance().pageSize()) << "]{geometry}" << endl; ts << "\\bibliographystyle{" << bibliographyStyle << "}" << endl; ts << "\\begin{document}" << endl; ts << "\\nocite{*}" << endl; ts << "\\bibliography{bibtex-to-rtf}" << endl; ts << "\\end{document}" << endl; latexFile.close(); return true; } return false; } diff --git a/src/io/fileexporterrtf.h b/src/io/fileexporterrtf.h index 6de7e25d..2609d4dc 100644 --- a/src/io/fileexporterrtf.h +++ b/src/io/fileexporterrtf.h @@ -1,47 +1,51 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERRTF_H #define KBIBTEX_IO_FILEEXPORTERRTF_H -#include "fileexportertoolchain.h" +#include + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 class QTextStream; /** @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporterRTF : public FileExporterToolchain { Q_OBJECT public: explicit FileExporterRTF(QObject *parent); ~FileExporterRTF() override; bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) override; bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) override; private: QString m_fileBasename; QString m_fileStem; bool generateRTF(QIODevice *iodevice, QStringList *errorLog); bool writeLatexFile(const QString &filename); }; #endif // KBIBTEX_IO_FILEEXPORTERRTF_H diff --git a/src/io/fileexportertoolchain.cpp b/src/io/fileexportertoolchain.cpp index 433e231a..1f66fba4 100644 --- a/src/io/fileexportertoolchain.cpp +++ b/src/io/fileexportertoolchain.cpp @@ -1,161 +1,161 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 "fileexportertoolchain.h" #include #include #include #include #include #include #include #include #include #include -#include "preferences.h" +#include FileExporterToolchain::FileExporterToolchain(QObject *parent) : FileExporter(parent) { tempDir.setAutoRemove(true); } bool FileExporterToolchain::runProcesses(const QStringList &progs, QStringList *errorLog) { bool result = true; int i = 0; emit progress(0, progs.size()); for (QStringList::ConstIterator it = progs.constBegin(); result && it != progs.constEnd(); ++it) { QCoreApplication::instance()->processEvents(); QStringList args = (*it).split(' '); QString cmd = args.first(); args.erase(args.begin()); result &= runProcess(cmd, args, errorLog); emit progress(i++, progs.size()); } QCoreApplication::instance()->processEvents(); return result; } bool FileExporterToolchain::runProcess(const QString &cmd, const QStringList &args, QStringList *errorLog) { QProcess process(this); QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment(); /// Avoid some paranoid security settings in BibTeX processEnvironment.insert(QStringLiteral("openout_any"), QStringLiteral("r")); /// Make applications use working directory as temporary directory processEnvironment.insert(QStringLiteral("TMPDIR"), tempDir.path()); processEnvironment.insert(QStringLiteral("TEMPDIR"), tempDir.path()); process.setProcessEnvironment(processEnvironment); process.setWorkingDirectory(tempDir.path()); /// Assemble the full command line (program name + arguments) /// for use in log messages and debug output const QString fullCommandLine = cmd + QLatin1Char(' ') + args.join(QStringLiteral(" ")); if (errorLog != nullptr) errorLog->append(i18n("Running command '%1' using working directory '%2'", fullCommandLine, process.workingDirectory())); process.start(cmd, args); if (errorLog != nullptr) { /// Redirect any standard output from process into errorLog connect(&process, &QProcess::readyReadStandardOutput, [errorLog, &process] { QTextStream ts(process.readAllStandardOutput()); while (!ts.atEnd()) errorLog->append(ts.readLine()); }); /// Redirect any standard error from process into errorLog connect(&process, &QProcess::readyReadStandardError, [errorLog, &process] { QTextStream ts(process.readAllStandardError()); while (!ts.atEnd()) errorLog->append(ts.readLine()); }); } bool result = process.waitForStarted(3000); if (!result) { if (errorLog != nullptr) errorLog->append(i18n("Starting command '%1' failed: %2", fullCommandLine, process.errorString())); return false; } if (process.waitForFinished(30000)) result = process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0; if (errorLog != nullptr) { if (result) errorLog->append(i18n("Command '%1' succeeded", fullCommandLine)); else errorLog->append(i18n("Command '%1' failed with exit code %2: %3", fullCommandLine, process.exitCode(), process.errorString())); } return result; } bool FileExporterToolchain::writeFileToIODevice(const QString &filename, QIODevice *device, QStringList *errorLog) { QFile file(filename); if (file.open(QIODevice::ReadOnly)) { bool result = true; static const qint64 buffersize = 0x10000; qint64 amount = 0; char buffer[buffersize]; do { result = ((amount = file.read(buffer, buffersize)) > -1) && (device->write(buffer, amount) > -1); } while (result && amount > 0); file.close(); if (errorLog != nullptr) errorLog->append(i18n("Writing to file '%1' succeeded", filename)); return result; } if (errorLog != nullptr) errorLog->append(i18n("Writing to file '%1' failed", filename)); return false; } QString FileExporterToolchain::pageSizeToLaTeXName(const QPageSize::PageSizeId pageSizeId) const { for (const auto &dbItem : Preferences::availablePageSizes) if (dbItem.first == pageSizeId) return dbItem.second; return QPageSize::name(pageSizeId).toLower(); ///< just a wild guess } bool FileExporterToolchain::kpsewhich(const QString &filename) { static QHash kpsewhichMap; if (kpsewhichMap.contains(filename)) return kpsewhichMap.value(filename, false); bool result = false; QProcess kpsewhich; const QStringList param {filename}; kpsewhich.start(QStringLiteral("kpsewhich"), param); if (kpsewhich.waitForStarted(3000) && kpsewhich.waitForFinished(30000)) { const QString standardOut = QString::fromUtf8(kpsewhich.readAllStandardOutput()); result = kpsewhich.exitStatus() == QProcess::NormalExit && kpsewhich.exitCode() == 0 && standardOut.endsWith(QDir::separator() + filename + QChar('\n')); kpsewhichMap.insert(filename, result); } return result; } diff --git a/src/io/fileexportertoolchain.h b/src/io/fileexportertoolchain.h index 8440b540..0dfcad6e 100644 --- a/src/io/fileexportertoolchain.h +++ b/src/io/fileexportertoolchain.h @@ -1,50 +1,54 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERTOOLCHAIN_H #define KBIBTEX_IO_FILEEXPORTERTOOLCHAIN_H #include #include -#include "fileexporter.h" +#include + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 class QString; /** @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporterToolchain : public FileExporter { Q_OBJECT public: explicit FileExporterToolchain(QObject *parent); static bool kpsewhich(const QString &filename); protected: QTemporaryDir tempDir; bool runProcesses(const QStringList &progs, QStringList *errorLog = nullptr); bool runProcess(const QString &cmd, const QStringList &args, QStringList *errorLog = nullptr); bool writeFileToIODevice(const QString &filename, QIODevice *device, QStringList *errorLog = nullptr); QString pageSizeToLaTeXName(const QPageSize::PageSizeId pageSizeId) const; }; #endif // KBIBTEX_IO_FILEEXPORTERTOOLCHAIN_H diff --git a/src/io/fileexporterxml.cpp b/src/io/fileexporterxml.cpp index b20776d9..7e065135 100644 --- a/src/io/fileexporterxml.cpp +++ b/src/io/fileexporterxml.cpp @@ -1,259 +1,259 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * 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 "fileexporterxml.h" #include #include -#include "kbibtex.h" -#include "file.h" -#include "entry.h" -#include "macro.h" -#include "comment.h" +#include +#include +#include +#include +#include #include "encoderxml.h" #include "logging_io.h" FileExporterXML::FileExporterXML(QObject *parent) : FileExporter(parent), m_cancelFlag(false) { /// nothing } FileExporterXML::~FileExporterXML() { /// nothing } bool FileExporterXML::save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog) { Q_UNUSED(errorLog) if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } bool result = true; m_cancelFlag = false; QTextStream stream(iodevice); stream.setCodec("UTF-8"); stream << "" << endl; stream << "" << endl; stream << "" << endl; stream << "" << endl; for (File::ConstIterator it = bibtexfile->constBegin(); it != bibtexfile->constEnd() && result && !m_cancelFlag; ++it) write(stream, (*it).data(), bibtexfile); stream << "" << endl; iodevice->close(); return result && !m_cancelFlag; } bool FileExporterXML::save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog) { Q_UNUSED(bibtexfile) Q_UNUSED(errorLog) if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } QTextStream stream(iodevice); stream.setCodec("UTF-8"); stream << "" << endl; stream << "" << endl; stream << "" << endl; const bool result = write(stream, element.data()); iodevice->close(); return result; } void FileExporterXML::cancel() { m_cancelFlag = true; } bool FileExporterXML::write(QTextStream &stream, const Element *element, const File *bibtexfile) { bool result = false; const Entry *entry = dynamic_cast(element); if (entry != nullptr) { if (bibtexfile == nullptr) result |= writeEntry(stream, entry); else { QScopedPointer resolvedEntry(entry->resolveCrossref(bibtexfile)); result |= writeEntry(stream, resolvedEntry.data()); } } else { const Macro *macro = dynamic_cast(element); if (macro != nullptr) result |= writeMacro(stream, macro); else { const Comment *comment = dynamic_cast(element); if (comment != nullptr) result |= writeComment(stream, comment); else { // preambles are ignored, make no sense in XML files } } } return result; } bool FileExporterXML::writeEntry(QTextStream &stream, const Entry *entry) { stream << " id(), Encoder::TargetEncodingUTF8) << "\" type=\"" << entry->type().toLower() << "\">" << endl; for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it) { const QString key = it.key().toLower(); const Value value = it.value(); if (key == Entry::ftAuthor || key == Entry::ftEditor) { Value internal = value; Value::ConstIterator lastIt = internal.constEnd(); --lastIt; const QSharedPointer last = *lastIt; stream << " <" << key << "s"; if (!value.isEmpty() && PlainText::isPlainText(*last)) { QSharedPointer pt = internal.last().staticCast(); if (pt->text() == QStringLiteral("others")) { internal.erase(internal.end() - 1); stream << " etal=\"true\""; } } stream << ">" << endl; stream << valueToXML(internal, key) << endl; stream << " " << endl; } else if (key == Entry::ftAbstract) { static const QRegularExpression abstractRegExp(QStringLiteral("\\bAbstract[:]?([ ]| |&nbsp;)*"), QRegularExpression::CaseInsensitiveOption); /// clean up HTML artifacts QString text = valueToXML(value); text = text.remove(abstractRegExp); stream << " <" << key << ">" << text << "" << endl; } else if (key == Entry::ftMonth) { stream << " macro = valueItem.dynamicCast(); if (!macro.isNull()) for (int i = 0; i < 12; i++) { if (QString::compare(macro->text(), KBibTeX::MonthsTriple[ i ]) == 0) { if (month < 1) { tag = KBibTeX::MonthsTriple[ i ]; month = i + 1; } content.append(KBibTeX::Months[ i ]); ok = true; break; } } else content.append(PlainTextValue::text(valueItem)); } if (!ok) content = valueToXML(value) ; if (!tag.isEmpty()) stream << " tag=\"" << key << "\""; if (month > 0) stream << " month=\"" << month << "\""; stream << '>' << content; stream << "" << endl; } else { stream << " <" << key << ">" << valueToXML(value) << "" << endl; } } stream << " " << endl; return true; } bool FileExporterXML::writeMacro(QTextStream &stream, const Macro *macro) { stream << " key() << "\">"; stream << valueToXML(macro->value()); stream << "" << endl; return true; } bool FileExporterXML::writeComment(QTextStream &stream, const Comment *comment) { stream << " " ; stream << EncoderXML::instance().encode(comment->text(), Encoder::TargetEncodingUTF8); stream << "" << endl; return true; } QString FileExporterXML::valueToXML(const Value &value, const QString &) { QString result; bool isFirst = true; for (const auto &valueItem : value) { if (!isFirst) result.append(' '); isFirst = false; QSharedPointer plainText = valueItem.dynamicCast(); if (!plainText.isNull()) result.append("" + cleanXML(EncoderXML::instance().encode(PlainTextValue::text(valueItem), Encoder::TargetEncodingUTF8)) + ""); else { QSharedPointer p = valueItem.dynamicCast(); if (!p.isNull()) { result.append(""); if (!p->firstName().isEmpty()) result.append("" + cleanXML(EncoderXML::instance().encode(p->firstName(), Encoder::TargetEncodingUTF8)) + ""); if (!p->lastName().isEmpty()) result.append("" + cleanXML(EncoderXML::instance().encode(p->lastName(), Encoder::TargetEncodingUTF8)) + ""); if (!p->suffix().isEmpty()) result.append("" + cleanXML(EncoderXML::instance().encode(p->suffix(), Encoder::TargetEncodingUTF8)) + ""); result.append(""); } // TODO: Other data types else result.append("" + cleanXML(EncoderXML::instance().encode(PlainTextValue::text(valueItem), Encoder::TargetEncodingUTF8)) + ""); } } return result; } QString FileExporterXML::cleanXML(const QString &text) { static const QRegularExpression removal(QStringLiteral("[{}]+")); static const QRegularExpression lineBreaksRegExp(QStringLiteral("[ \\t]*[\\n\\r]")); QString result = text; result = result.replace(lineBreaksRegExp, QStringLiteral("
")).remove(removal).remove(QStringLiteral("\\ensuremath")); return result; } diff --git a/src/io/fileexporterxml.h b/src/io/fileexporterxml.h index 9c227034..52f4da88 100644 --- a/src/io/fileexporterxml.h +++ b/src/io/fileexporterxml.h @@ -1,65 +1,65 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERXML_H #define KBIBTEX_IO_FILEEXPORTERXML_H #include -#include "element.h" -#include "value.h" -#include "fileexporter.h" +#include +#include +#include #ifdef HAVE_KF5 #include "kbibtexio_export.h" #endif // HAVE_KF5 class Entry; class Macro; class Comment; /** * @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporterXML : public FileExporter { Q_OBJECT public: explicit FileExporterXML(QObject *parent); ~FileExporterXML() override; bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) override; bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) override; static QString valueToXML(const Value &value, const QString &fieldType = QString()); public slots: void cancel() override; private: bool m_cancelFlag; bool write(QTextStream &stream, const Element *element, const File *bibtexfile = nullptr); bool writeEntry(QTextStream &stream, const Entry *entry); bool writeMacro(QTextStream &stream, const Macro *macro); bool writeComment(QTextStream &stream, const Comment *comment); static QString cleanXML(const QString &text); }; -#endif +#endif // KBIBTEX_IO_FILEEXPORTERXML_H diff --git a/src/io/fileexporterxslt.cpp b/src/io/fileexporterxslt.cpp index 4adc67ad..82b67bcd 100644 --- a/src/io/fileexporterxslt.cpp +++ b/src/io/fileexporterxslt.cpp @@ -1,125 +1,124 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer * * * * 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 "fileexporterxslt.h" #include #include #include #include -#include "file.h" -#include "element.h" -#include "entry.h" -#include "macro.h" -#include "comment.h" +#include +#include +#include +#include #include "encoderxml.h" #include "fileexporterxml.h" #include "xsltransform.h" #include "logging_io.h" FileExporterXSLT::FileExporterXSLT(const QString &xsltFilename, QObject *parent) : FileExporter(parent), m_cancelFlag(false), m_xsltFilename(xsltFilename) { if (xsltFilename.isEmpty() || !QFile(xsltFilename).exists()) qCWarning(LOG_KBIBTEX_IO) << "Invalid XSLT filename: " << xsltFilename; } FileExporterXSLT::~FileExporterXSLT() { /// nothing } bool FileExporterXSLT::save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog) { if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } else if (m_xsltFilename.isEmpty() || !QFile(m_xsltFilename).exists()) { qCWarning(LOG_KBIBTEX_IO) << "Invalid XSLT filename: " << m_xsltFilename; return false; } m_cancelFlag = false; XSLTransform xsltransformer(m_xsltFilename); FileExporterXML xmlExporter(this); QBuffer buffer; buffer.open(QIODevice::WriteOnly); if (xmlExporter.save(&buffer, bibtexfile, errorLog)) { buffer.close(); buffer.open(QIODevice::ReadOnly); const QString xml = QString::fromUtf8(buffer.readAll().constData()); buffer.close(); const QString html = xsltransformer.transform(xml); if (!html.isEmpty()) { iodevice->write(html.toUtf8()); iodevice->close(); return !m_cancelFlag; } } iodevice->close(); return false; } bool FileExporterXSLT::save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog) { if (!iodevice->isWritable() && !iodevice->open(QIODevice::WriteOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Output device not writable"; return false; } else if (m_xsltFilename.isEmpty() || !QFile(m_xsltFilename).exists()) { qCWarning(LOG_KBIBTEX_IO) << "Invalid XSLT filename: " << m_xsltFilename; return false; } m_cancelFlag = false; XSLTransform xsltransformer(m_xsltFilename); FileExporterXML xmlExporter(this); QBuffer buffer; buffer.open(QIODevice::WriteOnly); if (xmlExporter.save(&buffer, element, bibtexfile, errorLog)) { buffer.close(); buffer.open(QIODevice::ReadOnly); const QString xml = QString::fromUtf8(buffer.readAll().constData()); buffer.close(); const QString html = xsltransformer.transform(xml); if (!html.isEmpty()) { iodevice->write(html.toUtf8()); iodevice->close(); return !m_cancelFlag; } } iodevice->close(); return false; } void FileExporterXSLT::cancel() { m_cancelFlag = true; } FileExporterHTML::FileExporterHTML(QObject *parent) : FileExporterXSLT(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kbibtex/standard.xsl")), parent) { /// nothing } diff --git a/src/io/fileexporterxslt.h b/src/io/fileexporterxslt.h index ebc036b7..1b64027e 100644 --- a/src/io/fileexporterxslt.h +++ b/src/io/fileexporterxslt.h @@ -1,65 +1,69 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEEXPORTERXSLT_H #define KBIBTEX_IO_FILEEXPORTERXSLT_H #include -#include "element.h" -#include "value.h" -#include "fileexporter.h" +#include +#include +#include + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 class Entry; class Macro; class Comment; /** * @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporterXSLT : public FileExporter { Q_OBJECT public: explicit FileExporterXSLT(const QString &xsltFilename, QObject *parent); ~FileExporterXSLT() override; bool save(QIODevice *iodevice, const File *bibtexfile, QStringList *errorLog = nullptr) override; bool save(QIODevice *iodevice, const QSharedPointer element, const File *bibtexfile, QStringList *errorLog = nullptr) override; public slots: void cancel() override; private: bool m_cancelFlag; QString m_xsltFilename; }; /** * @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileExporterHTML : public FileExporterXSLT { Q_OBJECT public: explicit FileExporterHTML(QObject *parent); }; #endif // KBIBTEX_IO_FILEEXPORTERXSLT_H diff --git a/src/io/fileimporter.cpp b/src/io/fileimporter.cpp index 81274564..358ed7ef 100644 --- a/src/io/fileimporter.cpp +++ b/src/io/fileimporter.cpp @@ -1,145 +1,145 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * 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 "fileimporter.h" #include #include #include #include -#include "value.h" +#include #include "logging_io.h" FileImporter::FileImporter(QObject *parent) : QObject(parent) { /// nothing } FileImporter::~FileImporter() { /// nothing } File *FileImporter::fromString(const QString &text) { if (text.isEmpty()) { qCWarning(LOG_KBIBTEX_IO) << "Cannot create File object from empty string"; return nullptr; } QBuffer buffer; buffer.open(QIODevice::WriteOnly); buffer.write(text.toUtf8()); buffer.close(); buffer.open(QIODevice::ReadOnly); File *result = load(&buffer); if (result == nullptr) qCWarning(LOG_KBIBTEX_IO) << "Creating File object from" << buffer.size() << "Bytes of data failed"; buffer.close(); return result; } Person *FileImporter::splitName(const QString &name) { QString firstName; QString lastName; QString suffix; if (!name.contains(QLatin1Char(','))) { static const QRegularExpression splittingRegExp(QStringLiteral("[ ]+")); const QStringList segments = name.split(splittingRegExp); /** PubMed uses a special writing style for names, where the last name is followed by * single capital letters, each being the first letter of each first name * So, check how many single capital letters are at the end of the given segment list */ int singleCapitalLettersCounter = 0; int p = segments.count() - 1; while (segments[p].length() == 1 && segments[p][0].isUpper()) { --p; ++singleCapitalLettersCounter; } if (singleCapitalLettersCounter > 0) { /** This is a special case for names from PubMed, which are formatted like "Fischer T A" * all segment values until the first single letter segment are last name parts */ for (int i = 0; i < p; ++i) lastName.append(segments[i]).append(QStringLiteral(" ")); lastName.append(segments[p]); /// Single letter segments are first name parts for (int i = p + 1; i < segments.count() - 1; ++i) firstName.append(segments[i]).append(QStringLiteral(" ")); firstName.append(segments[segments.count() - 1]); } else { int from = segments.count() - 1; if (looksLikeSuffix(segments[from])) { suffix = segments[from]; --from; } lastName = segments[from]; ///< Initialize last name with last segment /// Check for lower case parts of the last name such as "van", "von", "de", ... while (from > 0) { if (segments[from - 1].compare(segments[from - 1].toLower()) != 0) break; --from; lastName.prepend(QStringLiteral(" ")); lastName.prepend(segments[from]); } if (from > 0) { firstName = *segments.begin(); /// First name initialized with first segment for (QStringList::ConstIterator it = ++segments.begin(); from > 1; ++it, --from) { firstName.append(" "); firstName.append(*it); } } } } else { const QStringList segments = name.split(QStringLiteral(",")); /// segments.count() must be >=2 if (segments.count() == 2) { /// Most probably "Smith, Adam" lastName = segments[0].trimmed(); firstName = segments[1].trimmed(); } else if (segments.count() == 3 && looksLikeSuffix(segments[2])) { /// Most probably "Smith, Adam, Jr." lastName = segments[0].trimmed(); firstName = segments[1].trimmed(); suffix = segments[2].trimmed(); } else qWarning() << "Too many commas in name:" << name; } return new Person(firstName, lastName, suffix); } bool FileImporter::looksLikeSuffix(const QString &suffix) { const QString normalizedSuffix = suffix.trimmed().toLower(); return normalizedSuffix == QStringLiteral("jr") || normalizedSuffix == QStringLiteral("jr.") || normalizedSuffix == QStringLiteral("sr") || normalizedSuffix == QStringLiteral("sr.") || normalizedSuffix == QStringLiteral("ii") || normalizedSuffix == QStringLiteral("iii") || normalizedSuffix == QStringLiteral("iv"); } // #include "fileimporter.moc" diff --git a/src/io/fileimporter.h b/src/io/fileimporter.h index 1ccba3f5..9a3ab8c5 100644 --- a/src/io/fileimporter.h +++ b/src/io/fileimporter.h @@ -1,114 +1,114 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * 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 KBIBTEX_IO_FILEIMPORTER_H #define KBIBTEX_IO_FILEIMPORTER_H +#include + #ifdef HAVE_KF5 #include "kbibtexio_export.h" #endif // HAVE_KF5 -#include - class QIODevice; class File; class Person; /** @author Thomas Fischer */ class KBIBTEXIO_EXPORT FileImporter : public QObject { Q_OBJECT public: enum MessageSeverity { SeverityInfo, ///< Messages that are of informative type, such as additional comma for last key-value pair in BibTeX entry SeverityWarning, ///< Messages that are of warning type, such as automatic corrections of BibTeX code without loss of information SeverityError ///< Messages that are of error type, which point to issue where information may get lost, e.g. invalid syntax or incomplete data }; explicit FileImporter(QObject *parent); ~FileImporter() override; File *fromString(const QString &text); virtual File *load(QIODevice *iodevice) = 0; /** * When importing data, show a dialog where the user may select options on the * import process such as selecting encoding. Re-implementing this function is * optional and should only be done if user interaction is necessary at import * actions. * Return true if the configuration step was successful and the application * may proceed. If returned false, the import process has to be stopped. * The importer may store configurations done here for future use (e.g. set default * values based on user input). * A calling application should call this function before calling load() or similar * functions. * The implementer may choose to show or not show a dialog, depending on e.g. if * additional information is necessary or not. */ virtual bool showImportDialog(QWidget *parent) { Q_UNUSED(parent); return true; } static bool guessCanDecode(const QString &) { return false; } /** * Split a person's name into its parts and construct a Person object from them. * This is a rather general functions and takes e.g. the curly brackets used in * (La)TeX not into account. * @param name The persons name * @return A Person object containing the name * @see Person */ static Person *splitName(const QString &name); private: static bool looksLikeSuffix(const QString &suffix); signals: void progress(int current, int total); /** * Signal to notify the user of a FileImporter class about issues detected * during loading and parsing bibliographic data. Messages may be of various * severity levels. The message text may reveal additional information on * what the issue is and where it has been found (e.g. line number). * Implementations of FileImporter are recommended to print a similar message * as debug output. * TODO messages shall get i18n'ized if the code is compiled with/linked against * KDE Frameworks libraries. * * @param severity The message's severity level * @param messageText The message's text */ void message(const FileImporter::MessageSeverity severity, const QString &messageText); public slots: virtual void cancel() { // nothing } }; Q_DECLARE_METATYPE(FileImporter::MessageSeverity) #endif // KBIBTEX_IO_FILEIMPORTER_H diff --git a/src/io/fileimporterbibtex.cpp b/src/io/fileimporterbibtex.cpp index 8efe79ee..ba271b40 100644 --- a/src/io/fileimporterbibtex.cpp +++ b/src/io/fileimporterbibtex.cpp @@ -1,1305 +1,1305 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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 "fileimporterbibtex.h" #include #include #include #include #include -#include "preferences.h" -#include "file.h" -#include "comment.h" -#include "macro.h" -#include "preamble.h" -#include "entry.h" -#include "element.h" -#include "value.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "encoder.h" #include "encoderlatex.h" -#include "bibtexentries.h" -#include "bibtexfields.h" #include "logging_io.h" #define qint64toint(a) (static_cast(qMax(0LL,qMin(0x7fffffffLL,(a))))) FileImporterBibTeX::FileImporterBibTeX(QObject *parent) : FileImporter(parent), m_cancelFlag(false), m_textStream(nullptr), m_commentHandling(IgnoreComments), m_keywordCasing(KBibTeX::cLowerCase), m_lineNo(1) { m_keysForPersonDetection.append(Entry::ftAuthor); m_keysForPersonDetection.append(Entry::ftEditor); m_keysForPersonDetection.append(QStringLiteral("bookauthor")); /// used by JSTOR } File *FileImporterBibTeX::load(QIODevice *iodevice) { m_cancelFlag = false; if (!iodevice->isReadable() && !iodevice->open(QIODevice::ReadOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Input device not readable"; emit message(SeverityError, QStringLiteral("Input device not readable")); return nullptr; } File *result = new File(); /// Used to determine if file prefers quotation marks over /// curly brackets or the other way around m_statistics.countCurlyBrackets = 0; m_statistics.countQuotationMarks = 0; m_statistics.countFirstNameFirst = 0; m_statistics.countLastNameFirst = 0; m_statistics.countNoCommentQuote = 0; m_statistics.countCommentPercent = 0; m_statistics.countCommentCommand = 0; m_statistics.countProtectedTitle = 0; m_statistics.countUnprotectedTitle = 0; m_statistics.mostRecentListSeparator.clear(); m_textStream = new QTextStream(iodevice); m_textStream->setCodec(Preferences::defaultBibTeXEncoding.toLatin1()); ///< unless we learn something else, assume default codec result->setProperty(File::Encoding, Preferences::defaultBibTeXEncoding); QString rawText; rawText.reserve(qint64toint(iodevice->size())); while (!m_textStream->atEnd()) { QString line = m_textStream->readLine(); bool skipline = evaluateParameterComments(m_textStream, line.toLower(), result); // FIXME XML data should be removed somewhere else? onlinesearch ... if (line.startsWith(QStringLiteral(""))) /// Hop over XML declarations skipline = true; if (!skipline) rawText.append(line).append("\n"); } delete m_textStream; /** Remove HTML code from the input source */ // FIXME HTML data should be removed somewhere else? onlinesearch ... const int originalLength = rawText.length(); rawText = rawText.remove(KBibTeX::htmlRegExp); const int afterHTMLremovalLength = rawText.length(); if (originalLength != afterHTMLremovalLength) { qCInfo(LOG_KBIBTEX_IO) << (originalLength - afterHTMLremovalLength) << "characters of HTML tags have been removed"; emit message(SeverityInfo, QString(QStringLiteral("%1 characters of HTML tags have been removed")).arg(originalLength - afterHTMLremovalLength)); } // TODO really necessary to pipe data through several QTextStreams? m_textStream = new QTextStream(&rawText, QIODevice::ReadOnly); m_textStream->setCodec(Preferences::defaultBibTeXEncoding.toLower() == QStringLiteral("latex") ? "us-ascii" : Preferences::defaultBibTeXEncoding.toLatin1()); m_lineNo = 1; m_prevLine = m_currentLine = QString(); m_knownElementIds.clear(); readChar(); while (!m_nextChar.isNull() && !m_cancelFlag && !m_textStream->atEnd()) { emit progress(qint64toint(m_textStream->pos()), rawText.length()); Element *element = nextElement(); if (element != nullptr) { if (m_commentHandling == KeepComments || !Comment::isComment(*element)) result->append(QSharedPointer(element)); else delete element; } } emit progress(100, 100); if (m_cancelFlag) { qCWarning(LOG_KBIBTEX_IO) << "Loading bibliography data has been canceled"; emit message(SeverityError, QStringLiteral("Loading bibliography data has been canceled")); delete result; result = nullptr; } delete m_textStream; if (result != nullptr) { /// Set the file's preferences for string delimiters /// deduced from statistics built while parsing the file result->setProperty(File::StringDelimiter, m_statistics.countQuotationMarks > m_statistics.countCurlyBrackets ? QStringLiteral("\"\"") : QStringLiteral("{}")); /// Set the file's preferences for name formatting result->setProperty(File::NameFormatting, m_statistics.countFirstNameFirst > m_statistics.countLastNameFirst ? Preferences::personNameFormatFirstLast : Preferences::personNameFormatLastFirst); /// Set the file's preferences for title protected Qt::CheckState triState = (m_statistics.countProtectedTitle > m_statistics.countUnprotectedTitle * 4) ? Qt::Checked : ((m_statistics.countProtectedTitle * 4 < m_statistics.countUnprotectedTitle) ? Qt::Unchecked : Qt::PartiallyChecked); result->setProperty(File::ProtectCasing, static_cast(triState)); /// Set the file's preferences for quoting of comments if (m_statistics.countNoCommentQuote > m_statistics.countCommentCommand && m_statistics.countNoCommentQuote > m_statistics.countCommentPercent) result->setProperty(File::QuoteComment, static_cast(Preferences::qcNone)); else if (m_statistics.countCommentCommand > m_statistics.countNoCommentQuote && m_statistics.countCommentCommand > m_statistics.countCommentPercent) result->setProperty(File::QuoteComment, static_cast(Preferences::qcCommand)); else result->setProperty(File::QuoteComment, static_cast(Preferences::qcPercentSign)); if (!m_statistics.mostRecentListSeparator.isEmpty()) result->setProperty(File::ListSeparator, m_statistics.mostRecentListSeparator); // TODO gather more statistics for keyword casing etc. } iodevice->close(); return result; } bool FileImporterBibTeX::guessCanDecode(const QString &rawText) { static const QRegularExpression bibtexLikeText(QStringLiteral("@\\w+\\{.+\\}")); QString text = EncoderLaTeX::instance().decode(rawText); return bibtexLikeText.match(text).hasMatch(); } void FileImporterBibTeX::cancel() { m_cancelFlag = true; } Element *FileImporterBibTeX::nextElement() { Token token = nextToken(); if (token == tAt) { const QString elementType = readSimpleString(); const QString elementTypeLower = elementType.toLower(); if (elementTypeLower == QStringLiteral("comment")) { ++m_statistics.countCommentCommand; return readCommentElement(); } else if (elementTypeLower == QStringLiteral("string")) return readMacroElement(); else if (elementTypeLower == QStringLiteral("preamble")) return readPreambleElement(); else if (elementTypeLower == QStringLiteral("import")) { qCDebug(LOG_KBIBTEX_IO) << "Skipping potential HTML/JavaScript @import statement near line" << m_lineNo; emit message(SeverityInfo, QString(QStringLiteral("Skipping potential HTML/JavaScript @import statement near line %1")).arg(m_lineNo)); return nullptr; } else if (!elementType.isEmpty()) return readEntryElement(elementType); else { qCWarning(LOG_KBIBTEX_IO) << "Element type after '@' is empty or invalid near line" << m_lineNo; emit message(SeverityError, QString(QStringLiteral("Element type after '@' is empty or invalid near line %1")).arg(m_lineNo)); return nullptr; } } else if (token == tUnknown && m_nextChar == QLatin1Char('%')) { /// do not complain about LaTeX-like comments, just eat them ++m_statistics.countCommentPercent; return readPlainCommentElement(QString()); } else if (token == tUnknown) { if (m_nextChar.isLetter()) { qCDebug(LOG_KBIBTEX_IO) << "Unknown character" << m_nextChar << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << ")" << ", treating as comment"; emit message(SeverityInfo, QString(QStringLiteral("Unknown character '%1' near line %2, treating as comment")).arg(m_nextChar).arg(m_lineNo)); } else if (m_nextChar.isPrint()) { qCDebug(LOG_KBIBTEX_IO) << "Unknown character" << m_nextChar << "(" << QString(QStringLiteral("0x%1")).arg(m_nextChar.unicode(), 4, 16, QLatin1Char('0')) << ") near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << ")" << ", treating as comment"; emit message(SeverityInfo, QString(QStringLiteral("Unknown character '%1' (0x%2) near line %3, treating as comment")).arg(m_nextChar).arg(m_nextChar.unicode(), 4, 16, QLatin1Char('0')).arg(m_lineNo)); } else { qCDebug(LOG_KBIBTEX_IO) << "Unknown character" << QString(QStringLiteral("0x%1")).arg(m_nextChar.unicode(), 4, 16, QLatin1Char('0')) << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << ")" << ", treating as comment"; emit message(SeverityInfo, QString(QStringLiteral("Unknown character 0x%1 near line %2, treating as comment")).arg(m_nextChar.unicode(), 4, 16, QLatin1Char('0')).arg(m_lineNo)); } ++m_statistics.countNoCommentQuote; return readPlainCommentElement(QString(m_prevChar) + m_nextChar); } if (token != tEOF) { qCWarning(LOG_KBIBTEX_IO) << "Don't know how to parse next token of type" << tokenidToString(token) << "in line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << ")" << endl; emit message(SeverityError, QString(QStringLiteral("Don't know how to parse next token of type %1 in line %2")).arg(tokenidToString(token)).arg(m_lineNo)); } return nullptr; } Comment *FileImporterBibTeX::readCommentElement() { if (!readCharUntil(QStringLiteral("{("))) return nullptr; return new Comment(EncoderLaTeX::instance().decode(readBracketString())); } Comment *FileImporterBibTeX::readPlainCommentElement(const QString &prefix) { QString result = EncoderLaTeX::instance().decode(prefix + readLine()); while (m_nextChar == QLatin1Char('\n') || m_nextChar == QLatin1Char('\r')) readChar(); while (!m_nextChar.isNull() && m_nextChar != QLatin1Char('@')) { const QChar nextChar = m_nextChar; const QString line = readLine(); while (m_nextChar == QLatin1Char('\n') || m_nextChar == QLatin1Char('\r')) readChar(); result.append(EncoderLaTeX::instance().decode((nextChar == QLatin1Char('%') ? QString() : QString(nextChar)) + line)); } if (result.startsWith(QStringLiteral("x-kbibtex"))) { qCWarning(LOG_KBIBTEX_IO) << "Plain comment element starts with 'x-kbibtex', this should not happen"; emit message(SeverityWarning, QStringLiteral("Plain comment element starts with 'x-kbibtex', this should not happen")); /// ignore special comments return nullptr; } return new Comment(result); } Macro *FileImporterBibTeX::readMacroElement() { Token token = nextToken(); while (token != tBracketOpen) { if (token == tEOF) { qCWarning(LOG_KBIBTEX_IO) << "Error in parsing macro near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Opening curly brace '{' expected"; emit message(SeverityError, QString(QStringLiteral("Error in parsing macro near line %1: Opening curly brace '{' expected")).arg(m_lineNo)); return nullptr; } token = nextToken(); } QString key = readSimpleString(); if (key.isEmpty()) { /// Cope with empty keys, /// duplicates are handled further below key = QStringLiteral("EmptyId"); } else if (!Encoder::containsOnlyAscii(key)) { /// Try to avoid non-ascii characters in ids const QString newKey = Encoder::instance().convertToPlainAscii(key); qCWarning(LOG_KBIBTEX_IO) << "Macro key" << key << "near line" << m_lineNo << "contains non-ASCII characters, converted to" << newKey; emit message(SeverityWarning, QString(QStringLiteral("Macro key '%1' near line %2 contains non-ASCII characters, converted to '%3'")).arg(key).arg(m_lineNo).arg(newKey)); key = newKey; } /// Check for duplicate entry ids, avoid collisions if (m_knownElementIds.contains(key)) { static const QString newIdPattern = QStringLiteral("%1-%2"); int idx = 2; QString newKey = newIdPattern.arg(key).arg(idx); while (m_knownElementIds.contains(newKey)) newKey = newIdPattern.arg(key).arg(++idx); qCDebug(LOG_KBIBTEX_IO) << "Duplicate macro key" << key << ", using replacement key" << newKey; emit message(SeverityWarning, QString(QStringLiteral("Duplicate macro key '%1', using replacement key '%2'")).arg(key, newKey)); key = newKey; } m_knownElementIds.insert(key); if (nextToken() != tAssign) { qCCritical(LOG_KBIBTEX_IO) << "Error in parsing macro" << key << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Assign symbol '=' expected"; emit message(SeverityError, QString(QStringLiteral("Error in parsing macro '%1' near line %2: Assign symbol '=' expected")).arg(key).arg(m_lineNo)); return nullptr; } Macro *macro = new Macro(key); do { bool isStringKey = false; QString text = readString(isStringKey); if (text.isNull()) { qCWarning(LOG_KBIBTEX_IO) << "Error in parsing macro" << key << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Could not read macro's text"; emit message(SeverityError, QString(QStringLiteral("Error in parsing macro '%1' near line %2: Could not read macro's text")).arg(key).arg(m_lineNo)); delete macro; } text = EncoderLaTeX::instance().decode(bibtexAwareSimplify(text)); if (isStringKey) macro->value().append(QSharedPointer(new MacroKey(text))); else macro->value().append(QSharedPointer(new PlainText(text))); token = nextToken(); } while (token == tDoublecross); return macro; } Preamble *FileImporterBibTeX::readPreambleElement() { Token token = nextToken(); while (token != tBracketOpen) { if (token == tEOF) { qCWarning(LOG_KBIBTEX_IO) << "Error in parsing preamble near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Opening curly brace '{' expected"; emit message(SeverityError, QString(QStringLiteral("Error in parsing preamble near line %1: Opening curly brace '{' expected")).arg(m_lineNo)); return nullptr; } token = nextToken(); } Preamble *preamble = new Preamble(); do { bool isStringKey = false; QString text = readString(isStringKey); if (text.isNull()) { qCWarning(LOG_KBIBTEX_IO) << "Error in parsing preamble near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Could not read preamble's text"; emit message(SeverityError, QString(QStringLiteral("Error in parsing preamble near line %1: Could not read preamble's text")).arg(m_lineNo)); delete preamble; return nullptr; } /// Remember: strings from preamble do not get encoded, /// may contain raw LaTeX commands and code text = bibtexAwareSimplify(text); if (isStringKey) preamble->value().append(QSharedPointer<MacroKey>(new MacroKey(text))); else preamble->value().append(QSharedPointer<PlainText>(new PlainText(text))); token = nextToken(); } while (token == tDoublecross); return preamble; } Entry *FileImporterBibTeX::readEntryElement(const QString &typeString) { Token token = nextToken(); while (token != tBracketOpen) { if (token == tEOF) { qCWarning(LOG_KBIBTEX_IO) << "Error in parsing entry near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Opening curly brace '{' expected"; emit message(SeverityError, QString(QStringLiteral("Error in parsing entry near line %1: Opening curly brace '{' expected")).arg(m_lineNo)); return nullptr; } token = nextToken(); } QString id = readSimpleString(QStringLiteral(",}"), true).trimmed(); if (id.isEmpty()) { if (m_nextChar == QLatin1Char(',') || m_nextChar == QLatin1Char('}')) { /// Cope with empty ids, /// duplicates are handled further below id = QStringLiteral("EmptyId"); } else { qCWarning(LOG_KBIBTEX_IO) << "Error in parsing entry near line" << m_lineNo << ":" << m_prevLine << endl << m_currentLine << "): Could not read entry id"; emit message(SeverityError, QString(QStringLiteral("Error in parsing preambentryle near line %1: Could not read entry id")).arg(m_lineNo)); return nullptr; } } else { if (id.contains(QStringLiteral("\\")) || id.contains(QStringLiteral("{"))) { const QString newId = EncoderLaTeX::instance().decode(id); qCWarning(LOG_KBIBTEX_IO) << "Entry id" << id << "near line" << m_lineNo << "contains backslashes or curly brackets, converted to" << newId; emit message(SeverityWarning, QString(QStringLiteral("Entry id '%1' near line %2 contains backslashes or curly brackets, converted to '%3'")).arg(id).arg(m_lineNo).arg(newId)); id = newId; } if (!Encoder::containsOnlyAscii(id)) { /// Try to avoid non-ascii characters in ids const QString newId = Encoder::instance().convertToPlainAscii(id); qCWarning(LOG_KBIBTEX_IO) << "Entry id" << id << "near line" << m_lineNo << "contains non-ASCII characters, converted to" << newId; emit message(SeverityWarning, QString(QStringLiteral("Entry id '%1' near line %2 contains non-ASCII characters, converted to '%3'")).arg(id).arg(m_lineNo).arg(newId)); id = newId; } } static const QVector<QChar> invalidIdCharacters = {QLatin1Char('{'), QLatin1Char('}'), QLatin1Char(',')}; for (const QChar &invalidIdCharacter : invalidIdCharacters) if (id.contains(invalidIdCharacter)) { qCWarning(LOG_KBIBTEX_IO) << "Entry id" << id << "near line" << m_lineNo << "contains invalid character" << invalidIdCharacter; emit message(SeverityError, QString(QStringLiteral("Entry id '%1' near line %2 contains invalid character '%3'")).arg(id).arg(m_lineNo).arg(invalidIdCharacter)); return nullptr; } /// Check for duplicate entry ids, avoid collisions if (m_knownElementIds.contains(id)) { static const QString newIdPattern = QStringLiteral("%1-%2"); int idx = 2; QString newId = newIdPattern.arg(id).arg(idx); while (m_knownElementIds.contains(newId)) newId = newIdPattern.arg(id).arg(++idx); qCDebug(LOG_KBIBTEX_IO) << "Duplicate id" << id << "near line" << m_lineNo << ", using replacement id" << newId; emit message(SeverityInfo, QString(QStringLiteral("Duplicate id '%1' near line %2, using replacement id '%3'")).arg(id).arg(m_lineNo).arg(newId)); id = newId; } m_knownElementIds.insert(id); Entry *entry = new Entry(BibTeXEntries::instance().format(typeString, m_keywordCasing), id); token = nextToken(); do { if (token == tBracketClose) break; else if (token == tEOF) { qCWarning(LOG_KBIBTEX_IO) << "Unexpected end of data in entry" << id << "near line" << m_lineNo << ":" << m_prevLine << endl << m_currentLine; emit message(SeverityError, QString(QStringLiteral("Unexpected end of data in entry '%1' near line %2")).arg(id).arg(m_lineNo)); delete entry; return nullptr; } else if (token != tComma) { if (m_nextChar.isLetter()) { qCWarning(LOG_KBIBTEX_IO) << "Error in parsing entry" << id << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Comma symbol ',' expected but got character" << m_nextChar << "(token" << tokenidToString(token) << ")"; emit message(SeverityError, QString(QStringLiteral("Error in parsing entry '%1' near line %2: Comma symbol ',' expected but got character '%3' (token %4)")).arg(id).arg(m_lineNo).arg(m_nextChar).arg(tokenidToString(token))); } else if (m_nextChar.isPrint()) { qCWarning(LOG_KBIBTEX_IO) << "Error in parsing entry" << id << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Comma symbol ',' expected but got character" << m_nextChar << "(" << QString(QStringLiteral("0x%1")).arg(m_nextChar.unicode(), 4, 16, QLatin1Char('0')) << ", token" << tokenidToString(token) << ")"; emit message(SeverityError, QString(QStringLiteral("Error in parsing entry '%1' near line %2: Comma symbol ',' expected but got character '%3' (0x%4, token %5)")).arg(id).arg(m_lineNo).arg(m_nextChar).arg(m_nextChar.unicode(), 4, 16, QLatin1Char('0')).arg(tokenidToString(token))); } else { qCWarning(LOG_KBIBTEX_IO) << "Error in parsing entry" << id << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Comma symbol (,) expected but got character" << QString(QStringLiteral("0x%1")).arg(m_nextChar.unicode(), 4, 16, QLatin1Char('0')) << "(token" << tokenidToString(token) << ")"; emit message(SeverityError, QString(QStringLiteral("Error in parsing entry '%1' near line %2: Comma symbol ',' expected but got character 0x%3 (token %4)")).arg(id).arg(m_lineNo).arg(m_nextChar.unicode(), 4, 16, QLatin1Char('0')).arg(tokenidToString(token))); } delete entry; return nullptr; } QString keyName = BibTeXFields::instance().format(readSimpleString(), m_keywordCasing); if (keyName.isEmpty()) { token = nextToken(); if (token == tBracketClose) { /// Most often it is the case that the previous line ended with a comma, /// implying that this entry continues, but instead it gets closed by /// a closing curly bracket. qCDebug(LOG_KBIBTEX_IO) << "Issue while parsing entry" << id << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Last key-value pair ended with a non-conformant comma, ignoring that"; emit message(SeverityInfo, QString(QStringLiteral("Issue while parsing entry '%1' near line %2: Last key-value pair ended with a non-conformant comma, ignoring that")).arg(id).arg(m_lineNo)); break; } else { /// Something looks terribly wrong qCWarning(LOG_KBIBTEX_IO) << "Error in parsing entry" << id << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Closing curly bracket expected, but found" << tokenidToString(token); emit message(SeverityError, QString(QStringLiteral("Error in parsing entry '%1' near line %2: Closing curly bracket expected, but found %3")).arg(id).arg(m_lineNo).arg(tokenidToString(token))); delete entry; return nullptr; } } /// Try to avoid non-ascii characters in keys const QString newkeyName = Encoder::instance().convertToPlainAscii(keyName); if (newkeyName != keyName) { qCWarning(LOG_KBIBTEX_IO) << "Field name " << keyName << "near line" << m_lineNo << "contains non-ASCII characters, converted to" << newkeyName; emit message(SeverityWarning, QString(QStringLiteral("Field name '%1' near line %2 contains non-ASCII characters, converted to '%3'")).arg(keyName).arg(m_lineNo).arg(newkeyName)); keyName = newkeyName; } token = nextToken(); if (token != tAssign) { qCWarning(LOG_KBIBTEX_IO) << "Error in parsing entry" << id << ", field name" << keyName << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "): Assign symbol '=' expected after field name"; emit message(SeverityError, QString(QStringLiteral("Error in parsing entry '%1', field name '%2' near line %3: Assign symbol '=' expected after field name")).arg(id, keyName).arg(m_lineNo)); delete entry; return nullptr; } Value value; /// check for duplicate fields if (entry->contains(keyName)) { if (keyName.toLower() == Entry::ftKeywords || keyName.toLower() == Entry::ftUrl) { /// Special handling of keywords and URLs: instead of using fallback names /// like "keywords2", "keywords3", ..., append new keywords to /// already existing keyword value value = entry->value(keyName); } else if (m_keysForPersonDetection.contains(keyName.toLower())) { /// Special handling of authors and editors: instead of using fallback names /// like "author2", "author3", ..., append new authors to /// already existing author value value = entry->value(keyName); } else { int i = 2; QString appendix = QString::number(i); while (entry->contains(keyName + appendix)) { ++i; appendix = QString::number(i); } qCDebug(LOG_KBIBTEX_IO) << "Entry" << id << "already contains a key" << keyName << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << "), using" << (keyName + appendix); emit message(SeverityWarning, QString(QStringLiteral("Entry '%1' already contains a key '%2' near line %4, using '%3'")).arg(id, keyName, keyName + appendix).arg(m_lineNo)); keyName += appendix; } } token = readValue(value, keyName); if (token != tBracketClose && token != tComma) { qCWarning(LOG_KBIBTEX_IO) << "Failed to read value in entry" << id << ", field name" << keyName << "near line" << m_lineNo << "(" << m_prevLine << endl << m_currentLine << ")"; emit message(SeverityError, QString(QStringLiteral("Failed to read value in entry '%1', field name '%2' near line %3")).arg(id, keyName).arg(m_lineNo)); delete entry; return nullptr; } entry->insert(keyName, value); } while (true); return entry; } FileImporterBibTeX::Token FileImporterBibTeX::nextToken() { if (!skipWhiteChar()) { /// Some error occurred while reading from data stream return tEOF; } Token result = tUnknown; switch (m_nextChar.toLatin1()) { case '@': result = tAt; break; case '{': case '(': result = tBracketOpen; break; case '}': case ')': result = tBracketClose; break; case ',': result = tComma; break; case '=': result = tAssign; break; case '#': result = tDoublecross; break; default: if (m_textStream->atEnd()) result = tEOF; } if (m_nextChar != QLatin1Char('%')) { /// Unclean solution, but necessary for comments /// that have a percent sign as a prefix readChar(); } return result; } QString FileImporterBibTeX::readString(bool &isStringKey) { /// Most often it is not a string key isStringKey = false; if (!skipWhiteChar()) { /// Some error occurred while reading from data stream return QString::null; } switch (m_nextChar.toLatin1()) { case '{': case '(': { ++m_statistics.countCurlyBrackets; const QString result = readBracketString(); return result; } case '"': { ++m_statistics.countQuotationMarks; const QString result = readQuotedString(); return result; } default: isStringKey = true; const QString result = readSimpleString(); return result; } } QString FileImporterBibTeX::readSimpleString(const QString &until, const bool readNestedCurlyBrackets) { static const QString extraAlphaNumChars = QString(QStringLiteral("?'`-_:.+/$\\\"&")); QString result; ///< 'result' is Null on purpose: simple strings cannot be empty in contrast to e.g. quoted strings if (!skipWhiteChar()) { /// Some error occurred while reading from data stream return QString::null; } QChar prevChar = QChar(0x00); while (!m_nextChar.isNull()) { if (readNestedCurlyBrackets && m_nextChar == QLatin1Char('{') && prevChar != QLatin1Char('\\')) { int depth = 1; while (depth > 0) { result.append(m_nextChar); prevChar = m_nextChar; if (!readChar()) return result; if (m_nextChar == QLatin1Char('{') && prevChar != QLatin1Char('\\')) ++depth; else if (m_nextChar == QLatin1Char('}') && prevChar != QLatin1Char('\\')) --depth; } result.append(m_nextChar); prevChar = m_nextChar; if (!readChar()) return result; } const ushort nextCharUnicode = m_nextChar.unicode(); if (!until.isEmpty()) { /// Variable "until" has user-defined value if (m_nextChar == QLatin1Char('\n') || m_nextChar == QLatin1Char('\r') || until.contains(m_nextChar)) { /// Force break on line-breaks or if one of the "until" chars has been read break; } else { /// Append read character to final result result.append(m_nextChar); } } else if ((nextCharUnicode >= (ushort)'a' && nextCharUnicode <= (ushort)'z') || (nextCharUnicode >= (ushort)'A' && nextCharUnicode <= (ushort)'Z') || (nextCharUnicode >= (ushort)'0' && nextCharUnicode <= (ushort)'9') || extraAlphaNumChars.contains(m_nextChar)) { /// Accept default set of alpha-numeric characters result.append(m_nextChar); } else break; prevChar = m_nextChar; if (!readChar()) break; } return result; } QString FileImporterBibTeX::readQuotedString() { QString result(0, QChar()); ///< Construct an empty but non-null string Q_ASSERT_X(m_nextChar == QLatin1Char('"'), "QString FileImporterBibTeX::readQuotedString()", "m_nextChar is not '\"'"); if (!readChar()) return QString::null; while (!m_nextChar.isNull()) { if (m_nextChar == QLatin1Char('"') && m_prevChar != QLatin1Char('\\') && m_prevChar != QLatin1Char('{')) break; else result.append(m_nextChar); if (!readChar()) return QString::null; } if (!readChar()) return QString::null; /// Remove protection around quotation marks result.replace(QStringLiteral("{\"}"), QStringLiteral("\"")); return result; } QString FileImporterBibTeX::readBracketString() { static const QChar backslash = QLatin1Char('\\'); QString result(0, QChar()); ///< Construct an empty but non-null string const QChar openingBracket = m_nextChar; const QChar closingBracket = openingBracket == QLatin1Char('{') ? QLatin1Char('}') : (openingBracket == QLatin1Char('(') ? QLatin1Char(')') : QChar()); Q_ASSERT_X(!closingBracket.isNull(), "QString FileImporterBibTeX::readBracketString()", "openingBracket==m_nextChar is neither '{' nor '('"); int counter = 1; if (!readChar()) return QString::null; while (!m_nextChar.isNull()) { if (m_nextChar == openingBracket && m_prevChar != backslash) ++counter; else if (m_nextChar == closingBracket && m_prevChar != backslash) --counter; if (counter == 0) { break; } else result.append(m_nextChar); if (!readChar()) return QString::null; } if (!readChar()) return QString::null; return result; } FileImporterBibTeX::Token FileImporterBibTeX::readValue(Value &value, const QString &key) { Token token = tUnknown; const QString iKey = key.toLower(); static const QSet<QString> verbatimKeys {Entry::ftColor.toLower(), Entry::ftCrossRef.toLower(), Entry::ftXData.toLower()}; do { bool isStringKey = false; const QString rawText = readString(isStringKey); if (rawText.isNull()) return tEOF; QString text = EncoderLaTeX::instance().decode(rawText); /// for all entries except for abstracts ... if (iKey != Entry::ftAbstract && !(iKey.startsWith(Entry::ftUrl) && !iKey.startsWith(Entry::ftUrlDate)) && !iKey.startsWith(Entry::ftLocalFile) && !iKey.startsWith(Entry::ftFile)) { /// ... remove redundant spaces including newlines text = bibtexAwareSimplify(text); } /// abstracts will keep their formatting (regarding line breaks) /// as requested by Thomas Jensch via mail (20 October 2010) /// Maintain statistics on if (book) titles are protected /// by surrounding curly brackets if (iKey == Entry::ftTitle || iKey == Entry::ftBookTitle) { if (text[0] == QLatin1Char('{') && text[text.length() - 1] == QLatin1Char('}')) ++m_statistics.countProtectedTitle; else ++m_statistics.countUnprotectedTitle; } if (m_keysForPersonDetection.contains(iKey)) { if (isStringKey) value.append(QSharedPointer<MacroKey>(new MacroKey(text))); else { CommaContainment comma = ccContainsComma; parsePersonList(text, value, &comma, m_lineNo, this); /// Update statistics on name formatting if (comma == ccContainsComma) ++m_statistics.countLastNameFirst; else ++m_statistics.countFirstNameFirst; } } else if (iKey == Entry::ftPages) { static const QRegularExpression rangeInAscii(QStringLiteral("\\s*--?\\s*")); text.replace(rangeInAscii, QChar(0x2013)); if (isStringKey) value.append(QSharedPointer<MacroKey>(new MacroKey(text))); else value.append(QSharedPointer<PlainText>(new PlainText(text))); } else if ((iKey.startsWith(Entry::ftUrl) && !iKey.startsWith(Entry::ftUrlDate)) || iKey.startsWith(Entry::ftLocalFile) || iKey.startsWith(Entry::ftFile) || iKey == QStringLiteral("ee") || iKey == QStringLiteral("biburl")) { if (isStringKey) value.append(QSharedPointer<MacroKey>(new MacroKey(text))); else { /// Assumption: in fields like Url or LocalFile, file names are separated by ; static const QRegularExpression semicolonSpace = QRegularExpression(QStringLiteral("[;]\\s*")); const QStringList fileList = rawText.split(semicolonSpace, QString::SkipEmptyParts); for (const QString &filename : fileList) { value.append(QSharedPointer<VerbatimText>(new VerbatimText(filename))); } } } else if (iKey.startsWith(Entry::ftFile)) { if (isStringKey) value.append(QSharedPointer<MacroKey>(new MacroKey(text))); else { /// Assumption: this field was written by Mendeley, which uses /// a very strange format for file names: /// :C$\backslash$:/Users/BarisEvrim/Documents/Mendeley Desktop/GeversPAMI10.pdf:pdf /// :: /// :Users/Fred/Library/Application Support/Mendeley Desktop/Downloaded/Hasselman et al. - 2011 - (Still) Growing Up What should we be a realist about in the cognitive and behavioural sciences Abstract.pdf:pdf const QRegularExpressionMatch match = KBibTeX::mendeleyFileRegExp.match(rawText); if (match.hasMatch()) { static const QString backslashLaTeX = QStringLiteral("$\\backslash$"); QString filename = match.captured(1).remove(backslashLaTeX); if (filename.startsWith(QStringLiteral("home/")) || filename.startsWith(QStringLiteral("Users/"))) { /// Mendeley doesn't have a slash at the beginning of absolute paths, /// so, insert one /// See bug 19833, comment 5: https://gna.org/bugs/index.php?19833#comment5 filename.prepend(QLatin1Char('/')); } value.append(QSharedPointer<VerbatimText>(new VerbatimText(filename))); } else value.append(QSharedPointer<VerbatimText>(new VerbatimText(text))); } } else if (iKey == Entry::ftMonth) { if (isStringKey) { static const QRegularExpression monthThreeChars(QStringLiteral("^[a-z]{3}"), QRegularExpression::CaseInsensitiveOption); if (monthThreeChars.match(text).hasMatch()) text = text.left(3).toLower(); value.append(QSharedPointer<MacroKey>(new MacroKey(text))); } else value.append(QSharedPointer<PlainText>(new PlainText(text))); } else if (iKey.startsWith(Entry::ftDOI)) { if (isStringKey) value.append(QSharedPointer<MacroKey>(new MacroKey(text))); else { /// Take care of "; " which separates multiple DOIs, but which may baffle the regexp QString preprocessedText = rawText; preprocessedText.replace(QStringLiteral("; "), QStringLiteral(" ")); /// Extract everything that looks like a DOI using a regular expression, /// ignore everything else QRegularExpressionMatchIterator doiRegExpMatchIt = KBibTeX::doiRegExp.globalMatch(preprocessedText); while (doiRegExpMatchIt.hasNext()) { const QRegularExpressionMatch doiRegExpMatch = doiRegExpMatchIt.next(); value.append(QSharedPointer<VerbatimText>(new VerbatimText(doiRegExpMatch.captured(0)))); } } } else if (iKey == Entry::ftKeywords) { if (isStringKey) value.append(QSharedPointer<MacroKey>(new MacroKey(text))); else { char splitChar; const QList<QSharedPointer<Keyword> > keywords = splitKeywords(text, &splitChar); for (const auto &keyword : keywords) value.append(keyword); /// Memorize (some) split characters for later use /// (e.g. when writing file again) if (splitChar == ';') m_statistics.mostRecentListSeparator = QStringLiteral("; "); else if (splitChar == ',') m_statistics.mostRecentListSeparator = QStringLiteral(", "); } } else if (verbatimKeys.contains(iKey)) { if (isStringKey) value.append(QSharedPointer<MacroKey>(new MacroKey(text))); else value.append(QSharedPointer<VerbatimText>(new VerbatimText(rawText))); } else { if (isStringKey) value.append(QSharedPointer<MacroKey>(new MacroKey(text))); else value.append(QSharedPointer<PlainText>(new PlainText(text))); } token = nextToken(); } while (token == tDoublecross); return token; } bool FileImporterBibTeX::readChar() { /// Memorize previous char m_prevChar = m_nextChar; if (m_textStream->atEnd()) { /// At end of data stream m_nextChar = QChar::Null; return false; } /// Read next char *m_textStream >> m_nextChar; /// Test for new line if (m_nextChar == QLatin1Char('\n')) { /// Update variables tracking line numbers and line content ++m_lineNo; m_prevLine = m_currentLine; m_currentLine.clear(); } else { /// Add read char to current line m_currentLine.append(m_nextChar); } return true; } bool FileImporterBibTeX::readCharUntil(const QString &until) { Q_ASSERT_X(!until.isEmpty(), "bool FileImporterBibTeX::readCharUntil(const QString &until)", "\"until\" is empty or invalid"); bool result = true; while (!until.contains(m_nextChar) && (result = readChar())); return result; } bool FileImporterBibTeX::skipWhiteChar() { bool result = true; while ((m_nextChar.isSpace() || m_nextChar == QLatin1Char('\t') || m_nextChar == QLatin1Char('\n') || m_nextChar == QLatin1Char('\r')) && result) result = readChar(); return result; } QString FileImporterBibTeX::readLine() { QString result; while (m_nextChar != QLatin1Char('\n') && m_nextChar != QLatin1Char('\r') && readChar()) result.append(m_nextChar); return result; } QList<QSharedPointer<Keyword> > FileImporterBibTeX::splitKeywords(const QString &text, char *usedSplitChar) { QList<QSharedPointer<Keyword> > result; static const QHash<char, QRegularExpression> splitAlong = { {'\n', QRegularExpression(QStringLiteral("\\s*\n\\s*"))}, {';', QRegularExpression(QStringLiteral("\\s*;\\s*"))}, {',', QRegularExpression(QString("\\s*,\\s*"))} }; if (usedSplitChar != nullptr) *usedSplitChar = '\0'; for (auto it = splitAlong.constBegin(); it != splitAlong.constEnd(); ++it) { /// check if character is contained in text (should be cheap to test) if (text.contains(QLatin1Char(it.key()))) { /// split text along a pattern like spaces-splitchar-spaces /// extract keywords static const QRegularExpression unneccessarySpacing(QStringLiteral("[ \n\r\t]+")); const QStringList keywords = text.split(it.value(), QString::SkipEmptyParts).replaceInStrings(unneccessarySpacing, QStringLiteral(" ")); /// build QList of Keyword objects from keywords for (const QString &keyword : keywords) { result.append(QSharedPointer<Keyword>(new Keyword(keyword))); } /// Memorize (some) split characters for later use /// (e.g. when writing file again) if (usedSplitChar != nullptr) *usedSplitChar = it.key(); /// no more splits necessary break; } } /// no split was performed, so whole text must be a single keyword if (result.isEmpty()) result.append(QSharedPointer<Keyword>(new Keyword(text))); return result; } QList<QSharedPointer<Person> > FileImporterBibTeX::splitNames(const QString &text, const int line_number, QObject *parent) { /// Case: Smith, John and Johnson, Tim /// Case: Smith, John and Fulkerson, Ford and Johnson, Tim /// Case: Smith, John, Fulkerson, Ford, and Johnson, Tim /// Case: John Smith and Tim Johnson /// Case: John Smith and Ford Fulkerson and Tim Johnson /// Case: Smith, John, Johnson, Tim /// Case: Smith, John, Fulkerson, Ford, Johnson, Tim /// Case: John Smith, Tim Johnson /// Case: John Smith, Tim Johnson, Ford Fulkerson /// Case: Smith, John ; Johnson, Tim ; Fulkerson, Ford (IEEE Xplore) /// German case: Robert A. Gehring und Bernd Lutterbeck QString internalText = text; /// Remove invalid characters such as dots or (double) daggers for footnotes static const QList<QChar> invalidChars {QChar(0x00b7), QChar(0x2020), QChar(0x2217), QChar(0x2021), QChar(0x002a), QChar(0x21d1) /** Upwards double arrow */}; for (const auto &invalidChar : invalidChars) /// Replacing daggers with commas ensures that they act as persons' names separator internalText = internalText.replace(invalidChar, QChar(',')); /// Remove numbers to footnotes static const QRegularExpression numberFootnoteRegExp(QStringLiteral("(\\w)\\d+\\b")); internalText = internalText.replace(numberFootnoteRegExp, QStringLiteral("\\1")); /// Remove academic degrees static const QRegularExpression academicDegreesRegExp(QStringLiteral("(,\\s*)?(MA|PhD)\\b")); internalText = internalText.remove(academicDegreesRegExp); /// Remove email addresses static const QRegularExpression emailAddressRegExp(QStringLiteral("\\b[a-zA-Z0-9][a-zA-Z0-9._-]+[a-zA-Z0-9]@[a-z0-9][a-z0-9-]*([.][a-z0-9-]+)*([.][a-z]+)+\\b")); internalText = internalText.remove(emailAddressRegExp); /// Split input string into tokens which are either name components (first or last name) /// or full names (composed of first and last name), depending on the input string's structure static const QRegularExpression split(QStringLiteral("\\s*([,]+|[,]*\\b[au]nd\\b|[;]|&|\\n|\\s{4,})\\s*")); const QStringList authorTokenList = internalText.split(split, QString::SkipEmptyParts); bool containsSpace = true; for (QStringList::ConstIterator it = authorTokenList.constBegin(); containsSpace && it != authorTokenList.constEnd(); ++it) containsSpace = (*it).contains(QChar(' ')); QList<QSharedPointer<Person> > result; result.reserve(authorTokenList.size()); if (containsSpace) { /// Tokens look like "John Smith" for (const QString &authorToken : authorTokenList) { QSharedPointer<Person> person = personFromString(authorToken, nullptr, line_number, parent); if (!person.isNull()) result.append(person); } } else { /// Tokens look like "Smith" or "John" /// Assumption: two consecutive tokens form a name for (QStringList::ConstIterator it = authorTokenList.constBegin(); it != authorTokenList.constEnd(); ++it) { QString lastname = *it; ++it; if (it != authorTokenList.constEnd()) { lastname += QStringLiteral(", ") + (*it); QSharedPointer<Person> person = personFromString(lastname, nullptr, line_number, parent); if (!person.isNull()) result.append(person); } else break; } } return result; } void FileImporterBibTeX::parsePersonList(const QString &text, Value &value, const int line_number, QObject *parent) { parsePersonList(text, value, nullptr, line_number, parent); } void FileImporterBibTeX::parsePersonList(const QString &text, Value &value, CommaContainment *comma, const int line_number, QObject *parent) { static const QString tokenAnd = QStringLiteral("and"); static const QString tokenOthers = QStringLiteral("others"); static QStringList tokens; contextSensitiveSplit(text, tokens); if (tokens.count() > 0) { if (tokens[0] == tokenAnd) { qCInfo(LOG_KBIBTEX_IO) << "Person list starts with" << tokenAnd << "near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Person list starts with 'and' near line %1")).arg(line_number))); } else if (tokens.count() > 1 && tokens[tokens.count() - 1] == tokenAnd) { qCInfo(LOG_KBIBTEX_IO) << "Person list ends with" << tokenAnd << "near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Person list ends with 'and' near line %1")).arg(line_number))); } if (tokens[0] == tokenOthers) { qCInfo(LOG_KBIBTEX_IO) << "Person list starts with" << tokenOthers << "near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Person list starts with 'others' near line %1")).arg(line_number))); } else if (tokens[tokens.count() - 1] == tokenOthers && (tokens.count() < 3 || tokens[tokens.count() - 2] != tokenAnd)) { qCInfo(LOG_KBIBTEX_IO) << "Person list ends with" << tokenOthers << "but is not preceeded with name and" << tokenAnd << "near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Person list ends with 'others' but is not preceeded with name and 'and' near line %1")).arg(line_number))); } } int nameStart = 0; QString prevToken; for (int i = 0; i < tokens.count(); ++i) { if (tokens[i] == tokenAnd) { if (prevToken == tokenAnd) { qCInfo(LOG_KBIBTEX_IO) << "Two subsequent" << tokenAnd << "found in person list near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Two subsequent 'and' found in person list near line %1")).arg(line_number))); } else if (nameStart < i) { const QSharedPointer<Person> person = personFromTokenList(tokens.mid(nameStart, i - nameStart), comma, line_number, parent); if (!person.isNull()) value.append(person); else { qCInfo(LOG_KBIBTEX_IO) << "Text" << tokens.mid(nameStart, i - nameStart).join(' ') << "does not form a name near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Text '%1' does not form a name near line %2")).arg(tokens.mid(nameStart, i - nameStart).join(' ')).arg(line_number))); } } else { qCInfo(LOG_KBIBTEX_IO) << "Found" << tokenAnd << "but no name before it near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Found 'and' but no name before it near line %1")).arg(line_number))); } nameStart = i + 1; } else if (tokens[i] == tokenOthers) { if (i < tokens.count() - 1) { qCInfo(LOG_KBIBTEX_IO) << "Special word" << tokenOthers << "found before last position in person name near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Special word 'others' found before last position in person name near line %1")).arg(line_number))); } else value.append(QSharedPointer<PlainText>(new PlainText(QStringLiteral("others")))); nameStart = tokens.count() + 1; } prevToken = tokens[i]; } if (nameStart < tokens.count()) { const QSharedPointer<Person> person = personFromTokenList(tokens.mid(nameStart), comma, line_number, parent); if (!person.isNull()) value.append(person); else { qCInfo(LOG_KBIBTEX_IO) << "Text" << tokens.mid(nameStart).join(' ') << "does not form a name near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Text '%1' does not form a name near line %2")).arg(tokens.mid(nameStart).join(' ')).arg(line_number))); } } } QSharedPointer<Person> FileImporterBibTeX::personFromString(const QString &name, const int line_number, QObject *parent) { return personFromString(name, nullptr, line_number, parent); } QSharedPointer<Person> FileImporterBibTeX::personFromString(const QString &name, CommaContainment *comma, const int line_number, QObject *parent) { static QStringList tokens; contextSensitiveSplit(name, tokens); return personFromTokenList(tokens, comma, line_number, parent); } QSharedPointer<Person> FileImporterBibTeX::personFromTokenList(const QStringList &tokens, CommaContainment *comma, const int line_number, QObject *parent) { if (comma != nullptr) *comma = ccNoComma; /// Simple case: provided list of tokens is empty, return invalid Person if (tokens.isEmpty()) return QSharedPointer<Person>(); /** * Sequence of tokens may contain somewhere a comma, like * "Tuckwell," "Peter". In this case, fill two string lists: * one with tokens before the comma, one with tokens after the * comma (excluding the comma itself). Example: * partA = ( "Tuckwell" ); partB = ( "Peter" ); partC = ( "Jr." ) * If a comma was found, boolean variable gotComma is set. */ QStringList partA, partB, partC; int commaCount = 0; for (const QString &token : tokens) { /// Position where comma was found, or -1 if no comma in token int p = -1; if (commaCount < 2) { /// Only check if token contains comma /// if no comma was found before int bracketCounter = 0; for (int i = 0; i < token.length(); ++i) { /// Consider opening curly brackets if (token[i] == QChar('{')) ++bracketCounter; /// Consider closing curly brackets else if (token[i] == QChar('}')) --bracketCounter; /// Only if outside any open curly bracket environments /// consider comma characters else if (bracketCounter == 0 && token[i] == QChar(',')) { /// Memorize comma's position and break from loop p = i; break; } else if (bracketCounter < 0) { /// Should never happen: more closing brackets than opening ones qCWarning(LOG_KBIBTEX_IO) << "Opening and closing brackets do not match near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Opening and closing brackets do not match near line %1")).arg(line_number))); } } } if (p >= 0) { if (commaCount == 0) { if (p > 0) partA.append(token.left(p)); if (p < token.length() - 1) partB.append(token.mid(p + 1)); } else if (commaCount == 1) { if (p > 0) partB.append(token.left(p)); if (p < token.length() - 1) partC.append(token.mid(p + 1)); } ++commaCount; } else if (commaCount == 0) partA.append(token); else if (commaCount == 1) partB.append(token); else if (commaCount == 2) partC.append(token); } if (commaCount > 0) { if (comma != nullptr) *comma = ccContainsComma; return QSharedPointer<Person>(new Person(partC.isEmpty() ? partB.join(QChar(' ')) : partC.join(QChar(' ')), partA.join(QChar(' ')), partC.isEmpty() ? QString() : partB.join(QChar(' ')))); } /** * PubMed uses a special writing style for names, where the * last name is followed by single capital letters, each being * the first letter of each first name. Example: Tuckwell P H * So, check how many single capital letters are at the end of * the given token list */ partA.clear(); partB.clear(); bool singleCapitalLetters = true; QStringList::ConstIterator it = tokens.constEnd(); while (it != tokens.constBegin()) { --it; if (singleCapitalLetters && it->length() == 1 && it->at(0).isUpper()) partB.prepend(*it); else { singleCapitalLetters = false; partA.prepend(*it); } } if (!partB.isEmpty()) { /// Name was actually given in PubMed format return QSharedPointer<Person>(new Person(partB.join(QChar(' ')), partA.join(QChar(' ')))); } /** * Normally, the last upper case token in a name is the last name * (last names consisting of multiple space-separated parts *have* * to be protected by {...}), but some languages have fill words * in lower case belonging to the last name as well (example: "van"). * In addition, some languages have capital case letters as well * (example: "Di Cosmo"). * Exception: Special keywords such as "Jr." can be appended to the * name, not counted as part of the last name. */ partA.clear(); partB.clear(); partC.clear(); static const QSet<QString> capitalCaseLastNameFragments {QStringLiteral("Di")}; it = tokens.constEnd(); while (it != tokens.constBegin()) { --it; if (partB.isEmpty() && (it->toLower().startsWith(QStringLiteral("jr")) || it->toLower().startsWith(QStringLiteral("sr")) || it->toLower().startsWith(QStringLiteral("iii")))) /// handle name suffices like "Jr" or "III." partC.prepend(*it); else if (partB.isEmpty() || it->at(0).isLower() || capitalCaseLastNameFragments.contains(*it)) partB.prepend(*it); else partA.prepend(*it); } if (!partB.isEmpty()) { /// Name was actually like "Peter Ole van der Tuckwell", /// split into "Peter Ole" and "van der Tuckwell" return QSharedPointer<Person>(new Person(partA.join(QChar(' ')), partB.join(QChar(' ')), partC.isEmpty() ? QString() : partC.join(QChar(' ')))); } qCWarning(LOG_KBIBTEX_IO) << "Don't know how to handle name" << tokens.join(QLatin1Char(' ')) << "near line" << line_number; if (parent != nullptr) QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Don't know how to handle name '%1' near line %2")).arg(tokens.join(QLatin1Char(' '))).arg(line_number))); return QSharedPointer<Person>(); } void FileImporterBibTeX::contextSensitiveSplit(const QString &text, QStringList &segments) { int bracketCounter = 0; ///< keep track of opening and closing brackets: {...} QString buffer; int len = text.length(); segments.clear(); ///< empty list for results before proceeding for (int pos = 0; pos < len; ++pos) { if (text[pos] == '{') ++bracketCounter; else if (text[pos] == '}') --bracketCounter; if (text[pos].isSpace() && bracketCounter == 0) { if (!buffer.isEmpty()) { segments.append(buffer); buffer.clear(); } } else buffer.append(text[pos]); } if (!buffer.isEmpty()) segments.append(buffer); } QString FileImporterBibTeX::bibtexAwareSimplify(const QString &text) { QString result; int i = 0; /// Consume initial spaces ... while (i < text.length() && text[i].isSpace()) ++i; /// ... but if there have been spaces (i.e. i>0), then record a single space only if (i > 0) result.append(QStringLiteral(" ")); while (i < text.length()) { /// Consume non-spaces while (i < text.length() && !text[i].isSpace()) { result.append(text[i]); ++i; } /// String may end with a non-space if (i >= text.length()) break; /// Consume spaces, ... while (i < text.length() && text[i].isSpace()) ++i; /// ... but record only a single space result.append(QStringLiteral(" ")); } return result; } bool FileImporterBibTeX::evaluateParameterComments(QTextStream *textStream, const QString &line, File *file) { /// Assertion: variable "line" is all lower-case /** check if this file requests a special encoding */ if (line.startsWith(QStringLiteral("@comment{x-kbibtex-encoding=")) && line.endsWith(QLatin1Char('}'))) { const QString encoding = line.mid(28, line.length() - 29).toLower(); textStream->setCodec(encoding.toLower() == QStringLiteral("latex") ? "us-ascii" : encoding.toLatin1()); file->setProperty(File::Encoding, encoding.toLower() == QStringLiteral("latex") ? encoding : QString::fromLatin1(textStream->codec()->name())); return true; } else if (line.startsWith(QStringLiteral("@comment{x-kbibtex-personnameformatting=")) && line.endsWith(QLatin1Char('}'))) { // TODO usage of x-kbibtex-personnameformatting is deprecated, // as automatic detection is in place QString personNameFormatting = line.mid(40, line.length() - 41); file->setProperty(File::NameFormatting, personNameFormatting); return true; } else if (line.startsWith(QStringLiteral("% encoding:"))) { /// Interprete JabRef's encoding information QString encoding = line.mid(12); qCDebug(LOG_KBIBTEX_IO) << "Using JabRef's encoding:" << encoding; textStream->setCodec(encoding.toLatin1()); file->setProperty(File::Encoding, QString::fromLatin1(textStream->codec()->name())); return true; } return false; } QString FileImporterBibTeX::tokenidToString(Token token) { switch (token) { case tAt: return QString(QStringLiteral("At")); case tBracketClose: return QString(QStringLiteral("BracketClose")); case tBracketOpen: return QString(QStringLiteral("BracketOpen")); case tAlphaNumText: return QString(QStringLiteral("AlphaNumText")); case tAssign: return QString(QStringLiteral("Assign")); case tComma: return QString(QStringLiteral("Comma")); case tDoublecross: return QString(QStringLiteral("Doublecross")); case tEOF: return QString(QStringLiteral("EOF")); case tUnknown: return QString(QStringLiteral("Unknown")); default: return QString(QStringLiteral("<Unknown>")); } } void FileImporterBibTeX::setCommentHandling(CommentHandling commentHandling) { m_commentHandling = commentHandling; } diff --git a/src/io/fileimporterbibtex.h b/src/io/fileimporterbibtex.h index a3e38564..961b5fbb 100644 --- a/src/io/fileimporterbibtex.h +++ b/src/io/fileimporterbibtex.h @@ -1,171 +1,171 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * 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 <https://www.gnu.org/licenses/>. * ***************************************************************************/ #ifndef KBIBTEX_IO_FILEIMPORTERBIBTEX_H #define KBIBTEX_IO_FILEIMPORTERBIBTEX_H -#ifdef HAVE_KF5 -#include "kbibtexio_export.h" -#endif // HAVE_KF5 - #include <QTextStream> #include <QSharedPointer> #include <QStringList> #include <QSet> -#include "kbibtex.h" -#include "fileimporter.h" +#include <KBibTeX> +#include <FileImporter> + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 class Element; class Comment; class Preamble; class Macro; class Entry; class Value; class Keyword; /** * This class reads a BibTeX file from a QIODevice (such as a QFile) and * creates a File object which can be used to access the BibTeX elements. * @see File * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXIO_EXPORT FileImporterBibTeX : public FileImporter { Q_OBJECT public: enum CommentHandling {IgnoreComments = 0, KeepComments = 1}; /** * Creates an importer class to read a BibTeX file. */ explicit FileImporterBibTeX(QObject *parent); /** * Read data from the given device and construct a File object holding * the bibliographic data. * @param iodevice opened QIODevice instance ready to read from * @return @c valid File object with elements, @c NULL if reading failed for some reason */ File *load(QIODevice *iodevice) override; /** TODO */ static bool guessCanDecode(const QString &text); /** * Split a list of keyword separated by ";" or "," into single Keyword objects. * @param text Text containing the keyword list * @return A list of Keyword object containing the keywords * @see Keyword */ static QList<QSharedPointer<Keyword> > splitKeywords(const QString &text, char *usedSplitChar = nullptr); /** * Split a list of names into single Person objects. * Examples: "Smith, John, Fulkerson, Ford, and Johnson, Tim" * or "John Smith and Tim Johnson" * @param text Text containing the persons' names * @return A list of Person object containing the names * @see Person */ static QList<QSharedPointer<Person> > splitNames(const QString &text, const int line_number = 1, QObject *parent = nullptr); /** * Split a person's name into its parts and construct a Person object from them. * This is a functions specialized on the properties of (La)TeX code considering * e.g. curly brackets. * @param name The persons name * @return A Person object containing the name * @see Person */ static QSharedPointer<Person> personFromString(const QString &name, const int line_number = 1, QObject *parent = nullptr); static void parsePersonList(const QString &text, Value &value, const int line_number = 1, QObject *parent = nullptr); void setCommentHandling(CommentHandling commentHandling); public slots: void cancel() override; private: enum Token { tAt = 1, tBracketOpen = 2, tBracketClose = 3, tAlphaNumText = 4, tComma = 5, tAssign = 6, tDoublecross = 7, tEOF = 0xffff, tUnknown = -1 }; enum CommaContainment { ccNoComma = 0, ccContainsComma = 1 }; struct { int countCurlyBrackets, countQuotationMarks; int countFirstNameFirst, countLastNameFirst; int countNoCommentQuote, countCommentPercent, countCommentCommand; int countProtectedTitle, countUnprotectedTitle; QString mostRecentListSeparator; } m_statistics; bool m_cancelFlag; QTextStream *m_textStream; CommentHandling m_commentHandling; KBibTeX::Casing m_keywordCasing; QStringList m_keysForPersonDetection; QSet<QString> m_knownElementIds; /// low-level character operations QChar m_prevChar, m_nextChar; unsigned int m_lineNo; QString m_prevLine, m_currentLine; bool readChar(); bool readCharUntil(const QString &until); bool skipWhiteChar(); QString readLine(); /// high-level parsing functions Comment *readCommentElement(); Comment *readPlainCommentElement(const QString &prefix); Macro *readMacroElement(); Preamble *readPreambleElement(); Entry *readEntryElement(const QString &typeString); Element *nextElement(); Token nextToken(); QString readString(bool &isStringKey); QString readSimpleString(const QString &until = QString(), const bool readNestedCurlyBrackets = false); QString readQuotedString(); QString readBracketString(); Token readValue(Value &value, const QString &fieldType); static QSharedPointer<Person> personFromString(const QString &name, CommaContainment *comma, const int line_number, QObject *parent); static QSharedPointer<Person> personFromTokenList(const QStringList &tokens, CommaContainment *comma, const int line_number, QObject *parent); static void parsePersonList(const QString &text, Value &value, CommaContainment *comma, const int line_number, QObject *parent); /** * Split a string into white-space separated chunks, * but keep parts intact which are protected by {...}. * Example: "aa bb ccc {dd ee ff}" * will be split into "aa", "bb", "ccc", "{dd ee ff}" * * @param text input string to be split * @param segments list where chunks will be added to */ static void contextSensitiveSplit(const QString &text, QStringList &segments); static QString bibtexAwareSimplify(const QString &text); bool evaluateParameterComments(QTextStream *textStream, const QString &line, File *file); QString tokenidToString(Token token); }; #endif // KBIBTEX_IO_FILEIMPORTERBIBTEX_H diff --git a/src/io/fileimporterbibutils.h b/src/io/fileimporterbibutils.h index 4c3bc24c..747c4935 100644 --- a/src/io/fileimporterbibutils.h +++ b/src/io/fileimporterbibutils.h @@ -1,43 +1,47 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * 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 <https://www.gnu.org/licenses/>. * ***************************************************************************/ #ifndef KBIBTEX_IO_FILEIMPORTERBIBUTILS_H #define KBIBTEX_IO_FILEIMPORTERBIBUTILS_H -#include "fileimporter.h" -#include "bibutils.h" +#include <FileImporter> +#include <BibUtils> + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXIO_EXPORT FileImporterBibUtils : public FileImporter, public BibUtils { Q_OBJECT public: explicit FileImporterBibUtils(QObject *parent); ~FileImporterBibUtils() override; File *load(QIODevice *iodevice) override; private: class Private; Private *const d; }; #endif // KBIBTEX_IO_FILEIMPORTERBIBUTILS_H diff --git a/src/io/fileimporterpdf.cpp b/src/io/fileimporterpdf.cpp index a656fb41..cc12dfef 100644 --- a/src/io/fileimporterpdf.cpp +++ b/src/io/fileimporterpdf.cpp @@ -1,108 +1,108 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * 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 <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "fileimporterpdf.h" #include <QBuffer> #include <QFile> #include <poppler-qt5.h> -#include "file.h" +#include <File> #include "fileimporterbibtex.h" #include "logging_io.h" FileImporterPDF::FileImporterPDF(QObject *parent) : FileImporter(parent), m_cancelFlag(false) { m_bibTeXimporter = new FileImporterBibTeX(this); connect(m_bibTeXimporter, &FileImporterBibTeX::message, this, &FileImporterPDF::message); } FileImporterPDF::~FileImporterPDF() { delete m_bibTeXimporter; } File *FileImporterPDF::load(QIODevice *iodevice) { if (!iodevice->isReadable() && !iodevice->open(QIODevice::ReadOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Input device not readable"; return nullptr; } m_cancelFlag = false; File *result = nullptr; QByteArray buffer = iodevice->readAll(); Poppler::Document *doc = Poppler::Document::loadFromData(buffer); if (doc == nullptr) { qCWarning(LOG_KBIBTEX_IO) << "Could not load PDF document"; iodevice->close(); return nullptr; } /// Iterate through all files embedded in this PDF file (if any), /// check for file extension '.bib', and try to load bibliography /// data. if (doc->hasEmbeddedFiles()) { const QList<Poppler::EmbeddedFile *> embeddedFiles = doc->embeddedFiles(); for (Poppler::EmbeddedFile *file : embeddedFiles) { if (file->name().endsWith(QStringLiteral(".bib"))) { // TODO maybe request implementation of a constData() for // Poppler::EmbeddedFile to operate on const objects? QByteArray data(file->data()); QBuffer buffer(&data); FileImporterBibTeX bibTeXimporter(this); connect(&bibTeXimporter, &FileImporter::progress, this, &FileImporter::progress); buffer.open(QIODevice::ReadOnly); result = bibTeXimporter.load(&buffer); buffer.close(); if (result) { qCDebug(LOG_KBIBTEX_IO) << "Bibliography extracted from embedded file" << file->name() << "has" << result->count() << "entries"; if (result->count() > 0) break; ///< stop processing after first valid, non-empty BibTeX file else { /// ... otherwise delete empty bibliography object delete result; result = nullptr; } } else qCDebug(LOG_KBIBTEX_IO) << "Create bibliography file from embedded file" << file->name() << "failed"; } else qCDebug(LOG_KBIBTEX_IO) << "Embedded file" << file->name() << "doesn't have right extension ('.bib')"; } } else qCDebug(LOG_KBIBTEX_IO) << "PDF document has no files embedded"; delete doc; iodevice->close(); return result; } bool FileImporterPDF::guessCanDecode(const QString &) { return false; } void FileImporterPDF::cancel() { m_cancelFlag = true; m_bibTeXimporter->cancel(); } diff --git a/src/io/fileimporterpdf.h b/src/io/fileimporterpdf.h index 193a3c23..ac303e45 100644 --- a/src/io/fileimporterpdf.h +++ b/src/io/fileimporterpdf.h @@ -1,50 +1,53 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * 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 <https://www.gnu.org/licenses/>. * ***************************************************************************/ + #ifndef KBIBTEX_IO_FILEIMPORTERPDF_H #define KBIBTEX_IO_FILEIMPORTERPDF_H -#include "kbibtexio_export.h" - #include <QUrl> -#include "fileimporter.h" +#include <FileImporter> + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 class FileImporterBibTeX; /** @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXIO_EXPORT FileImporterPDF : public FileImporter { Q_OBJECT public: explicit FileImporterPDF(QObject *parent); ~FileImporterPDF() override; File *load(QIODevice *iodevice) override; static bool guessCanDecode(const QString &text); public slots: void cancel() override; private: bool m_cancelFlag; FileImporterBibTeX *m_bibTeXimporter; }; #endif // KBIBTEX_IO_FILEIMPORTERPDF_H diff --git a/src/io/fileimporterris.cpp b/src/io/fileimporterris.cpp index bf92b773..27c494c8 100644 --- a/src/io/fileimporterris.cpp +++ b/src/io/fileimporterris.cpp @@ -1,340 +1,340 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * 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 <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "fileimporterris.h" #include <QVector> #include <QTextStream> #include <QRegularExpression> #include <QCoreApplication> #include <QStringList> -#include "preferences.h" -#include "kbibtex.h" -#include "entry.h" -#include "value.h" +#include <Preferences> +#include <KBibTeX> +#include <Entry> +#include <Value> #include "logging_io.h" #define appendValue(entry, fieldname, newvalue) { Value value = (entry)->value((fieldname)); value.append((newvalue)); (entry)->insert((fieldname), value); } #define removeDuplicates(entry, fieldname) { Value value = (entry)->value((fieldname)); if (!(value).isEmpty()) removeDuplicateValueItems((value)); if (!(value).isEmpty()) (entry)->insert((fieldname), value); } class FileImporterRIS::FileImporterRISPrivate { public: FileImporterRIS *parent; int referenceCounter; bool cancelFlag; bool protectCasing; typedef struct { QString key; QString value; } RISitem; typedef QVector<RISitem> RISitemList; FileImporterRISPrivate(FileImporterRIS *_parent) : parent(_parent), referenceCounter(0), cancelFlag(false), protectCasing(false) { /// nothing } RISitemList readElement(QTextStream &textStream) { RISitemList result; QString line = textStream.readLine(); while (!line.startsWith(QStringLiteral("TY - ")) && !textStream.atEnd()) line = textStream.readLine(); if (textStream.atEnd()) return result; QString key, value; while (!line.startsWith(QStringLiteral("ER -")) && !textStream.atEnd()) { if (line.mid(2, 3) == QStringLiteral(" -")) { if (!value.isEmpty()) { RISitem item; item.key = key; item.value = value; result.append(item); } key = line.left(2); value = line.mid(6).simplified(); } else { line = line.simplified(); if (line.length() > 1) { /// multi-line field are joined to one long line value += QLatin1Char(' ') + line; } } line = textStream.readLine(); } if (!line.startsWith(QStringLiteral("ER -")) && textStream.atEnd()) { qCWarning(LOG_KBIBTEX_IO) << "Expected that entry that starts with 'TY' ends with 'ER' but instead met end of file"; /// Instead of an 'emit' ... QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QStringLiteral("Expected that entry that starts with 'TY' ends with 'ER' but instead met end of file"))); } if (!value.isEmpty()) { RISitem item; item.key = key; item.value = value; result.append(item); } return result; } inline QString optionallyProtectCasing(const QString &text) const { if (protectCasing) return QLatin1Char('{') + text + QLatin1Char('}'); else return text; } Element *nextElement(QTextStream &textStream) { RISitemList list = readElement(textStream); if (list.empty()) return nullptr; QString entryType = Entry::etMisc; Entry *entry = new Entry(entryType, QString(QStringLiteral("RIS_%1")).arg(referenceCounter++)); QString journalName, startPage, endPage, date; int fieldCounter = 0; for (RISitemList::iterator it = list.begin(); it != list.end(); ++it) { if ((*it).key == QStringLiteral("TY")) { if ((*it).value.startsWith(QStringLiteral("BOOK")) || (*it).value.startsWith(QStringLiteral("SER"))) entryType = Entry::etBook; else if ((*it).value.startsWith(QStringLiteral("CHAP"))) entryType = Entry::etInBook; else if ((*it).value.startsWith(QStringLiteral("CONF"))) entryType = Entry::etInProceedings; else if ((*it).value.startsWith(QStringLiteral("JFULL")) || (*it).value.startsWith(QStringLiteral("JOUR")) || (*it).value.startsWith(QStringLiteral("MGZN"))) entryType = Entry::etArticle; else if ((*it).value.startsWith(QStringLiteral("RPRT"))) entryType = Entry::etTechReport; else if ((*it).value.startsWith(QStringLiteral("THES"))) entryType = Entry::etPhDThesis; // FIXME what about etMastersThesis? else if ((*it).value.startsWith(QStringLiteral("UNPB"))) entryType = Entry::etUnpublished; entry->setType(entryType); } else if ((*it).key == QStringLiteral("AU") || (*it).key == QStringLiteral("A1")) { Person *person = splitName((*it).value); if (person != nullptr) appendValue(entry, Entry::ftAuthor, QSharedPointer<Person>(person)); } else if ((*it).key == QStringLiteral("ED") || (*it).key == QStringLiteral("A2")) { Person *person = splitName((*it).value); if (person != nullptr) appendValue(entry, Entry::ftEditor, QSharedPointer<Person>(person)); } else if ((*it).key == QStringLiteral("ID")) { entry->setId((*it).value); } else if ((*it).key == QStringLiteral("Y1") || (*it).key == QStringLiteral("PY")) { date = (*it).value; } else if ((*it).key == QStringLiteral("Y2")) { if (date.isEmpty()) date = (*it).value; } else if ((*it).key == QStringLiteral("AB") || (*it).key == QStringLiteral("N2")) { appendValue(entry, Entry::ftAbstract, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("N1")) { appendValue(entry, Entry::ftNote, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("KW")) { QString text = (*it).value; const QRegularExpression splitRegExp(text.contains(QStringLiteral(";")) ? QStringLiteral("\\s*[;\\n]\\s*") : (text.contains(QStringLiteral(",")) ? QStringLiteral("\\s*[,\\n]\\s*") : QStringLiteral("\\n"))); QStringList newKeywords = text.split(splitRegExp, QString::SkipEmptyParts); for (QStringList::Iterator it = newKeywords.begin(); it != newKeywords.end(); ++it) appendValue(entry, Entry::ftKeywords, QSharedPointer<Keyword>(new Keyword(*it))); } else if ((*it).key == QStringLiteral("TI") || (*it).key == QStringLiteral("T1")) { appendValue(entry, Entry::ftTitle, QSharedPointer<PlainText>(new PlainText(optionallyProtectCasing((*it).value)))); } else if ((*it).key == QStringLiteral("T3")) { appendValue(entry, Entry::ftSeries, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("JO") || (*it).key == QStringLiteral("J1") || (*it).key == QStringLiteral("J2")) { if (journalName.isEmpty()) journalName = (*it).value; } else if ((*it).key == QStringLiteral("JF") || (*it).key == QStringLiteral("JA")) { journalName = (*it).value; } else if ((*it).key == QStringLiteral("VL")) { appendValue(entry, Entry::ftVolume, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("CP")) { appendValue(entry, Entry::ftChapter, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("IS")) { appendValue(entry, Entry::ftNumber, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("DO") || (*it).key == QStringLiteral("M3")) { const QRegularExpressionMatch doiRegExpMatch = KBibTeX::doiRegExp.match((*it).value); if (doiRegExpMatch.hasMatch()) appendValue(entry, Entry::ftDOI, QSharedPointer<VerbatimText>(new VerbatimText(doiRegExpMatch.captured()))); } else if ((*it).key == QStringLiteral("PB")) { appendValue(entry, Entry::ftPublisher, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("IN")) { appendValue(entry, Entry::ftSchool, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("SN")) { const QString fieldName = entryType == Entry::etBook || entryType == Entry::etInBook ? Entry::ftISBN : Entry::ftISSN; appendValue(entry, fieldName, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("CY")) { appendValue(entry, Entry::ftLocation, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("AD")) { appendValue(entry, Entry::ftAddress, QSharedPointer<PlainText>(new PlainText((*it).value))); } else if ((*it).key == QStringLiteral("L1") || (*it).key == QStringLiteral("L2") || (*it).key == QStringLiteral("L3") || (*it).key == QStringLiteral("UR")) { QString fieldValue = (*it).value; fieldValue.replace(QStringLiteral("<Go to ISI>://"), QStringLiteral("isi://")); const QRegularExpressionMatch doiRegExpMatch = KBibTeX::doiRegExp.match(fieldValue); const QRegularExpressionMatch urlRegExpMatch = KBibTeX::urlRegExp.match(fieldValue); const QString fieldName = doiRegExpMatch.hasMatch() ? Entry::ftDOI : (KBibTeX::urlRegExp.match((*it).value).hasMatch() ? Entry::ftUrl : (Preferences::instance().bibliographySystem() == Preferences::BibTeX ? Entry::ftLocalFile : Entry::ftFile)); fieldValue = doiRegExpMatch.hasMatch() ? doiRegExpMatch.captured() : (urlRegExpMatch.hasMatch() ? urlRegExpMatch.captured() : fieldValue); if (fieldValue.startsWith(QStringLiteral("file:///"))) fieldValue = fieldValue.mid(7); appendValue(entry, fieldName, QSharedPointer<VerbatimText>(new VerbatimText(fieldValue))); } else if ((*it).key == QStringLiteral("SP")) { startPage = (*it).value; } else if ((*it).key == QStringLiteral("EP")) { endPage = (*it).value; } else { const QString fieldName = QString(QStringLiteral("RISfield_%1_%2")).arg(fieldCounter++).arg((*it).key.left(2)); appendValue(entry, fieldName, QSharedPointer<PlainText>(new PlainText((*it).value))); } } if (!journalName.isEmpty()) { const QString fieldName = entryType == Entry::etInBook || entryType == Entry::etInProceedings ? Entry::ftBookTitle : Entry::ftJournal; Value value = entry->value(fieldName); value.append(QSharedPointer<PlainText>(new PlainText(optionallyProtectCasing(journalName)))); entry->insert(fieldName, value); } if (!startPage.isEmpty() || !endPage.isEmpty()) { QString page; if (startPage.isEmpty()) page = endPage; else if (endPage.isEmpty()) page = startPage; else page = startPage + QChar(0x2013) + endPage; Value value; value.append(QSharedPointer<PlainText>(new PlainText(page))); entry->insert(Entry::ftPages, value); } QStringList dateFragments = date.split(QStringLiteral("/"), QString::SkipEmptyParts); if (dateFragments.count() > 0) { bool ok; int year = dateFragments[0].toInt(&ok); if (ok && year > 1000 && year < 3000) { Value value = entry->value(Entry::ftYear); value.append(QSharedPointer<PlainText>(new PlainText(QString::number(year)))); entry->insert(Entry::ftYear, value); } else { qCWarning(LOG_KBIBTEX_IO) << "Invalid year: " << dateFragments[0]; /// Instead of an 'emit' ... QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Invalid year: '%1'")).arg(dateFragments[0]))); } } if (dateFragments.count() > 1) { bool ok; int month = dateFragments[1].toInt(&ok); if (ok && month >= 1 && month <= 12) { Value value = entry->value(Entry::ftMonth); value.append(QSharedPointer<MacroKey>(new MacroKey(KBibTeX::MonthsTriple[month - 1]))); entry->insert(Entry::ftMonth, value); } else { qCWarning(LOG_KBIBTEX_IO) << "Invalid month: " << dateFragments[1]; /// Instead of an 'emit' ... QMetaObject::invokeMethod(parent, "message", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(FileImporter::MessageSeverity, SeverityWarning), Q_ARG(QString, QString(QStringLiteral("Invalid month: '%1'")).arg(dateFragments[1]))); } } removeDuplicates(entry, Entry::ftDOI); removeDuplicates(entry, Entry::ftUrl); return entry; } void removeDuplicateValueItems(Value &value) { if (value.count() < 2) return; /// Values with one or no ValueItem cannot have duplicates QSet<QString> uniqueStrings; for (Value::Iterator it = value.begin(); it != value.end();) { const QString itemString = PlainTextValue::text(*it); if (uniqueStrings.contains(itemString)) it = value.erase(it); else { uniqueStrings.insert(itemString); ++it; } } } }; FileImporterRIS::FileImporterRIS(QObject *parent) : FileImporter(parent), d(new FileImporterRISPrivate(this)) { // nothing } FileImporterRIS::~FileImporterRIS() { delete d; } File *FileImporterRIS::load(QIODevice *iodevice) { if (!iodevice->isReadable() && !iodevice->open(QIODevice::ReadOnly)) { qCWarning(LOG_KBIBTEX_IO) << "Input device not readable"; emit message(SeverityError, QStringLiteral("Input device not readable")); return nullptr; } d->cancelFlag = false; d->referenceCounter = 0; QTextStream textStream(iodevice); File *result = new File(); while (!d->cancelFlag && !textStream.atEnd()) { emit progress(textStream.pos(), iodevice->size()); QCoreApplication::instance()->processEvents(); Element *element = d->nextElement(textStream); if (element != nullptr) result->append(QSharedPointer<Element>(element)); QCoreApplication::instance()->processEvents(); } emit progress(100, 100); if (d->cancelFlag) { delete result; result = nullptr; } iodevice->close(); if (result != nullptr) result->setProperty(File::ProtectCasing, static_cast<int>(d->protectCasing ? Qt::Checked : Qt::Unchecked)); return result; } bool FileImporterRIS::guessCanDecode(const QString &text) { return text.indexOf(QStringLiteral("TY - ")) >= 0; } void FileImporterRIS::setProtectCasing(bool protectCasing) { d->protectCasing = protectCasing; } void FileImporterRIS::cancel() { d->cancelFlag = true; } diff --git a/src/io/fileimporterris.h b/src/io/fileimporterris.h index 0393cef6..3bd46fbf 100644 --- a/src/io/fileimporterris.h +++ b/src/io/fileimporterris.h @@ -1,48 +1,52 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * 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 <https://www.gnu.org/licenses/>. * ***************************************************************************/ #ifndef KBIBTEX_IO_FILEIMPORTERRIS_H #define KBIBTEX_IO_FILEIMPORTERRIS_H -#include "entry.h" -#include "fileimporter.h" +#include <Entry> +#include <FileImporter> + +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 /** @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXIO_EXPORT FileImporterRIS : public FileImporter { Q_OBJECT public: explicit FileImporterRIS(QObject *parent); ~FileImporterRIS() override; File *load(QIODevice *iodevice) override; static bool guessCanDecode(const QString &text); void setProtectCasing(bool protectCasing); public slots: void cancel() override; private: class FileImporterRISPrivate; FileImporterRISPrivate *d; }; #endif // KBIBTEX_IO_FILEIMPORTERRIS_H diff --git a/src/io/fileinfo.cpp b/src/io/fileinfo.cpp index 5f7a25b8..0231eb14 100644 --- a/src/io/fileinfo.cpp +++ b/src/io/fileinfo.cpp @@ -1,370 +1,370 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * 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 <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "fileinfo.h" #include <poppler-qt5.h> #include <QFileInfo> #include <QDir> #include <QTextStream> #include <QStandardPaths> #include <QRegularExpression> #include <QtConcurrentRun> -#include "kbibtex.h" -#include "entry.h" +#include <KBibTeX> +#include <Entry> #include "logging_io.h" FileInfo::FileInfo() { /// nothing } const QString FileInfo::mimetypeOctetStream = QStringLiteral("application/octet-stream"); const QString FileInfo::mimetypeHTML = QStringLiteral("text/html"); const QString FileInfo::mimetypeBibTeX = QStringLiteral("text/x-bibtex"); const QString FileInfo::mimetypeRIS = QStringLiteral("application/x-research-info-systems"); const QString FileInfo::mimetypePDF = QStringLiteral("application/pdf"); QMimeType FileInfo::mimeTypeForUrl(const QUrl &url) { if (!url.isValid() || url.isEmpty()) { qCWarning(LOG_KBIBTEX_IO) << "Cannot determine mime type for empty or invalid QUrl"; return QMimeType(); ///< invalid input gives invalid mime type } static const QMimeDatabase db; static const QMimeType mtHTML(db.mimeTypeForName(mimetypeHTML)); static const QMimeType mtOctetStream(db.mimeTypeForName(mimetypeOctetStream)); static const QMimeType mtBibTeX(db.mimeTypeForName(mimetypeBibTeX)); static const QMimeType mtPDF(db.mimeTypeForName(mimetypePDF)); static const QMimeType mtRIS(db.mimeTypeForName(mimetypeRIS)); /// Test if mime type for BibTeX is registered before determining file extension static const QString mimetypeBibTeXExt = mtBibTeX.preferredSuffix(); /// Test if mime type for RIS is registered before determining file extension static const QString mimetypeRISExt = mtRIS.preferredSuffix(); /// Test if mime type for PDF is registered before determining file extension static const QString mimetypePDFExt = mtPDF.preferredSuffix(); const QString extension = db.suffixForFileName(url.fileName()).toLower(); /// First, check preferred suffixes if (extension == mimetypeBibTeXExt) return mtBibTeX; else if (extension == mimetypeRISExt) return mtRIS; else if (extension == mimetypePDFExt) return mtPDF; /// Second, check any other suffixes else if (mtBibTeX.suffixes().contains(extension)) return mtBibTeX; else if (mtRIS.suffixes().contains(extension)) return mtRIS; else if (mtPDF.suffixes().contains(extension)) return mtPDF; /// Let the KDE subsystem guess the mime type QMimeType result = db.mimeTypeForUrl(url); /// Fall back to application/octet-stream if something goes wrong if (!result.isValid()) result = mtOctetStream; /// In case that KDE could not determine mime type, /// do some educated guesses on our own if (result.name() == mimetypeOctetStream) { if (url.scheme().startsWith(QStringLiteral("http"))) result = mtHTML; // TODO more tests? } return result; } void FileInfo::urlsInText(const QString &text, const TestExistence testExistence, const QString &baseDirectory, QSet<QUrl> &result) { if (text.isEmpty()) return; /// DOI identifiers have to extracted first as KBibTeX::fileListSeparatorRegExp /// contains characters that can be part of a DOI (e.g. ';') and thus could split /// a DOI in between. QString internalText = text; int pos = 0; QRegularExpressionMatch doiRegExpMatch; while ((doiRegExpMatch = KBibTeX::doiRegExp.match(internalText, pos)).hasMatch()) { pos = doiRegExpMatch.capturedStart(0); QString doiMatch = doiRegExpMatch.captured(0); const int semicolonHttpPos = doiMatch.indexOf(QStringLiteral(";http")); if (semicolonHttpPos > 0) doiMatch = doiMatch.left(semicolonHttpPos); const QUrl url(KBibTeX::doiUrlPrefix + QString(doiMatch).remove(QStringLiteral("\\"))); if (url.isValid() && !result.contains(url)) result << url; /// remove match from internal text to avoid duplicates /// Cut away any URL that may be right before found DOI number: /// For example, if DOI '10.1000/38-abc' was found in /// 'Lore ipsum http://doi.example.org/10.1000/38-abc Lore ipsum' /// also remove 'http://doi.example.org/' from the text, keeping only /// 'Lore ipsum Lore ipsum' static const QRegularExpression genericDoiUrlPrefix(QStringLiteral("http[s]?://[a-z0-9./-]+/$")); ///< looks like an URL const QRegularExpressionMatch genericDoiUrlPrefixMatch = genericDoiUrlPrefix.match(internalText.left(pos)); if (genericDoiUrlPrefixMatch.hasMatch()) /// genericDoiUrlPrefixMatch.captured(0) may contain (parts of) DOI internalText = internalText.left(genericDoiUrlPrefixMatch.capturedStart(0)) + internalText.mid(pos + doiMatch.length()); else internalText = internalText.left(pos) + internalText.mid(pos + doiMatch.length()); } const QStringList fileList = internalText.split(KBibTeX::fileListSeparatorRegExp, QString::SkipEmptyParts); for (const QString &text : fileList) { internalText = text; /// If testing for the actual existence of a filename found in the text ... if (testExistence == TestExistenceYes) { /// If a base directory (e.g. the location of the parent .bib file) is given /// and the potential filename fragment is NOT an absolute path, ... if (internalText.startsWith(QStringLiteral("~") + QDir::separator())) { const QString fullFilename = QDir::homePath() + internalText.mid(1); const QFileInfo fileInfo(fullFilename); const QUrl url = QUrl::fromLocalFile(fileInfo.canonicalFilePath()); if (fileInfo.exists() && fileInfo.isFile() && url.isValid() && !result.contains(url)) { result << url; /// Stop searching for URLs or filenames in current internal text continue; } } else if (!baseDirectory.isEmpty() && // TODO the following test assumes that absolute paths start // with a dir separator, which may only be true on Unix/Linux, // but not Windows. May be a test for 'first character is a letter, // second is ":", third is "\"' may be necessary. !internalText.startsWith(QDir::separator())) { /// To get the absolute path, prepend filename fragment with base directory const QString fullFilename = baseDirectory + QDir::separator() + internalText; const QFileInfo fileInfo(fullFilename); const QUrl url = QUrl::fromLocalFile(fileInfo.canonicalFilePath()); if (fileInfo.exists() && fileInfo.isFile() && url.isValid() && !result.contains(url)) { result << url; /// Stop searching for URLs or filenames in current internal text continue; } } else { /// Either the filename fragment is an absolute path OR no base directory /// was given (current working directory is assumed), ... const QFileInfo fileInfo(internalText); const QUrl url = QUrl::fromLocalFile(fileInfo.canonicalFilePath()); if (fileInfo.exists() && fileInfo.isFile() && url.isValid() && !result.contains(url)) { result << url; /// stop searching for URLs or filenames in current internal text continue; } } } /// extract URL from current field pos = 0; QRegularExpressionMatch urlRegExpMatch; while ((urlRegExpMatch = KBibTeX::urlRegExp.match(internalText, pos)).hasMatch()) { pos = urlRegExpMatch.capturedStart(0); const QString match = urlRegExpMatch.captured(0); QUrl url(match); if (url.isValid() && (testExistence == TestExistenceNo || !url.isLocalFile() || QFileInfo::exists(url.toLocalFile())) && !result.contains(url)) result << url; /// remove match from internal text to avoid duplicates internalText = internalText.left(pos) + internalText.mid(pos + match.length()); } /// explicitly check URL entry, may be an URL even if http:// or alike is missing pos = 0; QRegularExpressionMatch domainNameRegExpMatch; while ((domainNameRegExpMatch = KBibTeX::domainNameRegExp.match(internalText, pos)).hasMatch()) { pos = domainNameRegExpMatch.capturedStart(0); int pos2 = internalText.indexOf(QStringLiteral(" "), pos + 1); if (pos2 < 0) pos2 = internalText.length(); QString match = internalText.mid(pos, pos2 - pos); const QUrl url(QStringLiteral("http://") + match); // FIXME what about HTTPS? if (url.isValid() && !result.contains(url)) result << url; /// remove match from internal text to avoid duplicates internalText = internalText.left(pos) + internalText.mid(pos + match.length()); } /// extract general file-like patterns pos = 0; QRegularExpressionMatch fileRegExpMatch; while ((fileRegExpMatch = KBibTeX::fileRegExp.match(internalText, pos)).hasMatch()) { pos = fileRegExpMatch.capturedStart(0); const QString match = fileRegExpMatch.captured(0); QUrl url(match); if (url.isValid() && (testExistence == TestExistenceNo || !url.isLocalFile() || QFileInfo::exists(url.toLocalFile())) && !result.contains(url)) result << url; /// remove match from internal text to avoid duplicates internalText = internalText.left(pos) + internalText.mid(pos + match.length()); } } } QSet<QUrl> FileInfo::entryUrls(const QSharedPointer<const Entry> &entry, const QUrl &bibTeXUrl, TestExistence testExistence) { QSet<QUrl> result; if (entry.isNull() || entry->isEmpty()) return result; if (entry->contains(Entry::ftDOI)) { const QString doi = PlainTextValue::text(entry->value(Entry::ftDOI)); QRegularExpressionMatch doiRegExpMatch; if (!doi.isEmpty() && (doiRegExpMatch = KBibTeX::doiRegExp.match(doi)).hasMatch()) { QString match = doiRegExpMatch.captured(0); QUrl url(KBibTeX::doiUrlPrefix + match.remove(QStringLiteral("\\"))); result.insert(url); } } static const QString etPMID = QStringLiteral("pmid"); if (entry->contains(etPMID)) { const QString pmid = PlainTextValue::text(entry->value(etPMID)); bool ok = false; ok &= pmid.toInt(&ok) > 0; if (ok) { QUrl url(QStringLiteral("https://www.ncbi.nlm.nih.gov/pubmed/") + pmid); result.insert(url); } } static const QString etEPrint = QStringLiteral("eprint"); if (entry->contains(etEPrint)) { const QString eprint = PlainTextValue::text(entry->value(etEPrint)); if (!eprint.isEmpty()) { QUrl url(QStringLiteral("http://arxiv.org/search?query=") + eprint); result.insert(url); } } const QString baseDirectory = bibTeXUrl.isValid() ? bibTeXUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path() : QString(); for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it) { /// skip abstracts, they contain sometimes strange text fragments /// that are mistaken for URLs if (it.key().toLower() == Entry::ftAbstract) continue; const Value v = it.value(); for (const auto &valueItem : v) { QString plainText = PlainTextValue::text(*valueItem); static const QRegularExpression regExpEscapedChars = QRegularExpression(QStringLiteral("\\\\+([&_~])")); plainText.replace(regExpEscapedChars, QStringLiteral("\\1")); urlsInText(plainText, testExistence, baseDirectory, result); } } if (!baseDirectory.isEmpty()) { /// File types supported by "document preview" static const QStringList documentFileExtensions {QStringLiteral(".pdf"), QStringLiteral(".pdf.gz"), QStringLiteral(".pdf.bz2"), QStringLiteral(".ps"), QStringLiteral(".ps.gz"), QStringLiteral(".ps.bz2"), QStringLiteral(".eps"), QStringLiteral(".eps.gz"), QStringLiteral(".eps.bz2"), QStringLiteral(".html"), QStringLiteral(".xhtml"), QStringLiteral(".htm"), QStringLiteral(".dvi"), QStringLiteral(".djvu"), QStringLiteral(".wwf"), QStringLiteral(".jpeg"), QStringLiteral(".jpg"), QStringLiteral(".png"), QStringLiteral(".gif"), QStringLiteral(".tif"), QStringLiteral(".tiff")}; result.reserve(result.size() + documentFileExtensions.size() * 2); /// check if in the same directory as the BibTeX file /// a PDF file exists which filename is based on the entry's id for (const QString &extension : documentFileExtensions) { const QFileInfo fi(baseDirectory + QDir::separator() + entry->id() + extension); if (fi.exists()) { const QUrl url = QUrl::fromLocalFile(fi.canonicalFilePath()); if (!result.contains(url)) result << url; } } /// check if in the same directory as the BibTeX file there is a subdirectory /// similar to the BibTeX file's name and which contains a PDF file exists /// which filename is based on the entry's id static const QRegularExpression filenameExtension(QStringLiteral("\\.[^.]{2,5}$")); const QString basename = bibTeXUrl.fileName().remove(filenameExtension); QString directory = baseDirectory + QDir::separator() + basename; for (const QString &extension : documentFileExtensions) { const QFileInfo fi(directory + QDir::separator() + entry->id() + extension); if (fi.exists()) { const QUrl url = QUrl::fromLocalFile(fi.canonicalFilePath()); if (!result.contains(url)) result << url; } } } return result; } QString FileInfo::pdfToText(const QString &pdfFilename) { /// Build filename for text file where PDF file's plain text is cached const QString cacheDirectory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/pdftotext"); if (!QDir(cacheDirectory).exists() && !QDir::home().mkdir(cacheDirectory)) /// Could not create cache directory return QString(); static const QRegularExpression invalidChars(QStringLiteral("[^-a-z0-9_]"), QRegularExpression::CaseInsensitiveOption); const QString textFilename = QString(pdfFilename).remove(invalidChars).append(QStringLiteral(".txt")).prepend(QStringLiteral("/")).prepend(cacheDirectory); /// First, check if there is a cache text file if (QFileInfo::exists(textFilename)) { /// Load text from cache file QFile f(textFilename); if (f.open(QFile::ReadOnly)) { const QString text = QString::fromUtf8(f.readAll()); f.close(); return text; } } else /// No cache file exists, so run text extraction in another thread QtConcurrent::run(extractPDFTextToCache, pdfFilename, textFilename); return QString(); } void FileInfo::extractPDFTextToCache(const QString &pdfFilename, const QString &cacheFilename) { /// In case of multiple calls, skip text extraction if cache file already exists if (QFile(cacheFilename).exists()) return; QString text; QStringList msgList; /// Load PDF file through Poppler Poppler::Document *doc = Poppler::Document::load(pdfFilename); if (doc != nullptr) { static const int maxPages = 64; /// Build text by appending each page's text for (int i = 0; i < qMin(maxPages, doc->numPages()); ++i) text.append(doc->page(i)->text(QRect())).append(QStringLiteral("\n\n")); if (doc->numPages() > maxPages) msgList << QString(QStringLiteral("### Skipped %1 pages as PDF file contained too many pages (limit is %2 pages) ###")).arg(doc->numPages() - maxPages).arg(maxPages); delete doc; } else msgList << QStringLiteral("### Skipped as file could not be opened as PDF file ###"); /// Save text in cache file QFile f(cacheFilename); if (f.open(QFile::WriteOnly)) { static const int maxCharacters = 1 << 18; f.write(text.left(maxCharacters).toUtf8()); ///< keep only the first 2^18 many characters if (text.length() > maxCharacters) msgList << QString(QStringLiteral("### Text too long, skipping %1 characters ###")).arg(text.length() - maxCharacters); /// Write all messages (warnings) to end of text file for (const QString &msg : const_cast<const QStringList &>(msgList)) { static const char linebreak = '\n'; f.write(&linebreak, 1); f.write(msg.toUtf8()); } f.close(); } } diff --git a/src/io/fileinfo.h b/src/io/fileinfo.h index 116f272b..83a1c2f7 100644 --- a/src/io/fileinfo.h +++ b/src/io/fileinfo.h @@ -1,104 +1,106 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * 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 <https://www.gnu.org/licenses/>. * ***************************************************************************/ #ifndef KBIBTEX_IO_FILEINFO_H #define KBIBTEX_IO_FILEINFO_H -#include "kbibtexio_export.h" - #include <QSet> #include <QUrl> #include <QMimeDatabase> #include <QMimeType> #include <QSharedPointer> +#ifdef HAVE_KF5 +#include "kbibtexio_export.h" +#endif // HAVE_KF5 + class Entry; class KBIBTEXIO_EXPORT FileInfo { public: static const QString mimetypeOctetStream; static const QString mimetypeHTML; static const QString mimetypeBibTeX; static const QString mimetypeRIS; static const QString mimetypePDF; enum TestExistence { TestExistenceYes, ///< Test if file exists TestExistenceNo ///< Skip test if file exists }; /** * Finds a QMimeType with the given url. * Tries to guess a file's mime type by its extension first, * but falls back to QMimeType's mimeTypeForName if that does * not work. Background: If a HTTP or WebDAV server claims * that a .bib file is of mime type application/octet-stream, * QMimeType::mimeTypeForName will keep that assessment * instead of inspecting the file extension. * * @see QMimeType::mimeTypeForName * @param url Url to analyze * @return Guessed mime type */ static QMimeType mimeTypeForUrl(const QUrl &url); /** * Find all file or URL references in the given text. Found filenames or * URLs are appended to the addTo list (duplicates are avoided). * Different test may get performed depending of the test for existence * of a potential file should be checked or not checked or if this matter * is undecided/irrelevant (recommended default case). For the test of * existence, baseDirectory is used to resolve relative paths. * @param text text to scan for filenames or URLs * @param testExistence shall be tested for file existence? * @param baseDirectory base directory for tests on relative path names * @param addTo add found URLs/filenames to this list */ static void urlsInText(const QString &text, const TestExistence testExistence, const QString &baseDirectory, QSet<QUrl> &addTo); /** * Find all file or URL references in the given entry. Found filenames or * URLs are appended to the addTo list (duplicates are avoided). * Different test may get performed depending of the test for existence * of a potential file should be checked or not checked or if this matter * is undecided/irrelevant (recommended default case). For the test of * existence, bibTeXUrl is used to resolve relative paths. * @param entry entry to scan for filenames or URLs * @param bibTeXUrl base directory/URL for tests on relative path names * @param testExistence shall be tested for file existence? * @return list of found URLs/filenames (duplicates are avoided) */ static QSet<QUrl> entryUrls(const QSharedPointer<const Entry> &entry, const QUrl &bibTeXUrl, TestExistence testExistence); /** * Load the given PDF file and return the contained plain text. * Makes use of Poppler to load and parse the file. All text * will be cached and loaded from cache if possible. * @param pdfFilename PDF file to load and extract text from * @return extracted plain text, either directly from PDF file or from cache OR QString() if there was an error */ static QString pdfToText(const QString &pdfFilename); protected: FileInfo(); private: static void extractPDFTextToCache(const QString &pdfFilename, const QString &cacheFilename); }; #endif // KBIBTEX_IO_FILEINFO_H diff --git a/src/io/textencoder.cpp b/src/io/textencoder.cpp index ac1e3fd0..ff520df4 100644 --- a/src/io/textencoder.cpp +++ b/src/io/textencoder.cpp @@ -1,69 +1,69 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * 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 <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "textencoder.h" #include <QStringList> #include <QTextCodec> #include <QDebug> -#include "logging_io.h" #include "encoderlatex.h" +#include "logging_io.h" TextEncoder::TextEncoder() { /// nothing } QByteArray TextEncoder::encode(const QString &input, const QString &destinationEncoding) { QTextCodec *destinationCodec(QTextCodec::codecForName(destinationEncoding.toLatin1())); return encode(input, destinationCodec); } QByteArray TextEncoder::encode(const QString &input, const QTextCodec *destinationCodec) { /// Invalid codec? Cannot do anything if (destinationCodec == nullptr) return QByteArray(); /// Perform Canonical Decomposition followed by Canonical Composition const QString ninput = input.normalized(QString::NormalizationForm_C); QByteArray result; const Encoder &laTeXEncoder = EncoderLaTeX::instance(); /// Build result, character by character for (const QChar &c : ninput) { /// Get byte sequence representing current character in chosen codec const QByteArray cba = destinationCodec->fromUnicode(c); if (destinationCodec->canEncode(c) && (c == QChar(0x003f /** question mark */) || cba.size() != 1 || cba[0] != 0x3f /** question mark */)) { /// Codec claims that it can encode current character, but some codecs /// still cannot encode character and simply return a question mark, so /// only accept question marks as encoding result if original character /// was question mark (assuming all codecs can encode question marks). result.append(cba); } else { /// Chosen codec can NOT encode current Unicode character, so try to use /// 'LaTeX encoder', which may translate 0x00c5 (A with ring above) into /// '\AA'. LaTeX encoder returns UTF-8 representation if given character /// cannot be encoded result.append(laTeXEncoder.encode(QString(c), Encoder::TargetEncodingASCII).toUtf8()); } } return result; } diff --git a/src/io/xsltransform.h b/src/io/xsltransform.h index cdf333c6..7bcb48e1 100644 --- a/src/io/xsltransform.h +++ b/src/io/xsltransform.h @@ -1,62 +1,62 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * 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 <https://www.gnu.org/licenses/>. * ***************************************************************************/ #ifndef KBIBTEX_IO_XSLTRANSFORM_H #define KBIBTEX_IO_XSLTRANSFORM_H +#include <QString> + #ifdef HAVE_KF5 #include "kbibtexio_export.h" #endif // HAVE_KF5 -#include <QString> - class QXmlQuery; class QByteArray; /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXIO_EXPORT XSLTransform { public: /** * Create a new instance of a transformer. * @param xsltFilename file name of the XSL file */ explicit XSLTransform(const QString &xsltFilename); ~XSLTransform(); XSLTransform(const XSLTransform &other) = delete; XSLTransform &operator= (const XSLTransform &other) = delete; bool isValid() const; /** * Transform a given XML document using the tranformer's * XSL file. * @param xmlText XML document to transform * @return transformed document */ QString transform(const QString &xmlText) const; static QString locateXSLTfile(const QString &stem); private: QByteArray *xsltData; }; #endif // KBIBTEX_IO_XSLTRANSFORM_H diff --git a/src/networking/CMakeLists.txt b/src/networking/CMakeLists.txt index bc082185..4a2a9bbc 100644 --- a/src/networking/CMakeLists.txt +++ b/src/networking/CMakeLists.txt @@ -1,131 +1,129 @@ # Network library set( kbibtexnetworking_LIB_SRCS onlinesearch/onlinesearchabstract.cpp onlinesearch/onlinesearchbibsonomy.cpp onlinesearch/onlinesearcharxiv.cpp onlinesearch/onlinesearchsciencedirect.cpp onlinesearch/onlinesearchgooglescholar.cpp onlinesearch/onlinesearchieeexplore.cpp onlinesearch/onlinesearchpubmed.cpp onlinesearch/onlinesearchacmportal.cpp onlinesearch/onlinesearchspringerlink.cpp onlinesearch/onlinesearchjstor.cpp onlinesearch/onlinesearchmathscinet.cpp onlinesearch/onlinesearchmrlookup.cpp onlinesearch/onlinesearchinspirehep.cpp onlinesearch/onlinesearchcernds.cpp onlinesearch/onlinesearchingentaconnect.cpp onlinesearch/onlinesearchsimplebibtexdownload.cpp onlinesearch/onlinesearchgeneral.cpp onlinesearch/onlinesearchsoanasaads.cpp # onlinesearch/onlinesearchisbndb.cpp # disabled as provider switched to a paid model on 2017-12-26 onlinesearch/onlinesearchideasrepec.cpp onlinesearch/onlinesearchdoi.cpp onlinesearch/onlinesearchbiorxiv.cpp onlinesearch/onlinesearchsemanticscholar.cpp zotero/api.cpp zotero/collectionmodel.cpp zotero/collection.cpp zotero/items.cpp zotero/groups.cpp zotero/oauthwizard.cpp zotero/tags.cpp zotero/tagmodel.cpp associatedfiles.cpp findpdf.cpp internalnetworkaccessmanager.cpp logging_networking.cpp ) set( kbibtexnetworking_HDRS onlinesearch/onlinesearchgeneral.h onlinesearch/onlinesearchsciencedirect.h onlinesearch/onlinesearchabstract.h onlinesearch/onlinesearchacmportal.h onlinesearch/onlinesearchbibsonomy.h onlinesearch/onlinesearchgooglescholar.h onlinesearch/onlinesearchspringerlink.h onlinesearch/onlinesearchjstor.h onlinesearch/onlinesearchieeexplore.h onlinesearch/onlinesearcharxiv.h onlinesearch/onlinesearchpubmed.h onlinesearch/onlinesearchingentaconnect.h onlinesearch/onlinesearchsimplebibtexdownload.h onlinesearch/onlinesearchsoanasaads.h onlinesearch/onlinesearchmathscinet.h onlinesearch/onlinesearchmrlookup.h onlinesearch/onlinesearchinspirehep.h onlinesearch/onlinesearchcernds.h onlinesearch/onlinesearchisbndb.h onlinesearch/onlinesearchideasrepec.h onlinesearch/onlinesearchdoi.h onlinesearch/onlinesearchbiorxiv.h onlinesearch/onlinesearchsemanticscholar.h zotero/api.h zotero/collectionmodel.h zotero/collection.h zotero/items.h zotero/groups.h zotero/oauthwizard.h zotero/tags.h zotero/tagmodel.h associatedfiles.h findpdf.h internalnetworkaccessmanager.h ) if(UNITY_BUILD) enable_unity_build(kbibtexnetworking kbibtexnetworking_LIB_SRCS) endif(UNITY_BUILD) include_directories( - ${CMAKE_BINARY_DIR}/src/io - ${CMAKE_SOURCE_DIR}/src/io ${CMAKE_SOURCE_DIR}/src/networking/onlinesearch ${CMAKE_SOURCE_DIR}/src/networking ) add_library( kbibtexnetworking SHARED ${kbibtexnetworking_LIB_SRCS} ) target_link_libraries( kbibtexnetworking Qt5::Core Qt5::Widgets Qt5::Network Qt5::NetworkAuth KF5::I18n KF5::XmlGui KF5::Completion KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets KF5::KIONTLM KF5::Wallet kbibtexconfig kbibtexdata kbibtexio Poppler::Qt5 ) set_target_properties( kbibtexnetworking PROPERTIES EXPORT_NAME "kbibtexnetworking" VERSION ${KBIBTEX_RELEASE_VERSION} SOVERSION ${KBIBTEX_SOVERSION} ) install( TARGETS kbibtexnetworking ${INSTALL_TARGETS_DEFAULT_ARGS} ) generate_export_header( kbibtexnetworking ) diff --git a/src/parts/CMakeLists.txt b/src/parts/CMakeLists.txt index af465e17..2dc2bb16 100644 --- a/src/parts/CMakeLists.txt +++ b/src/parts/CMakeLists.txt @@ -1,86 +1,84 @@ # KBibTeX KPart set( kbibtexpart_SRCS part.cpp partfactory.cpp browserextension.cpp logging_parts.cpp ) if(UNITY_BUILD) enable_unity_build(kbibtexpart kbibtexpart_SRCS) endif(UNITY_BUILD) include_directories( ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_BINARY_DIR}/src/io - ${CMAKE_SOURCE_DIR}/src/io ${CMAKE_SOURCE_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui/config ${CMAKE_SOURCE_DIR}/src/gui/element ${CMAKE_SOURCE_DIR}/src/gui/preferences ${CMAKE_SOURCE_DIR}/src/gui/widgets ${CMAKE_SOURCE_DIR}/src/gui/file ${CMAKE_SOURCE_DIR}/src/processing ${CMAKE_SOURCE_DIR}/src/networking ) # Creates kbibtex-git-info.h containing information about the source code's Git revision # (if source directory is a Git clone) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${CMAKE_SOURCE_DIR} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_SOURCE_DIR}/src/getgit.cmake ) set_source_files_properties( ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h PROPERTIES GENERATED 1 HEADER_FILE_ONLY 1 SKIP_AUTOMOC ON SKIP_AUTOUIC ON SKIP_AUTOGEN ON ) add_library( kbibtexpart MODULE ${kbibtexpart_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) target_link_libraries( kbibtexpart KF5::Parts KF5::CoreAddons KF5::ItemViews kbibtexconfig kbibtexdata kbibtexio kbibtexgui kbibtexproc ) install( TARGETS kbibtexpart DESTINATION ${KDE_INSTALL_PLUGINDIR} ) kcoreaddons_desktop_to_json(kbibtexpart kbibtexpart.desktop SERVICE_TYPES kpart.desktop) install( FILES kbibtexpart.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES kbibtexpartui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kbibtexpart ) diff --git a/src/processing/CMakeLists.txt b/src/processing/CMakeLists.txt index 1f07e3c9..a8e8d882 100644 --- a/src/processing/CMakeLists.txt +++ b/src/processing/CMakeLists.txt @@ -1,63 +1,61 @@ # KBibTeX Processing library set( kbibtexproc_LIB_SRCS findduplicates.cpp idsuggestions.cpp lyx.cpp checkbibtex.cpp bibliographyservice.cpp journalabbreviations.cpp logging_processing.cpp ) set( kbibtexproc_HDRS findduplicates.h idsuggestions.h lyx.h checkbibtex.h bibliographyservice.h journalabbreviations.h ) if(UNITY_BUILD) enable_unity_build(kbibtexproc kbibtexproc_LIB_SRCS) endif(UNITY_BUILD) include_directories( - ${CMAKE_BINARY_DIR}/src/io - ${CMAKE_SOURCE_DIR}/src/io ${CMAKE_BINARY_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui ) add_library( kbibtexproc SHARED ${kbibtexproc_LIB_SRCS} ) target_link_libraries( kbibtexproc Qt5::Core KF5::Parts kbibtexconfig kbibtexdata kbibtexio ) set_target_properties( kbibtexproc PROPERTIES EXPORT_NAME "kbibtexproc" VERSION ${KBIBTEX_RELEASE_VERSION} SOVERSION ${KBIBTEX_SOVERSION} ) install( TARGETS kbibtexproc ${INSTALL_TARGETS_DEFAULT_ARGS} ) generate_export_header( kbibtexproc ) diff --git a/src/program/CMakeLists.txt b/src/program/CMakeLists.txt index 23b4dc76..9d5abf80 100644 --- a/src/program/CMakeLists.txt +++ b/src/program/CMakeLists.txt @@ -1,151 +1,148 @@ # KBibTeX main program project( kbibtexprogram ) set( kbibtex_SRCS program.cpp mainwindow.cpp documentlist.cpp mdiwidget.cpp docklets/statistics.cpp docklets/referencepreview.cpp docklets/documentpreview.cpp docklets/valuelist.cpp docklets/searchform.cpp docklets/searchresults.cpp docklets/elementform.cpp docklets/filesettings.cpp docklets/zoterobrowser.cpp openfileinfo.cpp logging_program.cpp ) if(UNITY_BUILD AND NOT WIN32) # FIXME: Unity build of programs breaks on Windows enable_unity_build(kbibtex kbibtex_SRCS) endif(UNITY_BUILD AND NOT WIN32) include_directories( ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src/processing ${CMAKE_BINARY_DIR}/src/processing - ${CMAKE_SOURCE_DIR}/src/io - ${CMAKE_BINARY_DIR}/src/io - ${CMAKE_SOURCE_DIR}/src/io/config ${CMAKE_SOURCE_DIR}/src/networking ${CMAKE_BINARY_DIR}/src/networking ${CMAKE_SOURCE_DIR}/src/networking/onlinesearch ${CMAKE_SOURCE_DIR}/src/gui ${CMAKE_BINARY_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui/widgets ${CMAKE_SOURCE_DIR}/src/gui/file ${CMAKE_SOURCE_DIR}/src/gui/element ${CMAKE_SOURCE_DIR}/src/program/docklets ) ecm_add_app_icon( kbibtex_SRCS ICONS ${CMAKE_SOURCE_DIR}/icons/128-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/16-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/22-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/32-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/48-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/64-apps-kbibtex.png ) # Creates kbibtex-git-info.h containing information about the source code's Git revision # (if source directory is a Git clone) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${CMAKE_SOURCE_DIR} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_SOURCE_DIR}/src/getgit.cmake ) set_source_files_properties( ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h PROPERTIES GENERATED 1 HEADER_FILE_ONLY 1 SKIP_AUTOMOC ON SKIP_AUTOUIC ON SKIP_AUTOGEN ON ) add_executable( kbibtex ${kbibtex_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) target_link_libraries( kbibtex Qt5::Core Qt5::Widgets KF5::CoreAddons KF5::I18n KF5::ConfigCore KF5::Service KF5::Parts KF5::IconThemes KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets KF5::KIONTLM KF5::Crash kbibtexio kbibtexgui kbibtexnetworking ) if(Qt5WebEngineWidgets_FOUND) target_link_libraries( kbibtex Qt5::WebEngineWidgets ) else(Qt5WebEngineWidgets_FOUND) if(Qt5WebKitWidgets_FOUND) target_link_libraries( kbibtex Qt5::WebKitWidgets ) endif(Qt5WebKitWidgets_FOUND) endif(Qt5WebEngineWidgets_FOUND) install( TARGETS kbibtex ${INSTALL_TARGETS_DEFAULT_ARGS} ) install( PROGRAMS org.kde.kbibtex.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install( FILES kbibtexui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kbibtex ) install( FILES org.kde.kbibtex.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) ecm_install_icons( ICONS ${CMAKE_SOURCE_DIR}/icons/128-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/16-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/22-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/32-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/48-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/64-apps-kbibtex.png DESTINATION ${KDE_INSTALL_ICONDIR} ) diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 09a21832..74d51667 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,182 +1,181 @@ # KBibTeX test program project( test ) include( AddFileDependencies ) include( ECMMarkAsTest ) configure_file(test-config.h.in test-config.h @ONLY) include_directories( ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/src/io ${CMAKE_SOURCE_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui/config ${CMAKE_SOURCE_DIR}/src/gui/bibtex ${CMAKE_SOURCE_DIR}/src/gui/element ${CMAKE_SOURCE_DIR}/src/gui/widgets ${CMAKE_SOURCE_DIR}/src/networking ${CMAKE_SOURCE_DIR}/src/networking/onlinesearch ${CMAKE_SOURCE_DIR}/src/processing ) set( kbibtextest_SRCS main.cpp kbibtextest.cpp logging_test.cpp ) set( kbibtexfilestest_SRCS kbibtexfilestest.cpp kbibtexfilestest-rawdata.h ) set( kbibtexnetworkingtest_SRCS kbibtexnetworkingtest.cpp ) set( kbibtexiotest_SRCS kbibtexiotest.cpp ) set( kbibtexdatatest_SRCS kbibtexdatatest.cpp ) if(UNITY_BUILD AND NOT WIN32) # FIXME: Unity build of programs breaks on Windows enable_unity_build(kbibtextest kbibtextest_SRCS) enable_unity_build(kbibtexfilestest kbibtexfilestest_SRCS) enable_unity_build(kbibtexnetworkingtest kbibtexnetworkingtest_SRCS) enable_unity_build(kbibtexiotest kbibtexiotest_SRCS) enable_unity_build(kbibtexdatatest kbibtexdatatest_SRCS) endif(UNITY_BUILD AND NOT WIN32) # Creates kbibtex-git-info.h containing information about the source code's Git revision # (if source directory is a Git clone) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${CMAKE_SOURCE_DIR} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_SOURCE_DIR}/src/getgit.cmake ) set_source_files_properties( ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h PROPERTIES GENERATED 1 HEADER_FILE_ONLY 1 SKIP_AUTOMOC ON SKIP_AUTOUIC ON SKIP_AUTOGEN ON ) add_executable( kbibtextest ${kbibtextest_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) add_executable( kbibtexfilestest ${kbibtexfilestest_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) add_executable( kbibtexnetworkingtest ${kbibtexnetworkingtest_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) add_executable( kbibtexiotest ${kbibtexiotest_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) add_executable( kbibtexdatatest ${kbibtexdatatest_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) target_link_libraries( kbibtextest Qt5::Core KF5::KIOCore kbibtexconfig kbibtexdata kbibtexio kbibtexproc kbibtexgui kbibtexnetworking ) target_link_libraries( kbibtexfilestest Qt5::Test kbibtexdata kbibtexio ) target_link_libraries( kbibtexnetworkingtest Qt5::Test kbibtexnetworking ) target_link_libraries( kbibtexiotest Qt5::Test kbibtexio ) target_link_libraries( kbibtexdatatest Qt5::Test kbibtexdata ) ecm_mark_as_test( kbibtexfilestest kbibtexnetworkingtest kbibtexiotest kbibtexdatatest ) add_test( NAME kbibtexfilestest COMMAND kbibtexfilestest ) add_test( NAME kbibtexnetworkingtest COMMAND kbibtexnetworkingtest ) add_test( NAME kbibtexiotest COMMAND kbibtexiotest ) add_test( NAME kbibtexdatatest COMMAND kbibtexdatatest )