diff --git a/mobile/sailfishos/ChangeLog b/mobile/sailfishos/ChangeLog new file mode 100644 index 00000000..6101295e --- /dev/null +++ b/mobile/sailfishos/ChangeLog @@ -0,0 +1,28 @@ +ChangeLog for BibSearch + +Version 0.5 (2019-01-xx) + * Migrating BibSearch to reside in KBibTeX's code repository. + * Latest code fixes from KBibTeX's 'master' branch. + +Version 0.4 (2018-08-04) + * Fixing various issues with search engines. + * Various minor UI improvements. + * Matching code between KBibTeX and BibSearch to increase + code sharing. + +Version 0.3 (2017-09-04) + * Support search in PubMed. + * User can enable/select which search engines to search in. + * Refactoring UI to follow more closely the Silica style. + * Matching code between KBibTeX and BibSearch to increase + code sharing + +Version 0.2 (2017-02-03) + * Search results can be sorted by various combinations of + publication date and first author's name. + * Various minor usability changes. + +Version 0.1 (2016-10-21) + * Support searching in ACM Digital Library, arXiv, Bibsonomy, + Google Scholar, IEEE Xplore, Ingenta Connect, JStor, Science + Direct, and Springer Link. diff --git a/mobile/sailfishos/README.md b/mobile/sailfishos/README.md new file mode 100644 index 00000000..004464e1 --- /dev/null +++ b/mobile/sailfishos/README.md @@ -0,0 +1,10 @@ +# BibSearch + +BibSearch is a SailfishOS application which allows to search for bibliographic data on scientific publications in various catalogs and publishers such as Springer, ACM, IEEE, Google Scholar, or arXiv. + +All catalogs will be searched in parallel for author, title, and free text as provided by the user. +The resulting list provides a quick overview on all matching bibliographic entries. Individual entries can be inspected for their full details. +If an entry has an URL or DOI associated, the corresponding webpage can be visited. + +Up to and including version 0.4, the core code was based on existing code derived from KBibTeX but maintained in a separate repository. To minimize the overhead for maintaining two related code bases, as of version 0.5 BibSearch resides inside KBibTeX's repository and shares code with it to the greatest extend. Indeed, BibSearch differs from KBibTeX only in respect to the user interface (BibSearch is QML-based), limited functionality (not all libraries are supported on mobile platforms), and *three* C++ source files not used in KBibTeX itself. + diff --git a/mobile/sailfishos/bibsearch.pro b/mobile/sailfishos/bibsearch.pro new file mode 100644 index 00000000..1149a092 --- /dev/null +++ b/mobile/sailfishos/bibsearch.pro @@ -0,0 +1,111 @@ +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/preferences.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/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/preferences.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/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 + +# to disable building translations every time, comment out the +# following CONFIG line +CONFIG += sailfishapp_i18n + +QT += xmlpatterns + +DEFINES += KBIBTEXCONFIG_EXPORT= KBIBTEXDATA_EXPORT= KBIBTEXIO_EXPORT= KBIBTEXNETWORKING_EXPORT= + +INCLUDEPATH += ../../src/data ../../src/networking ../../src/networking/onlinesearch ../../src/io ../../src/config ../../src/global + +# German translation is enabled as an example. If you aren't +# planning to localize your app, remember to comment out the +# following TRANSLATIONS line. And also do not forget to +# modify the localized app name in the the .desktop file. +##TRANSLATIONS += translations/$${TARGET}-de.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/mobile/sailfishos/harbour-bibsearch.desktop b/mobile/sailfishos/harbour-bibsearch.desktop new file mode 100644 index 00000000..749f1b85 --- /dev/null +++ b/mobile/sailfishos/harbour-bibsearch.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +X-Nemo-Application-Type=silica-qt5 +Icon=harbour-bibsearch +Exec=harbour-bibsearch +Name=BibSearch +# translation example: +# your app name in German locale (de) +# +# Remember to comment out the following line, if you do not want to use +# a different app name in German locale (de). +Name[de]=BibSearch + diff --git a/mobile/sailfishos/icons/108/harbour-bibsearch.png b/mobile/sailfishos/icons/108/harbour-bibsearch.png new file mode 100644 index 00000000..83a8c237 Binary files /dev/null and b/mobile/sailfishos/icons/108/harbour-bibsearch.png differ diff --git a/mobile/sailfishos/icons/128/harbour-bibsearch.png b/mobile/sailfishos/icons/128/harbour-bibsearch.png new file mode 100644 index 00000000..95efda44 Binary files /dev/null and b/mobile/sailfishos/icons/128/harbour-bibsearch.png differ diff --git a/mobile/sailfishos/icons/172/harbour-bibsearch.png b/mobile/sailfishos/icons/172/harbour-bibsearch.png new file mode 100644 index 00000000..f405f05c Binary files /dev/null and b/mobile/sailfishos/icons/172/harbour-bibsearch.png differ diff --git a/mobile/sailfishos/icons/256/harbour-bibsearch.png b/mobile/sailfishos/icons/256/harbour-bibsearch.png new file mode 100644 index 00000000..7d04d7a1 Binary files /dev/null and b/mobile/sailfishos/icons/256/harbour-bibsearch.png differ diff --git a/mobile/sailfishos/icons/86/harbour-bibsearch.png b/mobile/sailfishos/icons/86/harbour-bibsearch.png new file mode 100644 index 00000000..cef2383f Binary files /dev/null and b/mobile/sailfishos/icons/86/harbour-bibsearch.png differ diff --git a/src/data/element.h b/mobile/sailfishos/qml/BibSearch.qml similarity index 73% copy from src/data/element.h copy to mobile/sailfishos/qml/BibSearch.qml index 3942f3c4..d2bd1baa 100644 --- a/src/data/element.h +++ b/mobile/sailfishos/qml/BibSearch.qml @@ -1,41 +1,37 @@ /*************************************************************************** - * Copyright (C) 2004-2017 by Thomas Fischer * + * Copyright (C) 2016-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 BIBTEXELEMENT_H -#define BIBTEXELEMENT_H +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import harbour.bibsearch 1.0 +import "pages" -#include "file.h" - -#include "kbibtexdata_export.h" - -/** - * Base class for bibliographic elements in a BibTeX file. - * @author Thomas Fischer - */ -class KBIBTEXDATA_EXPORT Element +ApplicationWindow { -public: - Element(); - virtual ~Element() { - /* nothing */ + SortedBibliographyModel { + id: bibliographyModel } -private: - int uniqueId; -}; + SearchEngineList { + id: searchEngineList + } + + initialPage: Component { BibliographyListView { } } + cover: Qt.resolvedUrl("cover/CoverPage.qml") +} + -#endif diff --git a/src/data/element.h b/mobile/sailfishos/qml/cover/CoverPage.qml similarity index 73% copy from src/data/element.h copy to mobile/sailfishos/qml/cover/CoverPage.qml index 3942f3c4..5a6c008b 100644 --- a/src/data/element.h +++ b/mobile/sailfishos/qml/cover/CoverPage.qml @@ -1,41 +1,29 @@ /*************************************************************************** - * Copyright (C) 2004-2017 by Thomas Fischer * + * Copyright (C) 2016-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 BIBTEXELEMENT_H -#define BIBTEXELEMENT_H +import QtQuick 2.0 +import Sailfish.Silica 1.0 -#include "file.h" - -#include "kbibtexdata_export.h" - -/** - * Base class for bibliographic elements in a BibTeX file. - * @author Thomas Fischer - */ -class KBIBTEXDATA_EXPORT Element -{ -public: - Element(); - virtual ~Element() { - /* nothing */ +CoverBackground { + Label { + id: label + anchors.centerIn: parent + text: qsTr("BibSearch") } +} -private: - int uniqueId; -}; -#endif diff --git a/mobile/sailfishos/qml/pages/AboutPage.qml b/mobile/sailfishos/qml/pages/AboutPage.qml new file mode 100644 index 00000000..7bb90377 --- /dev/null +++ b/mobile/sailfishos/qml/pages/AboutPage.qml @@ -0,0 +1,124 @@ +/*************************************************************************** + * Copyright (C) 2016-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 . * + ***************************************************************************/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Page { + id: aboutPage + allowedOrientations: Orientation.All + + SilicaFlickable { + anchors.fill: parent + contentHeight: column.height + contentWidth: parent.width + + VerticalScrollDecorator { + } + + Item { + id: column + width: parent.width + height: childrenRect.height + + Column { + width: parent.width + spacing: Theme.horizontalPageMargin + + PageHeader { + id: header + title: qsTr("About BibSearch") + } + + Image { + source: "qrc:/icons/128/harbour-bibsearch.png" + anchors { + horizontalCenter: parent.horizontalCenter + } + } + + Label { + text: qsTr("Version 0.5") + anchors { + horizontalCenter: parent.horizontalCenter + } + } + + Label { + text: "\u00a9 2016\u20132018 Thomas Fischer" + anchors { + horizontalCenter: parent.horizontalCenter + } + } + + Rectangle { + height: Theme.horizontalPageMargin + width: parent.width + opacity: 0.0 + } + + Label { + text: "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." + width: parent.width - 2 * x + x: Theme.horizontalPageMargin + font.pointSize: Theme.fontSizeTiny + wrapMode: Text.WordWrap + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: "Source Code" + onClicked: { + Qt.openUrlExternally("https://gitlab.com/tfischer/BibSearch") + } + } + + Label { + text: "Based on KBibTeX, the bibliography editor using KDE technology." + width: parent.width - 2 * x + x: Theme.horizontalPageMargin + font.pointSize: Theme.fontSizeTiny + wrapMode: Text.WordWrap + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: "KBibTeX's Homepage" + onClicked: { + Qt.openUrlExternally("https://userbase.kde.org/KBibTeX") + } + } + + Rectangle { + height: 1 + width: parent.width + opacity: 0.0 + } + } + } + + PullDownMenu { + MenuItem { + id: menuItemIssueTracker + text: qsTr("Report Issue") + onClicked: { + Qt.openUrlExternally("https://gitlab.com/tfischer/BibSearch/issues") + } + } + } + } +} diff --git a/mobile/sailfishos/qml/pages/BibliographyListView.qml b/mobile/sailfishos/qml/pages/BibliographyListView.qml new file mode 100644 index 00000000..cde78e49 --- /dev/null +++ b/mobile/sailfishos/qml/pages/BibliographyListView.qml @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (C) 2016-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 . * + ***************************************************************************/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Page { + id: resultPage + allowedOrientations: Orientation.All + + SilicaListView { + id: bibliographyListView + model: bibliographyModel + + spacing: Theme.paddingMedium + anchors.fill: parent + + VerticalScrollDecorator { + enabled: bibliographyListView.count > 0 + } + + ViewPlaceholder { + enabled: bibliographyListView.count === 0 + text: bibliographyModel.busy ? qsTr("Waiting for results \u2026") : qsTr("Pull down to start a new search.") + } + + delegate: BackgroundItem { + id: delegate + width: parent.width + height: col.childrenRect.height + + Column { + id: col + width: parent.width - x + x: Theme.horizontalPageMargin + + Label { + text: authorShort + " (" + year + ")" + width: parent.width + font.pointSize: Theme.fontSizeSmall + clip: true + color: delegate.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor + truncationMode: TruncationMode.Fade + } + Label { + text: title + font.pointSize: Theme.fontSizeMedium + width: parent.width + clip: true + color: delegate.highlighted ? Theme.highlightColor : Theme.primaryColor + truncationMode: TruncationMode.Fade + } + Label { + visible: wherePublished.length > 0 + text: wherePublished + width: parent.width + clip: true + font.pointSize: Theme.fontSizeExtraSmall + color: delegate.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor + opacity: 0.9 + truncationMode: TruncationMode.Fade + } + } + + onClicked: pageStack.push("EntryView.qml", { + author: author, + title: title, + wherePublished: wherePublished, + year: year, + url: url, + doi: doi, + foundVia: foundVia + }) + } + + PullDownMenu { + MenuItem { + text: qsTr("About") + onClicked: pageStack.push("AboutPage.qml") + } + MenuItem { + text: qsTr("Settings") + onClicked: pageStack.push("SettingsPage.qml") + } + MenuItem { + text: qsTr("New Search") + onClicked: pageStack.push("SearchForm.qml") + } + } + } +} diff --git a/mobile/sailfishos/qml/pages/EntryView.qml b/mobile/sailfishos/qml/pages/EntryView.qml new file mode 100644 index 00000000..54c280b9 --- /dev/null +++ b/mobile/sailfishos/qml/pages/EntryView.qml @@ -0,0 +1,146 @@ +/*************************************************************************** + * Copyright (C) 2016-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 . * + ***************************************************************************/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Page { + id: entryView + allowedOrientations: Orientation.All + + property string author: "" + property string title: "" + property string wherePublished: "" + property string year: "" + property string url: "" + property string doi: "" + property string foundVia: "" + + SilicaFlickable { + anchors.fill: parent + contentHeight: content.height + + Column { + id: content + x: Theme.horizontalPageMargin + width: parent.width - x + + PageHeader { + title: entryView.title + } + + Label { + text: qsTr("Author") + color: Theme.highlightColor + font.pointSize: Theme.fontSizeExtraSmall + } + Label { + id: author + text: entryView.author + wrapMode: Text.WordWrap + width: parent.width - Theme.horizontalPageMargin + color: Theme.secondaryHighlightColor + font.pointSize: Theme.fontSizeMedium + } + + Label { + text: qsTr("Title") + color: Theme.highlightColor + font.pointSize: Theme.fontSizeExtraSmall + } + Label { + id: title + text: entryView.title + wrapMode: Text.WordWrap + width: parent.width - Theme.horizontalPageMargin + color: Theme.secondaryHighlightColor + font.pointSize: Theme.fontSizeMedium + } + Label { + visible: entryView.wherePublished.length > 0 + text: qsTr("Publication") + color: Theme.highlightColor + font.pointSize: Theme.fontSizeExtraSmall + } + Label { + id: wherePublished + text: entryView.wherePublished + visible: entryView.wherePublished.length > 0 + wrapMode: Text.Wrap + width: parent.width - Theme.horizontalPageMargin + color: Theme.secondaryHighlightColor + font.pointSize: Theme.fontSizeMedium + } + Label { + text: qsTr("Year") + color: Theme.highlightColor + font.pointSize: Theme.fontSizeExtraSmall + } + Label { + id: year + text: entryView.year + wrapMode: Text.WordWrap + width: parent.width - Theme.horizontalPageMargin + color: Theme.secondaryHighlightColor + font.pointSize: Theme.fontSizeMedium + } + Label { + visible: entryView.doi.length > 0 || entryView.url.length > 0 + text: entryView.doi.length > 0 ? qsTr("DOI") : qsTr("URL") + color: Theme.highlightColor + font.pointSize: Theme.fontSizeExtraSmall + } + Label { + id: urlOrDoi + visible: entryView.doi.length > 0 || entryView.url.length > 0 + text: entryView.doi.length > 0 ? entryView.doi : entryView.url + wrapMode: Text.WordWrap + width: parent.width - Theme.horizontalPageMargin + color: Theme.secondaryHighlightColor + font.pointSize: Theme.fontSizeMedium + } + Label { + visible: entryView.foundVia.length > 0 + text: qsTr("Found via") + color: Theme.highlightColor + font.pointSize: Theme.fontSizeExtraSmall + } + Label { + id: foundVia + text: entryView.foundVia + visible: entryView.foundVia.length > 0 + wrapMode: Text.Wrap + width: parent.width - Theme.horizontalPageMargin + color: Theme.secondaryHighlightColor + font.pointSize: Theme.fontSizeMedium + } + } + + PullDownMenu { + MenuItem { + id: menuItemViewOnline + text: qsTr("View Online") + enabled: entryView.url.length > 0 + onClicked: { + Qt.openUrlExternally(url) + } + } + + visible: menuItemViewOnline.enabled + } + } +} diff --git a/src/data/element.h b/mobile/sailfishos/qml/pages/SearchEngineListView.qml similarity index 54% copy from src/data/element.h copy to mobile/sailfishos/qml/pages/SearchEngineListView.qml index 3942f3c4..f8c6ed6e 100644 --- a/src/data/element.h +++ b/mobile/sailfishos/qml/pages/SearchEngineListView.qml @@ -1,41 +1,54 @@ /*************************************************************************** - * Copyright (C) 2004-2017 by Thomas Fischer * + * Copyright (C) 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 BIBTEXELEMENT_H -#define BIBTEXELEMENT_H +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import harbour.bibsearch 1.0 -#include "file.h" +Page { + id: searchEngineListViewPage + allowedOrientations: Orientation.All -#include "kbibtexdata_export.h" + SilicaFlickable { + id: resultList + anchors.fill: parent + contentHeight: content.height -/** - * Base class for bibliographic elements in a BibTeX file. - * @author Thomas Fischer - */ -class KBIBTEXDATA_EXPORT Element -{ -public: - Element(); - virtual ~Element() { - /* nothing */ - } + Column { + id: content + width: parent.width + + PageHeader { + title: "Available Search Engines" + } -private: - int uniqueId; -}; + SilicaListView { + id: searchEngineListView + model: searchEngineList + width: parent.width + height: childrenRect.height -#endif + delegate: TextSwitch { + width: parent.width + text: label + Component.onCompleted: checked = engineEnabled + onCheckedChanged: engineEnabled = checked + } + } + } + } +} diff --git a/mobile/sailfishos/qml/pages/SearchForm.qml b/mobile/sailfishos/qml/pages/SearchForm.qml new file mode 100644 index 00000000..339c88a3 --- /dev/null +++ b/mobile/sailfishos/qml/pages/SearchForm.qml @@ -0,0 +1,112 @@ +/*************************************************************************** + * Copyright (C) 2016-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 . * + ***************************************************************************/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Page { + id: searchForm + allowedOrientations: Orientation.All + + SilicaFlickable { + id: resultList + anchors.fill: parent + contentHeight: content.height + + function startSearching() { + bibliographyModel.clear(); + bibliographyModel.startSearch(inputFreeText.text, + inputTitle.text, + inputAuthor.text); + pop(); + } + + Column { + id: content + width: parent.width + + PageHeader { + title: "Enter Search Parameters" + } + + ValueButton { + label: qsTr("Search Engines") + value: searchEngineList.searchEngineCount === 0 + ? qsTr("None selected") + : qsTr("%1 selected").arg(searchEngineList.searchEngineCount) + description: searchEngineList.searchEngineCount === 0 + ? qsTr("At least one search engine must be selected.") + : searchEngineList.humanReadableSearchEngines() + + onClicked: { + pageStack.push("SearchEngineListView.qml") + } + } + + TextField { + id: inputFreeText + label: qsTr("Free text") + placeholderText: label + width: parent.width + focus: true + enabled: searchEngineList.searchEngineCount > 0 + + EnterKey.enabled: text.length > 0 + EnterKey.iconSource: "image://theme/icon-m-search" + EnterKey.onClicked: resultList.startSearching() + } + + TextField { + id: inputTitle + label: qsTr("Title") + placeholderText: label + width: parent.width + enabled: searchEngineList.searchEngineCount > 0 + + EnterKey.enabled: text.length > 0 + EnterKey.iconSource: "image://theme/icon-m-search" + EnterKey.onClicked: resultList.startSearching() + } + + TextField { + id: inputAuthor + label: qsTr("Author") + placeholderText: label + width: parent.width + enabled: searchEngineList.searchEngineCount > 0 + + EnterKey.enabled: text.length > 0 + EnterKey.iconSource: "image://theme/icon-m-search" + EnterKey.onClicked: resultList.startSearching() + } + } + + PullDownMenu { + MenuItem { + id: menuItemStartSearching + text: qsTr("Start Searching") + enabled: (inputFreeText.text.length > 0 + || inputTitle.text.length > 0 + || inputAuthor.text.length > 0) + && searchEngineList.searchEngineCount > 0 + onClicked: resultList.startSearching() + } + + visible: menuItemStartSearching.enabled + } + } +} diff --git a/mobile/sailfishos/qml/pages/SettingsPage.qml b/mobile/sailfishos/qml/pages/SettingsPage.qml new file mode 100644 index 00000000..889bf4ca --- /dev/null +++ b/mobile/sailfishos/qml/pages/SettingsPage.qml @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 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 . * + ***************************************************************************/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import harbour.bibsearch 1.0 + +Page { + id: settingsPage + allowedOrientations: Orientation.All + + SilicaFlickable { + anchors.fill: parent + contentHeight: column.height + contentWidth: parent.width + + VerticalScrollDecorator { + } + + Column { + id: column + width: parent.width + + PageHeader { + title: qsTr("Settings") + } + + ComboBox { + id: sortOrder + label: qsTr("Sort Order") + currentIndex: bibliographyModel.sortOrder + menu: ContextMenu { + Repeater { + model: bibliographyModel.humanReadableSortOrder() + MenuItem { + text: modelData + } + } + } + onCurrentItemChanged: { + bibliographyModel.sortOrder = currentIndex + } + } + + ValueButton { + label: qsTr("Search Engines") + value: searchEngineList.searchEngineCount === 0 + ? qsTr("None selected") + : qsTr("%1 selected").arg(searchEngineList.searchEngineCount) + description: searchEngineList.searchEngineCount === 0 + ? qsTr("At least one search engine must be selected.") + : searchEngineList.humanReadableSearchEngines() + + onClicked: { + pageStack.push("SearchEngineListView.qml") + } + } + } + } +} diff --git a/mobile/sailfishos/rpm/harbour-bibsearch.spec b/mobile/sailfishos/rpm/harbour-bibsearch.spec new file mode 100644 index 00000000..bca0ebd5 --- /dev/null +++ b/mobile/sailfishos/rpm/harbour-bibsearch.spec @@ -0,0 +1,71 @@ +# +# Do NOT Edit the Auto-generated Part! +# Generated by: spectacle version 0.27 +# + +Name: harbour-bibsearch + +# >> macros +# << macros + +%{!?qtc_qmake:%define qtc_qmake %qmake} +%{!?qtc_qmake5:%define qtc_qmake5 %qmake5} +%{!?qtc_make:%define qtc_make make} +%{?qtc_builddir:%define _builddir %qtc_builddir} +Summary: BibSearch +Version: 0.5 +Release: 1 +Group: Applications/Publishing +License: GNU General Public License version 2 or any later version +URL: https://gitlab.com/tfischer/BibSearch +Source0: %{name}-%{version}.tar.xz +Source100: harbour-bibsearch.yaml +Requires: sailfishsilica-qt5 >= 0.10.9 +BuildRequires: pkgconfig(sailfishapp) >= 1.0.2 +BuildRequires: pkgconfig(Qt5Core) +BuildRequires: pkgconfig(Qt5Qml) +BuildRequires: pkgconfig(Qt5Quick) +BuildRequires: desktop-file-utils + +%description +A mobile app to search for scientific publications, based on KBibTeX + + +%prep +%setup -q -n %{name}-%{version} + +# >> setup +# << setup + +%build +# >> build pre +# << build pre + +%qtc_qmake5 + +%qtc_make %{?_smp_mflags} + +# >> build post +# << build post + +%install +rm -rf %{buildroot} +# >> install pre +# << install pre +%qmake5_install + +# >> install post +# << install post + +desktop-file-install --delete-original \ + --dir %{buildroot}%{_datadir}/applications \ + %{buildroot}%{_datadir}/applications/*.desktop + +%files +%defattr(-,root,root,-) +%{_bindir} +%{_datadir}/%{name} +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png +# >> files +# << files diff --git a/mobile/sailfishos/rpm/harbour-bibsearch.yaml b/mobile/sailfishos/rpm/harbour-bibsearch.yaml new file mode 100644 index 00000000..a5364716 --- /dev/null +++ b/mobile/sailfishos/rpm/harbour-bibsearch.yaml @@ -0,0 +1,46 @@ +Name: harbour-bibsearch +Summary: BibSearch +Version: 0.5 +Release: 1 +# The contents of the Group field should be one of the groups listed here: +# https://github.com/mer-tools/spectacle/raw/master/data/GROUPS +Group: Applications/Publishing +URL: https://gitlab.com/tfischer/BibSearch +License: GNU General Public License version 2 or any later version +# This must be generated before uploading a package to a remote build service. +# Usually this line does not need to be modified. +Sources: +- '%{name}-%{version}.tar.xz' +Description: | + A mobile app to search for scientific publications, based on KBibTeX +Configure: none +# The qtc5 builder inserts macros to allow QtCreator to have fine +# control over qmake/make execution +Builder: qtc5 + +# This section specifies build dependencies that are resolved using pkgconfig. +# This is the preferred way of specifying build dependencies for your package. +PkgConfigBR: + - sailfishapp >= 1.0.2 + - Qt5Core + - Qt5Qml + - Qt5Quick + +# Build dependencies without a pkgconfig setup can be listed here +# PkgBR: +# - package-needed-to-build + +# Runtime dependencies which are not automatically detected +Requires: + - sailfishsilica-qt5 >= 0.10.9 + +# All installed files +Files: + - '%{_bindir}' + - '%{_datadir}/%{name}' + - '%{_datadir}/applications/%{name}.desktop' + - '%{_datadir}/icons/hicolor/*/apps/%{name}.png' + +# For more information about yaml and what's supported in Sailfish OS +# build system, please see https://wiki.merproject.org/wiki/Spectacle + diff --git a/mobile/sailfishos/sailfishos_res.qrc b/mobile/sailfishos/sailfishos_res.qrc new file mode 100644 index 00000000..89091b2f --- /dev/null +++ b/mobile/sailfishos/sailfishos_res.qrc @@ -0,0 +1,5 @@ + + + icons/128/harbour-bibsearch.png + + diff --git a/mobile/sailfishos/src/bibliographymodel.cpp b/mobile/sailfishos/src/bibliographymodel.cpp new file mode 100644 index 00000000..6557fd35 --- /dev/null +++ b/mobile/sailfishos/src/bibliographymodel.cpp @@ -0,0 +1,414 @@ +/*************************************************************************** + * Copyright (C) 2016-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 "bibliographymodel.h" + +#include + +#include "entry.h" +#include "onlinesearchabstract.h" + +const int SortedBibliographyModel::SortAuthorNewestTitle = 0; +const int SortedBibliographyModel::SortAuthorOldestTitle = 1; +const int SortedBibliographyModel::SortNewestAuthorTitle = 2; +const int SortedBibliographyModel::SortOldestAuthorTitle = 3; + +SortedBibliographyModel::SortedBibliographyModel() + : QSortFilterProxyModel(), m_sortOrder(0), model(new BibliographyModel()) { + const QSettings settings(QStringLiteral("harbour-bibsearch"), QStringLiteral("BibSearch")); + m_sortOrder = settings.value(QStringLiteral("sortOrder"), 0).toInt(); + + setSourceModel(model); + setDynamicSortFilter(true); + sort(0); + connect(model, &BibliographyModel::busyChanged, this, &SortedBibliographyModel::busyChanged); +} + +SortedBibliographyModel::~SortedBibliographyModel() { + QSettings settings(QStringLiteral("harbour-bibsearch"), QStringLiteral("BibSearch")); + settings.setValue(QStringLiteral("sortOrder"), m_sortOrder); + + delete model; +} + +QHash SortedBibliographyModel::roleNames() const { + QHash roles; + roles[BibTeXIdRole] = "bibtexid"; + roles[FoundViaRole] = "foundVia"; + roles[TitleRole] = "title"; + roles[AuthorRole] = "author"; + roles[AuthorShortRole] = "authorShort"; + roles[YearRole] = "year"; + roles[WherePublishedRole] = "wherePublished"; + roles[UrlRole] = "url"; + roles[DoiRole] = "doi"; + return roles; +} + +bool SortedBibliographyModel::isBusy() const { + if (model != nullptr) + return model->isBusy(); + else + return false; +} + +int SortedBibliographyModel::sortOrder() const { + return m_sortOrder; +} + +void SortedBibliographyModel::setSortOrder(int _sortOrder) { + if (_sortOrder != m_sortOrder) { + m_sortOrder = _sortOrder; + invalidate(); + emit sortOrderChanged(m_sortOrder); + } +} + +QStringList SortedBibliographyModel::humanReadableSortOrder() const { + static const QStringList result { + tr("Last name, newest first"), ///< SortAuthorNewestTitle + tr("Last name, oldest first"), ///< SortAuthorOldestTitle + tr("Newest first, last name"), ///< SortNewestAuthorTitle + tr("Oldest first, last name") ///< SortOldestAuthorTitle + }; + return result; +} + +void SortedBibliographyModel::startSearch(const QString &freeText, const QString &title, const QString &author) { + if (model != nullptr) + model->startSearch(freeText, title, author); +} + +void SortedBibliographyModel::clear() { + if (model != nullptr) + model->clear(); +} + +bool SortedBibliographyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { + if (model == nullptr) + return source_left.row() < source_right.row(); + + const QSharedPointer entryLeft = model->entry(source_left.row()); + const QSharedPointer entryRight = model->entry(source_right.row()); + if (entryLeft.isNull() || entryRight.isNull()) + return source_left.row() < source_right.row(); + + SortingTriState sortingTriState = Undecided; + switch (m_sortOrder) { + case SortAuthorNewestTitle: + sortingTriState = compareTitles(entryLeft, entryRight, compareYears(entryLeft, entryRight, MostRecentFirst, compareAuthors(entryLeft, entryRight))); + break; + case SortAuthorOldestTitle: + sortingTriState = compareTitles(entryLeft, entryRight, compareYears(entryLeft, entryRight, LeastRecentFirst, compareAuthors(entryLeft, entryRight))); + break; + case SortNewestAuthorTitle: + sortingTriState = compareTitles(entryLeft, entryRight, compareAuthors(entryLeft, entryRight, compareYears(entryLeft, entryRight, MostRecentFirst))); + break; + case SortOldestAuthorTitle: + sortingTriState = compareTitles(entryLeft, entryRight, compareAuthors(entryLeft, entryRight, compareYears(entryLeft, entryRight, LeastRecentFirst))); + break; + default: + sortingTriState = Undecided; + } + + switch (sortingTriState) { + case True: + return true; + case False: + return false; + default: + return (source_left.row() < source_right.row()); + } +} + +SortedBibliographyModel::SortingTriState SortedBibliographyModel::compareAuthors(const QSharedPointer entryLeft, const QSharedPointer entryRight, SortedBibliographyModel::SortingTriState previousDecision) const { + if (previousDecision != Undecided) return previousDecision; + + const Value authorsLeft = entryLeft->operator[](Entry::ftAuthor); + const Value authorsRight = entryRight->operator[](Entry::ftAuthor); + int p = 0; + while (p < authorsLeft.count() && p < authorsRight.count()) { + const QSharedPointer personLeft = authorsLeft.at(p).dynamicCast(); + const QSharedPointer personRight = authorsRight.at(p).dynamicCast(); + if (personLeft.isNull() || personRight.isNull()) + return Undecided; + + const int cmpLast = removeNobiliaryParticle(personLeft->lastName()).localeAwareCompare(removeNobiliaryParticle(personRight->lastName())); + if (cmpLast < 0) return True; + else if (cmpLast > 0) return False; + const int cmpFirst = personLeft->firstName().left(1).localeAwareCompare(personRight->firstName().left(1)); + if (cmpFirst < 0) return True; + else if (cmpFirst > 0) return False; + + ++p; + } + if (authorsLeft.count() < authorsRight.count()) return True; + else if (authorsLeft.count() > authorsRight.count()) return False; + + return Undecided; +} + +SortedBibliographyModel::SortingTriState SortedBibliographyModel::compareYears(const QSharedPointer entryLeft, const QSharedPointer entryRight, AgeSorting ageSorting, SortedBibliographyModel::SortingTriState previousDecision) const { + if (previousDecision != Undecided) return previousDecision; + + bool yearLeftOk = false, yearRightOk = false; + const int yearLeft = BibliographyModel::valueToText(entryLeft->operator[](Entry::ftYear)).toInt(&yearLeftOk); + const int yearRight = BibliographyModel::valueToText(entryRight->operator[](Entry::ftYear)).toInt(&yearRightOk); + if (yearLeftOk && yearRightOk && yearLeft > 1000 && yearRight > 1000 && yearLeft < 3000 && yearRight < 3000) { + if (yearLeft < yearRight) return ageSorting == LeastRecentFirst ? True : False; + else if (yearLeft > yearRight) return ageSorting == LeastRecentFirst ? False : True; + } + + return Undecided; +} + +SortedBibliographyModel::SortingTriState SortedBibliographyModel::compareTitles(const QSharedPointer entryLeft, const QSharedPointer entryRight, SortedBibliographyModel::SortingTriState previousDecision) const { + if (previousDecision != Undecided) return previousDecision; + + const QString titleLeft = BibliographyModel::valueToText(entryLeft->operator[](Entry::ftTitle)); + const QString titleRight = BibliographyModel::valueToText(entryRight->operator[](Entry::ftTitle)); + const int titleCmp = titleLeft.localeAwareCompare(titleRight); + if (titleCmp < 0) return True; + else if (titleCmp > 0) return False; + + return Undecided; +} + +QString SortedBibliographyModel::removeNobiliaryParticle(const QString &lastname) const { + static const QStringList nobiliaryParticles {QStringLiteral("af "), QStringLiteral("d'"), QStringLiteral("de "), QStringLiteral("di "), QStringLiteral("du "), QStringLiteral("of "), QStringLiteral("van "), QStringLiteral("von "), QStringLiteral("zu ")}; + for (QStringList::ConstIterator it = nobiliaryParticles.constBegin(); it != nobiliaryParticles.constEnd(); ++it) + if (lastname.startsWith(*it)) + return lastname.mid(it->length()); + return lastname; +} + +BibliographyModel::BibliographyModel() { + m_file = new File(); + m_runningSearches = 0; + + m_searchEngineList = new SearchEngineList(); + connect(m_searchEngineList, &SearchEngineList::foundEntry, this, &BibliographyModel::newEntry); + connect(m_searchEngineList, &SearchEngineList::busyChanged, this, &BibliographyModel::busyChanged); +} + +BibliographyModel::~BibliographyModel() { + delete m_file; + delete m_searchEngineList; +} + +int BibliographyModel::rowCount(const QModelIndex &parent) const { + if (parent == QModelIndex()) + return m_file->count(); + else + return 0; +} + +QVariant BibliographyModel::data(const QModelIndex &index, int role) const { + if (index.row() < 0 || index.row() >= m_file->count() || index.column() != 0) + return QVariant(); + + const QSharedPointer curEntry = entry(index.row()); + + if (!curEntry.isNull()) { + QString fieldName; + switch (role) { + case Qt::DisplayRole: /// fall-through on purpose + case TitleRole: fieldName = Entry::ftTitle; break; + case AuthorRole: fieldName = Entry::ftAuthor; break; + case YearRole: fieldName = Entry::ftYear; break; + } + if (!fieldName.isEmpty()) + return valueToText(curEntry->operator[](fieldName)); + + if (role == BibTeXIdRole) { + return curEntry->id(); + } else if (role == FoundViaRole) { + const QString foundVia = valueToText(curEntry->operator[](QStringLiteral("x-fetchedfrom"))); + if (!foundVia.isEmpty()) return foundVia; + } else if (role == AuthorRole) { + const QString authors = valueToText(curEntry->operator[](Entry::ftAuthor)); + if (!authors.isEmpty()) return authors; + else return valueToText(curEntry->operator[](Entry::ftEditor)); + } else if (role == WherePublishedRole) { + const QString journal = valueToText(curEntry->operator[](Entry::ftJournal)); + if (!journal.isEmpty()) { + const QString volume = valueToText(curEntry->operator[](Entry::ftVolume)); + const QString issue = valueToText(curEntry->operator[](Entry::ftNumber)); + if (volume.isEmpty()) + return journal; + else if (issue.isEmpty()) /// but 'volume' is not empty + return journal + QStringLiteral(" ") + volume; + else /// both 'volume' and 'issue' are not empty + return journal + QStringLiteral(" ") + volume + QStringLiteral(" (") + issue + QStringLiteral(")"); + } + const QString bookTitle = valueToText(curEntry->operator[](Entry::ftBookTitle)); + if (!bookTitle.isEmpty()) return bookTitle; + const bool isPhdThesis = curEntry->type() == Entry::etPhDThesis; + if (isPhdThesis) { + const QString school = valueToText(curEntry->operator[](Entry::ftSchool)); + if (school.isEmpty()) + return tr("Doctoral dissertation"); + else + return tr("Doctoral dissertation (%1)").arg(school); + } + const QString school = valueToText(curEntry->operator[](Entry::ftSchool)); + if (!school.isEmpty()) return school; + const QString publisher = valueToText(curEntry->operator[](Entry::ftPublisher)); + if (!publisher.isEmpty()) return publisher; + return QStringLiteral(""); + } else if (role == AuthorShortRole) { + const QStringList authors = valueToList(curEntry->operator[](Entry::ftAuthor)); + switch (authors.size()) { + case 0: return QString(); ///< empty list of authors + case 1: return authors.first(); ///< single author + case 2: return tr("%1 and %2").arg(authors.first()).arg(authors[1]); ///< two authors + default: return tr("%1 and %2 more").arg(authors.first()).arg(QString::number(authors.size() - 1)); ///< three or more authors + } + } else if (role == UrlRole) { + const QStringList doiList = valueToList(curEntry->operator[](Entry::ftDOI)); + if (!doiList.isEmpty()) return QStringLiteral("http://dx.doi.org/") + doiList.first(); + const QStringList urlList = valueToList(curEntry->operator[](Entry::ftUrl)); + if (!urlList.isEmpty()) return urlList.first(); + const QStringList bibUrlList = valueToList(curEntry->operator[](QStringLiteral("biburl"))); + if (!bibUrlList.isEmpty()) return bibUrlList.first(); + return QString(); + } else if (role == DoiRole) { + const QStringList doiList = valueToList(curEntry->operator[](Entry::ftDOI)); + if (!doiList.isEmpty()) return doiList.first(); + return QString(); + } + } + + return QVariant(); +} + +const QSharedPointer BibliographyModel::entry(int row) const { + const QSharedPointer element = m_file->at(row); + const QSharedPointer result = element.dynamicCast(); + return result; +} + +void BibliographyModel::startSearch(const QString &freeText, const QString &title, const QString &author) { + QMap query; + query[OnlineSearchAbstract::queryKeyFreeText] = freeText; + query[OnlineSearchAbstract::queryKeyTitle] = title; + query[OnlineSearchAbstract::queryKeyAuthor] = author; + + m_runningSearches = 0; + const QSettings settings(QStringLiteral("harbour-bibsearch"), QStringLiteral("BibSearch")); + for (int i = 0; i < m_searchEngineList->size(); ++i) { + OnlineSearchAbstract *osa = m_searchEngineList->at(i); + const bool doSearchThisEngine = isSearchEngineEnabled(settings, osa); + if (!doSearchThisEngine) continue; + osa->startSearch(query, 10 /** TODO make number configurable */); + ++m_runningSearches; + } +} + +void BibliographyModel::clear() { + beginResetModel(); + m_file->clear(); + endResetModel(); +} + +bool BibliographyModel::isBusy() const { + for (QVector::ConstIterator it = m_searchEngineList->constBegin(); it != m_searchEngineList->constEnd(); ++it) { + if ((*it)->busy()) return true; + } + return false; +} + +void BibliographyModel::searchFinished() { + --m_runningSearches; +} + +void BibliographyModel::newEntry(QSharedPointer e) { + const int n = m_file->count(); + beginInsertRows(QModelIndex(), n, n); + m_file->insert(n, e); + endInsertRows(); +} + +QString BibliographyModel::valueToText(const Value &value) { + return valueToList(value).join(QStringLiteral(", ")); +} + + +QStringList BibliographyModel::valueToList(const Value &value) { + if (value.isEmpty()) return QStringList(); + + QStringList resultItems; + + const QSharedPointer firstPerson = value.first().dynamicCast(); + if (!firstPerson.isNull()) { + /// First item in value is a Person, assume all other items are Persons as well + for (Value::ConstIterator it = value.constBegin(); it != value.constEnd(); ++it) { + QSharedPointer person = (*it).dynamicCast(); + if (person.isNull()) continue; + const QString name = personToText(person); + if (name.isEmpty()) continue; + resultItems.append(beautifyLaTeX(name)); + } + } else { + for (Value::ConstIterator it = value.constBegin(); it != value.constEnd(); ++it) { + const QString valueItem = valueItemToText(*it); + if (valueItem.isEmpty()) continue; + resultItems.append(beautifyLaTeX(valueItem)); + } + } + + return resultItems; +} + +QString BibliographyModel::personToText(const QSharedPointer &person) { + if (person.isNull()) return QString(); + QString name = person->lastName(); + if (name.isEmpty()) return QString(); + const QString firstName = person->firstName().left(1); + if (!firstName.isEmpty()) + name = name.prepend(QStringLiteral(". ")).prepend(firstName); + return name; +} + +QString BibliographyModel::valueItemToText(const QSharedPointer &valueItem) { + const QSharedPointer plainText = valueItem.dynamicCast<PlainText>(); + if (!plainText.isNull()) + return plainText->text(); + else { + const QSharedPointer<VerbatimText> verbatimText = valueItem.dynamicCast<VerbatimText>(); + if (!verbatimText.isNull()) + return verbatimText->text(); + else { + const QSharedPointer<MacroKey> macroKey = valueItem.dynamicCast<MacroKey>(); + if (!macroKey.isNull()) + return macroKey->text(); + else { + // TODO + return QString(); + } + } + } +} + +QString BibliographyModel::beautifyLaTeX(const QString &input) { + QString output = input; + static const QStringList toBeRemoved {QStringLiteral("\\textsuperscript{"), QStringLiteral("\\}"), QStringLiteral("\\{"), QStringLiteral("}"), QStringLiteral("{")}; + for (QStringList::ConstIterator it = toBeRemoved.constBegin(); it != toBeRemoved.constEnd(); ++it) + output = output.remove(*it); + + return output; +} diff --git a/mobile/sailfishos/src/bibliographymodel.h b/mobile/sailfishos/src/bibliographymodel.h new file mode 100644 index 00000000..818e3891 --- /dev/null +++ b/mobile/sailfishos/src/bibliographymodel.h @@ -0,0 +1,114 @@ +/*************************************************************************** + * Copyright (C) 2016-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 BIBLIOGRAPHY_MODEL_H +#define BIBLIOGRAPHY_MODEL_H + +#include <QAbstractListModel> +#include <QSortFilterProxyModel> +#include <QSet> + +#include "file.h" +#include "value.h" +#include "entry.h" +#include "searchenginelist.h" + +class OnlineSearchAbstract; +class BibliographyModel; + +class BibliographyRoles +{ +public: + enum Roles {BibTeXIdRole = Qt::UserRole + 100, FoundViaRole = Qt::UserRole + 102, EntryRole = Qt::UserRole + 999, TitleRole = Qt::UserRole + 1000, AuthorRole = Qt::UserRole + 1010, AuthorShortRole = Qt::UserRole + 1011, YearRole = Qt::UserRole + 1020, WherePublishedRole = Qt::UserRole + 1100, UrlRole = Qt::UserRole + 1110, DoiRole = Qt::UserRole + 1111}; +}; + +class SortedBibliographyModel : public QSortFilterProxyModel, public BibliographyRoles { + Q_OBJECT +public: + static const int SortAuthorNewestTitle, SortAuthorOldestTitle, SortNewestAuthorTitle, SortOldestAuthorTitle; + + SortedBibliographyModel(); + ~SortedBibliographyModel(); + + virtual QHash<int, QByteArray> roleNames() const; + + bool isBusy() const; + int sortOrder() const; + void setSortOrder(int sortOrder); + Q_PROPERTY(int sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged) + Q_INVOKABLE QStringList humanReadableSortOrder() const; + + Q_INVOKABLE void startSearch(const QString &freeText, const QString &title, const QString &author); + Q_INVOKABLE void clear(); + Q_PROPERTY(bool busy READ isBusy NOTIFY busyChanged) + +signals: + void busyChanged(); + void sortOrderChanged(int sortOrder); + +protected: + virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + +private: + enum SortingTriState {True = -1, Undecided = 0, False = 1}; + enum AgeSorting {MostRecentFirst = 0, LeastRecentFirst = 1}; + + SortingTriState compareAuthors(const QSharedPointer<const Entry> entryLeft, const QSharedPointer<const Entry> entryRight, SortingTriState previousDecision = Undecided) const; + SortingTriState compareYears(const QSharedPointer<const Entry> entryLeft, const QSharedPointer<const Entry> entryRight, AgeSorting ageSorting, SortingTriState previousDecision = Undecided) const; + SortingTriState compareTitles(const QSharedPointer<const Entry> entryLeft, const QSharedPointer<const Entry> entryRight, SortingTriState previousDecision = Undecided) const; + + int m_sortOrder; + BibliographyModel *model; + + QString removeNobiliaryParticle(const QString &lastname) const; +}; + +class BibliographyModel : public QAbstractListModel, public BibliographyRoles { + Q_OBJECT +public: + explicit BibliographyModel(); + ~BibliographyModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + const QSharedPointer<const Entry> entry(int row) const; + static QString valueToText(const Value &value); + + bool isBusy() const; + + void startSearch(const QString &freeText, const QString &title, const QString &author); + void clear(); + +signals: + void busyChanged(); + +private slots: + void newEntry(QSharedPointer<Entry>); + void searchFinished(); + +private: + File *m_file; + int m_runningSearches; + SearchEngineList *m_searchEngineList; + + static QStringList valueToList(const Value &value); + static QString personToText(const QSharedPointer<const Person> &person); + static QString valueItemToText(const QSharedPointer<ValueItem> &valueItem); + static QString beautifyLaTeX(const QString &input); +}; + +#endif // BIBLIOGRAPHY_MODEL_H diff --git a/mobile/sailfishos/src/kbibtexnamespace.h b/mobile/sailfishos/src/kbibtexnamespace.h new file mode 100644 index 00000000..ee68f572 --- /dev/null +++ b/mobile/sailfishos/src/kbibtexnamespace.h @@ -0,0 +1,110 @@ +/*************************************************************************** + * 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_NAMESPACE_H +#define KBIBTEX_NAMESPACE_H + +#include <QVariant> +#include <QRegExp> +#include <QUrl> + +namespace KBibTeX +{ + +const QString extensionTeX = QLatin1String(".tex"); +const QString extensionAux = QLatin1String(".aux"); +const QString extensionBBL = QLatin1String(".bbl"); +const QString extensionBLG = QLatin1String(".blg"); +const QString extensionBibTeX = QLatin1String(".bib"); +const QString extensionPDF = QLatin1String(".pdf"); +const QString extensionPostScript = QLatin1String(".ps"); +const QString extensionRTF = QLatin1String(".rtf"); + +enum Casing { + cLowerCase = 0, + cInitialCapital = 1, + cUpperCamelCase = 2, + cLowerCamelCase = 3, + cUpperCase = 4 +}; + +enum FieldInputType { + SingleLine = 1, + MultiLine = 2, + List = 3, + URL = 4, + Month = 5, + Color = 6, + PersonList = 7, + UrlList = 8, + KeywordList = 9, + CrossRef = 10, + StarRating = 11 +}; + +enum TypeFlag { + tfPlainText = 0x1, + tfReference = 0x2, + tfPerson = 0x4, + tfKeyword = 0x8, + tfVerbatim = 0x10, + tfSource = 0x100 +}; +Q_DECLARE_FLAGS(TypeFlags, TypeFlag) + +Q_DECLARE_OPERATORS_FOR_FLAGS(TypeFlags) + +static const QString MonthsTriple[] = { + QLatin1String("jan"), QLatin1String("feb"), QLatin1String("mar"), QLatin1String("apr"), QLatin1String("may"), QLatin1String("jun"), QLatin1String("jul"), QLatin1String("aug"), QLatin1String("sep"), QLatin1String("oct"), QLatin1String("nov"), QLatin1String("dec") +}; + +static const QRegExp fileListSeparatorRegExp(QStringLiteral("[ \\t]*[;\\n]+[ \\t]*")); +static const QRegExp fileRegExp(QStringLiteral("(\\bfile:)?[^{}\\t]+\\.\\w{2,4}\\b"), Qt::CaseInsensitive); +static const QRegExp urlRegExp(QStringLiteral("\\b(http|s?ftp|webdav|file)s?://[^ {}\"]+(\\b|[/])"), Qt::CaseInsensitive); +static const QRegExp doiRegExp(QStringLiteral("10([.][0-9]+)+/[/-a-z0-9.()<>_:;\\\\]+"), Qt::CaseInsensitive); +static const QRegExp mendeleyFileRegExp(QStringLiteral(":(.*):pdf"), Qt::CaseInsensitive); +static const QString doiUrlPrefix = QLatin1String("https://dx.doi.org/"); ///< use FileInfo::doiUrlPrefix() instead +static const QRegExp domainNameRegExp(QStringLiteral("[a-z0-9.-]+\\.((a[cdefgilmnoqrstuwxz]|aero|arpa)|(b[abdefghijmnorstvwyz]|biz)|(c[acdfghiklmnorsuvxyz]|cat|com|coop)|d[ejkmoz]|(e[ceghrstu]|edu)|f[ijkmor]|(g[abdefghilmnpqrstuwy]|gov)|h[kmnrtu]|(i[delmnoqrst]|info|int)|(j[emop]|jobs)|k[eghimnprwyz]|l[abcikrstuvy]|(m[acdghklmnopqrstuvwxyz]|me|mil|mobi|museum)|(n[acefgilopruz]|name|net)|(om|org)|(p[aefghklmnrstwy]|pro)|qa|r[eouw]|s[abcdeghijklmnortvyz]|(t[cdfghjklmnoprtvwz]|travel)|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw])\\b"), Qt::CaseInsensitive); +static const QRegExp htmlRegExp = QRegExp(QStringLiteral("</?(a|pre|p|br|span|i|b|italic)\\b[^>{}]{,32}>"), Qt::CaseInsensitive); + +} + +inline static bool isLocalOrRelative(const QUrl &url) +{ + return url.isLocalFile() || url.isRelative() || url.scheme().isEmpty(); +} + +/** + * Poor man's variant of a text-squeezing function. + * Effect is similar as observed in KSqueezedTextLabel: + * If the text is longer as n characters, the middle part + * will be cut away and replaced by "..." to get a + * string of max n characters. + */ +inline static QString squeezeText(const QString &text, int n) +{ + return text.length() <= n ? text : text.left(n / 2 - 1) + QLatin1String("...") + text.right(n / 2 - 2); +} + +inline static QString leftSqueezeText(const QString &text, int n) +{ + return text.length() <= n ? text : text.left(n) + QLatin1String("..."); +} + +#define squeeze_text(text, n) ((text).length()<=(n)?(text):(text).left((n)/2-1)+QLatin1String("...")+(text).right((n)/2-2)) + +#endif // KBIBTEX_NAMESPACE_H diff --git a/src/data/element.h b/mobile/sailfishos/src/main.cpp similarity index 57% copy from src/data/element.h copy to mobile/sailfishos/src/main.cpp index 3942f3c4..b321e1be 100644 --- a/src/data/element.h +++ b/mobile/sailfishos/src/main.cpp @@ -1,41 +1,44 @@ /*************************************************************************** - * Copyright (C) 2004-2017 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * + * Copyright (C) 2016-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/>. * + * along with this program; if not, see <http://www.gnu.org/licenses/>. * ***************************************************************************/ -#ifndef BIBTEXELEMENT_H -#define BIBTEXELEMENT_H +#include <QtQml> +#ifdef QT_QML_DEBUG +#include <QtQuick> +#endif + +#include <QQuickView> +#include <QScopedPointer> +#include <QGuiApplication> -#include "file.h" +#include <sailfishapp.h> -#include "kbibtexdata_export.h" +#include "bibliographymodel.h" +#include "searchenginelist.h" -/** - * Base class for bibliographic elements in a BibTeX file. - * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> - */ -class KBIBTEXDATA_EXPORT Element +int main(int argc, char *argv[]) { -public: - Element(); - virtual ~Element() { - /* nothing */ - } + QScopedPointer<QGuiApplication> app(SailfishApp::application(argc, argv)); + QScopedPointer<QQuickView> view(SailfishApp::createView()); -private: - int uniqueId; -}; + qmlRegisterType<SortedBibliographyModel>("harbour.bibsearch", 1, 0, "SortedBibliographyModel"); + qmlRegisterType<SearchEngineList>("harbour.bibsearch", 1, 0, "SearchEngineList"); -#endif + view->setSource(SailfishApp::pathTo("qml/BibSearch.qml")); + view->show(); + + return app->exec(); +} diff --git a/mobile/sailfishos/src/searchenginelist.cpp b/mobile/sailfishos/src/searchenginelist.cpp new file mode 100644 index 00000000..ec963280 --- /dev/null +++ b/mobile/sailfishos/src/searchenginelist.cpp @@ -0,0 +1,183 @@ +/*************************************************************************** + * Copyright (C) 2017-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 "searchenginelist.h" + +#include <QSettings> + +#include "onlinesearchabstract.h" +#include "onlinesearchacmportal.h" +#include "onlinesearcharxiv.h" +#include "onlinesearchbibsonomy.h" +#include "onlinesearchgooglescholar.h" +#include "onlinesearchieeexplore.h" +#include "onlinesearchingentaconnect.h" +#include "onlinesearchjstor.h" +#include "onlinesearchpubmed.h" +#include "onlinesearchsciencedirect.h" +#include "onlinesearchspringerlink.h" +#include "bibliographymodel.h" + +SearchEngineList::SearchEngineList() +{ + OnlineSearchAbstract *osa = new OnlineSearchAcmPortal(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + + osa = new OnlineSearchArXiv(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + + osa = new OnlineSearchBibsonomy(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + + osa = new OnlineSearchGoogleScholar(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + + osa = new OnlineSearchIEEEXplore(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + + osa = new OnlineSearchIngentaConnect(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + + osa = new OnlineSearchJStor(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + + osa = new OnlineSearchScienceDirect(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + + osa = new OnlineSearchPubMed(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + + osa = new OnlineSearchSpringerLink(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + + connect(this,&SearchEngineList::dataChanged,[this](const QModelIndex &, const QModelIndex &, const QVector<int> &roles = QVector<int>()){ + if (roles.contains(EngineEnabledRole)) + emit searchEngineCountChanged(); + }); +} + +SearchEngineList::SearchEngineList(const SearchEngineList &other) + : QAbstractListModel(), QVector<OnlineSearchAbstract *>(other) +{ + /// Nothing to do here, QVector constructor does the heavy lifting +} + +SearchEngineList *SearchEngineList::operator =(const SearchEngineList *other) { + /// Not much to do here, QVector constructor does the heavy lifting + QVector<OnlineSearchAbstract *>::operator =(*other); + return this; +} + +int SearchEngineList::rowCount(const QModelIndex &parent) const { + return parent == QModelIndex() ? size() : 0; +} + +QVariant SearchEngineList::data(const QModelIndex &index, int role) const { + if (index.row() < 0 || index.row() >= size() || index.parent() != QModelIndex() || index.column() != 0) + return QVariant(); + const int row = index.row(); + + switch (role) { + case Qt::DisplayRole: + return at(row)->label(); + break; + case EngineEnabledRole: { + const QSettings settings(QStringLiteral("harbour-bibsearch"), QStringLiteral("BibSearch")); + return isSearchEngineEnabled(settings, at(row)); + } + default: + return QVariant(); + } +} + +bool SearchEngineList::setData(const QModelIndex &index, const QVariant &value, int role) { + if (index.row() < 0 || index.row() >= size() || index.parent() != QModelIndex() || index.column() != 0 || role != EngineEnabledRole) + return false; + + static QSettings settings(QStringLiteral("harbour-bibsearch"), QStringLiteral("BibSearch")); + const int row = index.row(); + const bool toBeSetEnabled = value.toBool(); + const bool currentlyEnabled = isSearchEngineEnabled(settings, at(row)); + + if (toBeSetEnabled != currentlyEnabled) { + setSearchEngineEnabled(settings, at(row), toBeSetEnabled); + settings.sync(); + static const QVector<int> roles = QVector<int>(1, EngineEnabledRole); + emit dataChanged(index, index, roles); + } + + return true; +} + +QString SearchEngineList::humanReadableSearchEngines() const { + if (empty()) return QString(); + + const QSettings settings(QStringLiteral("harbour-bibsearch"), QStringLiteral("BibSearch")); + QStringList enabledSearchEnginesLabels; + for (int i = 0; i < size(); ++i) + if (isSearchEngineEnabled(settings, at(i))) + enabledSearchEnginesLabels.append(at(i)->label()); + if (enabledSearchEnginesLabels.isEmpty()) return QString(); + + QString result = enabledSearchEnginesLabels.first(); + if (enabledSearchEnginesLabels.size() == 1) return result; + if (enabledSearchEnginesLabels.size() == 2) return result.append(" and ").append(enabledSearchEnginesLabels.last()); + /// assertion: enabledSearchEnginesLabels.size() >= 3 + for (int i = 1 ; i < enabledSearchEnginesLabels.size() - 1; ++i) + result.append(QStringLiteral(", ")).append(enabledSearchEnginesLabels[i]); + result.append(", and ").append(enabledSearchEnginesLabels.last()); + + return result; +} + +int SearchEngineList::getSearchEngineCount() const { + if (empty()) return 0; + + const QSettings settings(QStringLiteral("harbour-bibsearch"), QStringLiteral("BibSearch")); + int count = 0; + for (int i = 0; i < size(); ++i) + if (isSearchEngineEnabled(settings, at(i))) + ++count; + return count; +} + +QHash<int, QByteArray> SearchEngineList::roleNames() const { + QHash<int, QByteArray> roles; + roles[LabelRole] = "label"; + roles[EngineEnabledRole] = "engineEnabled"; + return roles; +} diff --git a/mobile/sailfishos/src/searchenginelist.h b/mobile/sailfishos/src/searchenginelist.h new file mode 100644 index 00000000..ad2873c5 --- /dev/null +++ b/mobile/sailfishos/src/searchenginelist.h @@ -0,0 +1,60 @@ +/*************************************************************************** + * Copyright (C) 2017-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 SEARCHENGINELIST_H +#define SEARCHENGINELIST_H + +#define isSearchEngineEnabled(settings, osa) ((settings).value(QStringLiteral("SearchEngineList-Enable") + (osa)->name(), true).toBool()) +#define setSearchEngineEnabled(settings, osa, isEnabled) ((settings).setValue(QStringLiteral("SearchEngineList-Enable") + (osa)->name(), (isEnabled))) + +#include <QAbstractListModel> + +#include "entry.h" + +class OnlineSearchAbstract; + +class SearchEngineList : public QAbstractListModel, public QVector<OnlineSearchAbstract *> +{ + Q_OBJECT + Q_PROPERTY(int searchEngineCount READ getSearchEngineCount NOTIFY searchEngineCountChanged) + +public: + enum Roles {LabelRole = Qt::DisplayRole, EngineEnabledRole = Qt::UserRole + 1000}; + + explicit SearchEngineList(); + explicit SearchEngineList(const SearchEngineList &); + + SearchEngineList *operator =(const SearchEngineList *); + + Q_INVOKABLE virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + Q_INVOKABLE virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + Q_INVOKABLE virtual bool setData(const QModelIndex &index, const QVariant &value, int role = EngineEnabledRole); + + Q_INVOKABLE QString humanReadableSearchEngines() const; + int getSearchEngineCount() const; + + QHash<int, QByteArray> roleNames() const; + +signals: + void foundEntry(QSharedPointer<Entry>); + void busyChanged(); + void searchEngineCountChanged(); +}; + +Q_DECLARE_METATYPE(SearchEngineList) + +#endif // SEARCHENGINELIST_H diff --git a/src/config/bibtexentries.cpp b/src/config/bibtexentries.cpp index c01e9017..3f401576 100644 --- a/src/config/bibtexentries.cpp +++ b/src/config/bibtexentries.cpp @@ -1,166 +1,181 @@ /*************************************************************************** * 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 "bibtexentries.h" #include <QStandardPaths> #ifdef HAVE_KF5 #include <KLocalizedString> #include <KSharedConfig> #include <KConfigGroup> +#else // HAVE_KF5 +#include <QObject> +#define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "preferences.h" #include "logging_config.h" bool operator==(const EntryDescription &a, const EntryDescription &b) { return a.upperCamelCase == b.upperCamelCase; } uint qHash(const EntryDescription &a) { return qHash(a.upperCamelCase); } class BibTeXEntries::BibTeXEntriesPrivate { public: static const QVector<EntryDescription> entryDescriptionsBibTeX; +#ifdef HAVE_KF5 static const QVector<EntryDescription> entryDescriptionsBibLaTeX; +#endif // HAVE_KF5 }; BibTeXEntries::BibTeXEntries(const QVector<EntryDescription> &other) : QVector<EntryDescription>(other), d(new BibTeXEntriesPrivate()) { /// nothing } BibTeXEntries::~BibTeXEntries() { delete d; } const BibTeXEntries &BibTeXEntries::instance() { static const QVector<EntryDescription> entryDescriptionsBibTeX { EntryDescription {QStringLiteral("Article"), QString(), i18n("Journal Article"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("journal"), QStringLiteral("year")}, {QStringLiteral("volume"), QStringLiteral("number"), QStringLiteral("pages"), QStringLiteral("month"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("InProceedings"), i18n("Conference"), QStringLiteral("Publication in Conference Proceedings"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("booktitle"), QStringLiteral("year")}, {QStringLiteral("editor"), QStringLiteral("volume^number"), QStringLiteral("series"), QStringLiteral("pages"), QStringLiteral("address"), QStringLiteral("month"), QStringLiteral("organization"), QStringLiteral("publisher"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("Proceedings"), QString(), i18n("Conference or Workshop Proceedings"), {QStringLiteral("title"), QStringLiteral("year")}, {QStringLiteral("editor"), QStringLiteral("volume^number"), QStringLiteral("series"), QStringLiteral("address"), QStringLiteral("month"), QStringLiteral("organization"), QStringLiteral("publisher"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("TechReport"), QString(), i18n("Technical Report"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("institution"), QStringLiteral("year")}, {QStringLiteral("type"), QStringLiteral("number"), QStringLiteral("address"), QStringLiteral("month"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("Misc"), QString(), i18n("Miscellaneous"), {}, {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("howpublished"), QStringLiteral("month"), QStringLiteral("year"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("Book"), QString(), i18n("Book"), {QStringLiteral("author^editor"), QStringLiteral("title"), QStringLiteral("publisher"), QStringLiteral("year")}, {QStringLiteral("volume^number"), QStringLiteral("series"), QStringLiteral("address"), QStringLiteral("edition"), QStringLiteral("month"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("InBook"), QString(), i18n("Part of a Book"), {QStringLiteral("author^editor"), QStringLiteral("title"), QStringLiteral("chapter|pages"), QStringLiteral("publisher"), QStringLiteral("year")}, {QStringLiteral("volume^number"), QStringLiteral("series"), QStringLiteral("type"), QStringLiteral("address"), QStringLiteral("edition"), QStringLiteral("month"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("InCollection"), QString(), i18n("Part of a Book with own Title"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("booktitle"), QStringLiteral("publisher"), QStringLiteral("year")}, {QStringLiteral("editor"), QStringLiteral("volume^number"), QStringLiteral("series"), QStringLiteral("type"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("address"), QStringLiteral("edition"), QStringLiteral("month"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("PhDThesis"), QString(), i18n("PhD Thesis"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("school"), QStringLiteral("year")}, {QStringLiteral("type"), QStringLiteral("address"), QStringLiteral("month"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("MastersThesis"), QString(), i18n("Master's Thesis"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("school"), QStringLiteral("year")}, {QStringLiteral("type"), QStringLiteral("address"), QStringLiteral("month"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("Unpublished"), QString(), i18n("Unpublished Material"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("note")}, {QStringLiteral("month"), QStringLiteral("year")}}, EntryDescription {QStringLiteral("Manual"), QString(), i18n("Manual"), {QStringLiteral("title")}, {QStringLiteral("author"), QStringLiteral("organization"), QStringLiteral("address"), QStringLiteral("edition"), QStringLiteral("month"), QStringLiteral("year"), QStringLiteral("note")}}, EntryDescription {QStringLiteral("Booklet"), QString(), i18n("Booklet"), {QStringLiteral("title")}, {QStringLiteral("author"), QStringLiteral("howpublished"), QStringLiteral("address"), QStringLiteral("month"), QStringLiteral("year"), QStringLiteral("note")}} }; +#ifdef HAVE_KF5 static const QVector<EntryDescription> entryDescriptionsBibLaTeX { EntryDescription {QStringLiteral("Article"), QString(), i18n("Journal Article"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("journaltitle"), QStringLiteral("year^date")}, {QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("editor"), QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("journalsubtitle"), QStringLiteral("issuetitle"), QStringLiteral("issuesubtitle"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("series"), QStringLiteral("volume"), QStringLiteral("number"), QStringLiteral("eid"), QStringLiteral("issue"), QStringLiteral("month"), QStringLiteral("pages"), QStringLiteral("version"), QStringLiteral("note"), QStringLiteral("issn"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Book"), QString(), i18n("Book"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("editor"), QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("MVBook"), QString(), i18n("Multi-volume Book"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("editor"), QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("InBook"), QString(), i18n("Part of a Book"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("booktitle"), QStringLiteral("year^date")}, {QStringLiteral("bookauthor"), QStringLiteral("editor"), QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("booksubtitle"), QStringLiteral("booktitleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("BookInBook"), QString(), i18n("Former Monograph as Part of a Book"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("booktitle"), QStringLiteral("year^date")}, {QStringLiteral("bookauthor"), QStringLiteral("editor"), QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("booksubtitle"), QStringLiteral("booktitleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("SuppBook"), QString(), i18n("Supplemental Material in a Book"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("booktitle"), QStringLiteral("year^date")}, {QStringLiteral("bookauthor"), QStringLiteral("editor"), QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("booksubtitle"), QStringLiteral("booktitleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Booklet"), QString(), i18n("Booklet"), {QStringLiteral("author^editor"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("language"), QStringLiteral("howpublished"), QStringLiteral("type"), QStringLiteral("note"), QStringLiteral("location"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Collection"), QString(), i18n("Single-volume Collection"), {QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("MVCollection"), QString(), i18n("Multi-volume Collection"), {QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("InCollection"), QString(), i18n("Part of a Book with own Title"), {QStringLiteral("author"), QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("booktitle"), QStringLiteral("year^date")}, {QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("booksubtitle"), QStringLiteral("booktitleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("SuppCollection"), QString(), i18n("Supplemental Material in a Collection"), {QStringLiteral("author"), QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("booktitle"), QStringLiteral("year^date")}, {QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("booksubtitle"), QStringLiteral("booktitleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Manual"), QString(), i18n("Manual"), {QStringLiteral("author^editor"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("language"), QStringLiteral("edition"), QStringLiteral("type"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("version"), QStringLiteral("note"), QStringLiteral("organization"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Misc"), QString(), i18n("Miscellaneous"), {QStringLiteral("author^editor"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("language"), QStringLiteral("howpublished"), QStringLiteral("type"), QStringLiteral("version"), QStringLiteral("note"), QStringLiteral("organization"), QStringLiteral("location"), QStringLiteral("date"), QStringLiteral("month"), QStringLiteral("year"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Online"), QStringLiteral("Electronic"), i18n("Online Resource"), {QStringLiteral("author^editor"), QStringLiteral("title"), QStringLiteral("year^date"), QStringLiteral("url")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("language"), QStringLiteral("version"), QStringLiteral("note"), QStringLiteral("organization"), QStringLiteral("date"), QStringLiteral("month"), QStringLiteral("year"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Patent"), QString(), i18n("Patent"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("number"), QStringLiteral("year^date")}, {QStringLiteral("holder"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("type"), QStringLiteral("version"), QStringLiteral("location"), QStringLiteral("note"), QStringLiteral("date"), QStringLiteral("month"), QStringLiteral("year"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Periodical"), QString(), i18n("Periodical (Complete Issue)"), {QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("subtitle"), QStringLiteral("issuetitle"), QStringLiteral("issuesubtitle"), QStringLiteral("language"), QStringLiteral("series"), QStringLiteral("volume"), QStringLiteral("number"), QStringLiteral("issue"), QStringLiteral("date"), QStringLiteral("month"), QStringLiteral("year"), QStringLiteral("note"), QStringLiteral("issn"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("SuppPeriodical"), QString(), i18n("Supplemental Material in a Periodical"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("journaltitle"), QStringLiteral("year^date")}, {QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("editor"), QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("journalsubtitle"), QStringLiteral("issuetitle"), QStringLiteral("issuesubtitle"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("series"), QStringLiteral("volume"), QStringLiteral("number"), QStringLiteral("eid"), QStringLiteral("issue"), QStringLiteral("month"), QStringLiteral("pages"), QStringLiteral("version"), QStringLiteral("note"), QStringLiteral("issn"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Proceedings"), QString(), i18n("Conference or Workshop Proceedings"), {QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("eventtitle"), QStringLiteral("eventdate"), QStringLiteral("venue"), QStringLiteral("language"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("organization"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("month"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("MVProceedings"), QString(), i18n("Multi-volume Proceedings"), {QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("eventtitle"), QStringLiteral("eventdate"), QStringLiteral("venue"), QStringLiteral("language"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("organization"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("month"), QStringLiteral("isbn"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("InProceedings"), QStringLiteral("Conference"), i18n("Publication in Conference Proceedings"), {QStringLiteral(" author"), QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("booktitle"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("booksubtitle"), QStringLiteral("booktitleaddon"), QStringLiteral("eventtitle"), QStringLiteral("eventdate"), QStringLiteral("venue"), QStringLiteral("language"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("organization"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("month"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Reference"), QString(), i18n("Single-volume Reference (e.g. Encyclopedia)"), {QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("MVReference"), QString(), i18n("Multi-volume Reference (e.g. Encyclopedia)"), {QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("InReference"), QString(), i18n("Part of a Reference"), {QStringLiteral("author"), QStringLiteral("editor"), QStringLiteral("title"), QStringLiteral("booktitle"), QStringLiteral("year^date")}, {QStringLiteral("editora"), QStringLiteral("editorb"), QStringLiteral("editorc"), QStringLiteral("translator"), QStringLiteral("annotator"), QStringLiteral("commentator"), QStringLiteral("introduction"), QStringLiteral("foreword"), QStringLiteral("afterword"), QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("maintitle"), QStringLiteral("mainsubtitle"), QStringLiteral("maintitleaddon"), QStringLiteral("booksubtitle"), QStringLiteral("booktitleaddon"), QStringLiteral("language"), QStringLiteral("origlanguage"), QStringLiteral("volume"), QStringLiteral("part"), QStringLiteral("edition"), QStringLiteral("volumes"), QStringLiteral("series"), QStringLiteral("number"), QStringLiteral("note"), QStringLiteral("publisher"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Report"), QString(), i18n("Report"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("type"), QStringLiteral("institution"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("language"), QStringLiteral("number"), QStringLiteral("version"), QStringLiteral("note"), QStringLiteral("location"), QStringLiteral("month"), QStringLiteral("isrn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Thesis"), QString(), i18n("Thesis"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("type"), QStringLiteral("institution"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("language"), QStringLiteral("note"), QStringLiteral("location"), QStringLiteral("month"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("Unpublished"), QString(), i18n("Unpublished Material"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("language"), QStringLiteral("howpublished"), QStringLiteral("note"), QStringLiteral("location"), QStringLiteral("isbn"), QStringLiteral("date"), QStringLiteral("month"), QStringLiteral("year"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("MastersThesis"), QString(), i18n("Master's Thesis"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("institution"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("type"), QStringLiteral("language"), QStringLiteral("note"), QStringLiteral("location"), QStringLiteral("month"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("PhDThesis"), QString(), i18n("PhD Thesis"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("institution"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("type"), QStringLiteral("language"), QStringLiteral("note"), QStringLiteral("location"), QStringLiteral("month"), QStringLiteral("isbn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}}, EntryDescription {QStringLiteral("TechReport"), QString(), i18n("Technical Report"), {QStringLiteral("author"), QStringLiteral("title"), QStringLiteral("institution"), QStringLiteral("year^date")}, {QStringLiteral("subtitle"), QStringLiteral("titleaddon"), QStringLiteral("type"), QStringLiteral("language"), QStringLiteral("number"), QStringLiteral("version"), QStringLiteral("note"), QStringLiteral("location"), QStringLiteral("month"), QStringLiteral("isrn"), QStringLiteral("chapter"), QStringLiteral("pages"), QStringLiteral("pagetotal"), QStringLiteral("addendum"), QStringLiteral("pubstate"), QStringLiteral("doi"), QStringLiteral("eprint"), QStringLiteral("eprintclass"), QStringLiteral("eprinttype"), QStringLiteral("url"), QStringLiteral("urldate")}} }; +#endif // HAVE_KF5 - static const BibTeXEntries singletonBibTeX(entryDescriptionsBibTeX), singletonBibLaTeX(entryDescriptionsBibLaTeX); + static const BibTeXEntries singletonBibTeX(entryDescriptionsBibTeX) +#ifdef HAVE_KF5 + , singletonBibLaTeX(entryDescriptionsBibLaTeX) +#endif // HAVE_KF5 + ; +#ifdef HAVE_KF5 return Preferences::bibliographySystem() == Preferences::BibLaTeX ? singletonBibLaTeX : singletonBibTeX; +#else // HAVE_KF5 + return singletonBibTeX; +#endif // HAVE_KF5 } QString BibTeXEntries::format(const QString &name, KBibTeX::Casing casing) const { QString iName = name.toLower(); switch (casing) { case KBibTeX::cLowerCase: return iName; case KBibTeX::cUpperCase: return name.toUpper(); case KBibTeX::cInitialCapital: iName[0] = iName[0].toUpper(); return iName; case KBibTeX::cLowerCamelCase: { for (const auto &ed : const_cast<const BibTeXEntries &>(*this)) { /// configuration file uses camel-case QString itName = ed.upperCamelCase.toLower(); if (itName == iName) { iName = ed.upperCamelCase; break; } } /// make an educated guess how camel-case would look like iName[0] = iName[0].toLower(); return iName; } case KBibTeX::cUpperCamelCase: { for (const auto &ed : const_cast<const BibTeXEntries &>(*this)) { /// configuration file uses camel-case QString itName = ed.upperCamelCase.toLower(); if (itName == iName) { iName = ed.upperCamelCase; break; } } /// make an educated guess how camel-case would look like iName[0] = iName[0].toUpper(); return iName; } } return name; } QString BibTeXEntries::label(const QString &name) const { const QString iName = name.toLower(); for (const auto &ed : const_cast<const BibTeXEntries &>(*this)) { /// Configuration file uses camel-case, convert this to lower case for faster comparison QString itName = ed.upperCamelCase.toLower(); if (itName == iName || (!(itName = ed.upperCamelCaseAlt.toLower()).isEmpty() && itName == iName)) return ed.label; } return QString(); } diff --git a/src/config/bibtexentries.h b/src/config/bibtexentries.h index 801fa299..b756c227 100644 --- a/src/config/bibtexentries.h +++ b/src/config/bibtexentries.h @@ -1,80 +1,82 @@ /*************************************************************************** * 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_CONFIG_BIBTEXENTRIES_H #define KBIBTEX_CONFIG_BIBTEXENTRIES_H +#ifdef HAVE_KF5 #include "kbibtexconfig_export.h" +#endif // HAVE_KF5 #include <QStringList> #include <QVector> #include "kbibtex.h" typedef struct { QString upperCamelCase; QString upperCamelCaseAlt; QString label; QStringList requiredItems; QStringList optionalItems; } EntryDescription; bool operator==(const EntryDescription &a, const EntryDescription &b); uint qHash(const EntryDescription &a); /** @author Thomas Fischer */ class KBIBTEXCONFIG_EXPORT BibTeXEntries : public QVector<EntryDescription> { public: virtual ~BibTeXEntries(); /** * Only one instance of this class has to be used * @return the class's singleton */ static const BibTeXEntries &instance(); /** * Change the casing of a given entry name to one of the predefine formats. * */ /** * Change the casing of a given entry name to one of the predefine formats. * @param name entry name to format * @param casing can be any of the predefined formats such as lower camel case or upper case * @return returns the formatted entry name if possible or the "name" parameter's value as fall-back */ QString format(const QString &name, KBibTeX::Casing casing) const; /** * Returns the given entry name's i18n'ized, human-readable label, * for example "Journal Article" for entry name "article". * @param name entry name to look up the label for * @return the label for the entry if available, else an empty string */ QString label(const QString &name) const; private: explicit BibTeXEntries(const QVector<EntryDescription> &other); class BibTeXEntriesPrivate; BibTeXEntriesPrivate *d; }; #endif // KBIBTEX_CONFIG_BIBTEXENTRIES_H diff --git a/src/config/bibtexfields.cpp b/src/config/bibtexfields.cpp index 34b72d23..736911ac 100644 --- a/src/config/bibtexfields.cpp +++ b/src/config/bibtexfields.cpp @@ -1,409 +1,411 @@ /*************************************************************************** * 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 "bibtexfields.h" #include <QExplicitlySharedDataPointer> #include <QStandardPaths> #ifdef HAVE_KF5 #include <KSharedConfig> #include <KConfigGroup> #include <KLocalizedString> +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) #endif // HAVE KF5 #include "preferences.h" #include "logging_config.h" bool operator==(const FieldDescription &a, const FieldDescription &b) { return a.upperCamelCase == b.upperCamelCase; } uint qHash(const FieldDescription &a) { return qHash(a.upperCamelCase); } class BibTeXFields::BibTeXFieldsPrivate { public: static const QVector<FieldDescription> fieldDescriptionsBibTeX; static const QVector<FieldDescription> fieldDescriptionsBibLaTeX; BibTeXFields *p; #ifdef HAVE_KF5 KSharedConfigPtr layoutConfig; #endif // HAVE_KF5 BibTeXFieldsPrivate(BibTeXFields *parent) : p(parent) { #ifdef HAVE_KF5 KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))); KConfigGroup configGroup(config, QStringLiteral("User Interface")); const QString stylefile = configGroup.readEntry(QStringLiteral("CurrentStyle"), QString(QStringLiteral("bibtex"))).append(QStringLiteral(".kbstyle")); layoutConfig = KSharedConfig::openConfig(stylefile, KConfig::FullConfig, QStandardPaths::AppDataLocation); if (layoutConfig->groupList().isEmpty()) qCWarning(LOG_KBIBTEX_CONFIG) << "The configuration file for BibTeX fields could not be located or is empty:" << stylefile; #endif // HAVE_KF5 } #ifdef HAVE_KF5 void load() { for (BibTeXFields::Iterator it = p->begin(); it != p->end(); ++it) { auto &fd = *it; const QString groupName = QStringLiteral("Column") + fd.upperCamelCase + fd.upperCamelCaseAlt; KConfigGroup configGroup(layoutConfig, groupName); fd.visible.clear(); if (configGroup.exists()) { const QStringList keyList = configGroup.keyList(); for (const QString &key : keyList) { if (!key.startsWith(QStringLiteral("Visible_"))) continue; ///< a key other than a 'visibility' key const QString treeViewName = key.mid(8); fd.visible.insert(treeViewName, configGroup.readEntry(key, fd.defaultVisible)); } } } } void save() { for (const auto &fd : const_cast<const BibTeXFields &>(*p)) { const QString groupName = QStringLiteral("Column") + fd.upperCamelCase + fd.upperCamelCaseAlt; KConfigGroup configGroup(layoutConfig, groupName); const QList<QString> keys = fd.visible.keys(); for (const QString &treeViewName : keys) { const QString key = QStringLiteral("Visible_") + treeViewName; configGroup.writeEntry(key, fd.visible.value(treeViewName, fd.defaultVisible)); } } layoutConfig->sync(); } void resetToDefaults(const QString &treeViewName) { for (BibTeXFields::Iterator it = p->begin(); it != p->end(); ++it) { const QString groupName = QStringLiteral("Column") + it->upperCamelCase + it->upperCamelCaseAlt; KConfigGroup configGroup(layoutConfig, groupName); configGroup.deleteEntry("Visible_" + treeViewName); } layoutConfig->sync(); load(); } #endif // HAVE_KF5 }; BibTeXFields::BibTeXFields(const QVector<FieldDescription> &other) : QVector<FieldDescription>(other), d(new BibTeXFieldsPrivate(this)) { #ifdef HAVE_KF5 d->load(); #endif // HAVE_KF5 } BibTeXFields::~BibTeXFields() { delete d; } /// This function cannot return a 'const' BibTeXFields object /// similarly like BibTeXEntries::instance as the FieldDescription's /// QMap visible will be modified. BibTeXFields &BibTeXFields::instance() { static const QVector<FieldDescription> fieldDescriptionsBibTeX { FieldDescription {QStringLiteral("^type"), QString(), {}, i18n("Element Type"), KBibTeX::tfSource, KBibTeX::tfSource, 5, {}, true, true}, FieldDescription {QStringLiteral("^id"), QString(), {}, i18n("Identifier"), KBibTeX::tfSource, KBibTeX::tfSource, 6, {}, true, true}, FieldDescription {QStringLiteral("Title"), QString(), {}, i18n("Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("Title"), QStringLiteral("BookTitle"), {}, i18n("Title or Book Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, true, false}, FieldDescription {QStringLiteral("Author"), QStringLiteral("Editor"), {}, i18n("Author or Editor"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 7, {}, true, false}, FieldDescription {QStringLiteral("Author"), QString(), {}, i18n("Author"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Editor"), QString(), {}, i18n("Editor"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Month"), QString(), {}, i18n("Month"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 3, {}, false, false}, FieldDescription {QStringLiteral("Year"), QString(), {}, i18n("Year"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, true, false}, FieldDescription {QStringLiteral("Journal"), QString(), {}, i18n("Journal"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 4, {}, false, false}, FieldDescription {QStringLiteral("Volume"), QString(), {}, i18n("Volume"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("Number"), QString(), {}, i18n("Number"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("ISSN"), QString(), {}, i18n("ISSN"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("ISBN"), QString(), {}, i18n("ISBN"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("ISBN"), QStringLiteral("ISSN"), {}, i18n("ISBN or ISSN"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("HowPublished"), QString(), {}, i18n("How Published"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 5, {}, false, false}, FieldDescription {QStringLiteral("Note"), QString(), {}, i18n("Note"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 5, {}, false, false}, FieldDescription {QStringLiteral("Abstract"), QString(), {}, i18n("Abstract"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 7, {}, false, true}, FieldDescription {QStringLiteral("Pages"), QString(), {}, i18n("Pages"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, true, false}, FieldDescription {QStringLiteral("Publisher"), QString(), {}, i18n("Publisher"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 5, {}, false, false}, FieldDescription {QStringLiteral("Institution"), QString(), {}, i18n("Institution"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 5, {}, false, false}, FieldDescription {QStringLiteral("BookTitle"), QString(), {}, i18n("Book Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("Series"), QString(), {}, i18n("Series"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 12, {}, false, false}, FieldDescription {QStringLiteral("Edition"), QString(), {}, i18n("Edition"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Chapter"), QString(), {}, i18n("Chapter"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("Organization"), QString(), {}, i18n("Organization"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("School"), QString(), {}, i18n("School"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Keywords"), QString(), {}, i18n("Keywords"), KBibTeX::tfKeyword, KBibTeX::tfKeyword | KBibTeX::tfSource, 3, {}, false, true}, FieldDescription {QStringLiteral("CrossRef"), QString(), {}, i18n("Cross Reference"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, 3, {}, false, true}, FieldDescription {QStringLiteral("DOI"), QString(), {}, i18n("DOI"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, 1, {}, false, true}, FieldDescription {QStringLiteral("URL"), QString(), {}, i18n("URL"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, 3, {}, false, true}, FieldDescription {QStringLiteral("Location"), QString(), {}, i18n("Location"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 3, {}, false, false}, FieldDescription {QStringLiteral("Address"), QString(), {}, i18n("Address"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 3, {}, false, false}, FieldDescription {QStringLiteral("Type"), QString(), {}, i18n("Type"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Key"), QString(), {}, i18n("Key"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("X-Color"), QString(), {}, i18n("Color"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("X-Stars"), QString(), {}, i18n("Stars"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim | KBibTeX::tfSource, 4, {}, false, true}, }; static const QVector<FieldDescription> fieldDescriptionsBibLaTeX { FieldDescription {QStringLiteral("^type"), QString(), {}, i18n("Element Type"), KBibTeX::tfSource, KBibTeX::tfSource, 5, {}, true, true}, FieldDescription {QStringLiteral("^id"), QString(), {}, i18n("Identifier"), KBibTeX::tfSource, KBibTeX::tfSource, 6, {}, true, true}, FieldDescription {QStringLiteral("Title"), QString(), {}, i18n("Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("Title"), QStringLiteral("BookTitle"), {}, i18n("Title or Book Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, true, false}, FieldDescription {QStringLiteral("SubTitle"), QString(), {}, i18n("Subtitle"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("TitleAddon"), QString(), {}, i18n("Title Addon"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("ShortTitle"), QString(), {}, i18n("Shortitle"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 4, {}, false, false}, FieldDescription {QStringLiteral("OrigTitle"), QString(), {}, i18n("Original Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("ReprintTitle"), QString(), {}, i18n("Reprint Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("MainTitle"), QString(), {}, i18n("Main Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("MainSubTitle"), QString(), {}, i18n("Main Subtitle"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("MainTitleAddon"), QString(), {}, i18n("Maintitle Addon"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("Author"), QStringLiteral("Editor"), {}, i18n("Author or Editor"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 7, {}, true, false}, FieldDescription {QStringLiteral("Author"), QString(), {}, i18n("Author"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("ShortAuthor"), QString(), {}, i18n("Short Author"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("NameAddon"), QString(), {}, i18n("Name Addon"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("AuthorType"), QString(), {}, i18n("Author Type"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("BookAuthor"), QString(), {}, i18n("Book Author"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Editor"), QString(), {}, i18n("Editor"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("ShortEditor"), QString(), {}, i18n("Short Editor"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("EditorType"), QString(), {}, i18n("Editor Type"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("EditorA"), QString(), {}, i18n("Editor A"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("EditorAType"), QString(), {}, i18n("Editor A Type"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("EditorB"), QString(), {}, i18n("Editor B"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("EditorBType"), QString(), {}, i18n("Editor B Type"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("EditorC"), QString(), {}, i18n("Editor C"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("EditorCType"), QString(), {}, i18n("Editor C Type"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Translator"), QString(), {}, i18n("Translator"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Afterword"), QString(), {}, i18n("Afterword Author"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Introduction"), QString(), {}, i18n("Introduction Author"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Foreword"), QString(), {}, i18n("Foreword Author"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Annotator"), QString(), {}, i18n("Annotator"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Commentator"), QString(), {}, i18n("Commentator"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Holder"), QString(), {}, i18n("Patent Holder"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("Month"), QString(), {}, i18n("Month"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 3, {}, false, false}, FieldDescription {QStringLiteral("Year"), QString(), {}, i18n("Year"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Date"), QString(), {}, i18n("Date"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Year"), QStringLiteral("Date"), {}, i18n("Date or Year"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, true, false}, FieldDescription {QStringLiteral("EventDate"), QString(), {}, i18n("Event Date"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("OrigDate"), QString(), {}, i18n("Original Date"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("JournalTitle"), QString(), {QStringLiteral("Journal")}, i18n("Journal Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 4, {}, false, false}, FieldDescription {QStringLiteral("JournalSubTitle"), QString(), {}, i18n("Journal Subtitle"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 4, {}, false, false}, FieldDescription {QStringLiteral("ShortJournal"), QString(), {}, i18n("Journal Shortitle"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 4, {}, false, false}, FieldDescription {QStringLiteral("Volume"), QString(), {}, i18n("Volume"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("Volumes"), QString(), {}, i18n("Number of Volumes"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("Number"), QString(), {}, i18n("Number"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("Version"), QString(), {}, i18n("Version"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("Part"), QString(), {}, i18n("Part"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("Issue"), QString(), {}, i18n("Issue"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("IASN"), QString(), {}, i18n("IASN"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("ISMN"), QString(), {}, i18n("ISMN"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("ISRN"), QString(), {}, i18n("ISRN"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("ISSN"), QString(), {}, i18n("ISSN"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("ISBN"), QString(), {}, i18n("ISBN"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("ISBN"), QStringLiteral("ISSN"), {}, i18n("ISBN or ISSN"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("ISWC"), QString(), {}, i18n("ISWC"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("HowPublished"), QString(), {}, i18n("How Published"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 5, {}, false, false}, FieldDescription {QStringLiteral("Note"), QString(), {}, i18n("Note"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 5, {}, false, false}, FieldDescription {QStringLiteral("Addendum"), QString(), {}, i18n("Addendum"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 5, {}, false, false}, FieldDescription {QStringLiteral("Annotation"), QString(), {QStringLiteral("Annote")}, i18n("Annotation"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 5, {}, false, false}, FieldDescription {QStringLiteral("Abstract"), QString(), {}, i18n("Abstract"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 7, {}, false, true}, FieldDescription {QStringLiteral("Pages"), QString(), {}, i18n("Pages"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, true, false}, FieldDescription {QStringLiteral("PageTotal"), QString(), {}, i18n("Total Pages"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Pagination"), QString(), {}, i18n("Pagination"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("BookPagination"), QString(), {}, i18n("Book Pagination"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Publisher"), QString(), {}, i18n("Publisher"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 5, {}, false, false}, FieldDescription {QStringLiteral("OrigPublisher"), QString(), {}, i18n("Original Publisher"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Institution"), QString(), {QStringLiteral("School")}, i18n("Institution"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 5, {}, false, false}, FieldDescription {QStringLiteral("BookTitle"), QString(), {}, i18n("Book Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("BookSubTitle"), QString(), {}, i18n("Book Subtitle"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("IssueTitle"), QString(), {}, i18n("Issue Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("IssueSubTitle"), QString(), {}, i18n("Issue Subtitle"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("BookTitleAddon"), QString(), {}, i18n("Booktitle Addon"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 14, {}, false, false}, FieldDescription {QStringLiteral("Series"), QString(), {}, i18n("Series"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 12, {}, false, false}, FieldDescription {QStringLiteral("ShortSeries"), QString(), {}, i18n("Series Shortitle"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 4, {}, false, false}, FieldDescription {QStringLiteral("Edition"), QString(), {}, i18n("Edition"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Chapter"), QString(), {}, i18n("Chapter"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 1, {}, false, false}, FieldDescription {QStringLiteral("Organization"), QString(), {}, i18n("Organization"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("EventTitle"), QString(), {}, i18n("Event Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Venue"), QString(), {}, i18n("Venue"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("IndexTitle"), QString(), {}, i18n("Index Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("Keywords"), QString(), {}, i18n("Keywords"), KBibTeX::tfKeyword, KBibTeX::tfKeyword | KBibTeX::tfSource, 3, {}, false, true}, FieldDescription {QStringLiteral("CrossRef"), QString(), {}, i18n("Cross Reference"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, 3, {}, false, true}, FieldDescription {QStringLiteral("XRef"), QString(), {}, i18n("XRef"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, 3, {}, false, true}, FieldDescription {QStringLiteral("DOI"), QString(), {}, i18n("DOI"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, 1, {}, false, false}, FieldDescription {QStringLiteral("EPrint"), QString(), {}, i18n("E-Print"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, 1, {}, false, false}, FieldDescription {QStringLiteral("EPrintClass"), QString(), {QStringLiteral("PrimaryClass")}, i18n("E-Print Class"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("EPrintType"), QString(), {QStringLiteral("ArchivePrefix")}, i18n("E-Print Type"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("URL"), QString(), {}, i18n("URL"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, 3, {}, false, false}, FieldDescription {QStringLiteral("URLDate"), QString(), {}, i18n("URL Date"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 3, {}, false, false}, FieldDescription {QStringLiteral("File"), QString(), {QStringLiteral("PDF")}, i18n("Local File URL"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, 3, {}, false, true}, FieldDescription {QStringLiteral("Location"), QString(), {QStringLiteral("Address")}, i18n("Location"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 3, {}, false, false}, FieldDescription {QStringLiteral("OrigLocation"), QString(), {}, i18n("Original Location"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Type"), QString(), {}, i18n("Type"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("EID"), QString(), {}, i18n("EID"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Label"), QString(), {}, i18n("Label"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("ShortHand"), QString(), {}, i18n("Shorthand"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("ShortHandIntro"), QString(), {}, i18n("Shorthand Intro"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("PubState"), QString(), {}, i18n("Publication State"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Language"), QString(), {}, i18n("Language"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("OrigLanguage"), QString(), {}, i18n("Original Language"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Library"), QString(), {}, i18n("Library"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("X-Color"), QString(), {}, i18n("Color"), KBibTeX::tfVerbatim, KBibTeX::tfVerbatim | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("Gender"), QString(), {}, i18n("Gender"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("Hyphenation"), QString(), {}, i18n("Hyphenation"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("IndexSortTitle"), QString(), {}, i18n("Index Sorttitle"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("Options"), QString(), {}, i18n("Entry Options"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("Presort"), QString(), {}, i18n("Presort"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("SortKey"), QString(), {QStringLiteral("Key")}, i18n("Sort Key"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("SortName"), QString(), {}, i18n("Sort Names"), KBibTeX::tfPerson, KBibTeX::tfPerson | KBibTeX::tfReference, 7, {}, false, false}, FieldDescription {QStringLiteral("SortShortHand"), QString(), {}, i18n("Sort Shorthand"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("SortTitle"), QString(), {}, i18n("Sort Title"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("SortYear"), QString(), {}, i18n("Sort Year"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, true}, FieldDescription {QStringLiteral("ISAN"), QString(), {}, i18n("ISAN"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 2, {}, false, false}, FieldDescription {QStringLiteral("Location"), QString(), {}, i18n("Location"), KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfReference | KBibTeX::tfSource, 3, {}, false, false}, }; static BibTeXFields singletonBibTeX(fieldDescriptionsBibTeX), singletonBibLaTeX(fieldDescriptionsBibLaTeX); return Preferences::bibliographySystem() == Preferences::BibLaTeX ? singletonBibLaTeX : singletonBibTeX; } #ifdef HAVE_KF5 void BibTeXFields::save() { d->save(); } void BibTeXFields::resetToDefaults(const QString &treeViewName) { d->resetToDefaults(treeViewName); } #endif // HAVE_KF5 QString BibTeXFields::format(const QString &name, KBibTeX::Casing casing) const { QString iName = name.toLower(); switch (casing) { case KBibTeX::cLowerCase: return iName; case KBibTeX::cUpperCase: return name.toUpper(); case KBibTeX::cInitialCapital: iName[0] = iName[0].toUpper(); return iName; case KBibTeX::cLowerCamelCase: { for (const auto &fd : const_cast<const BibTeXFields &>(*this)) { /// configuration file uses camel-case QString itName = fd.upperCamelCase.toLower(); if (itName == iName && fd.upperCamelCaseAlt.isEmpty()) { iName = fd.upperCamelCase; break; } } /// make an educated guess how camel-case would look like iName[0] = iName[0].toLower(); return iName; } case KBibTeX::cUpperCamelCase: { for (const auto &fd : const_cast<const BibTeXFields &>(*this)) { /// configuration file uses camel-case QString itName = fd.upperCamelCase.toLower(); if (itName == iName && fd.upperCamelCaseAlt.isEmpty()) { iName = fd.upperCamelCase; break; } } /// make an educated guess how camel-case would look like iName[0] = iName[0].toUpper(); return iName; } } return name; } const FieldDescription BibTeXFields::find(const QString &name) const { const QString iName = name.toLower(); for (const auto &fd : const_cast<const BibTeXFields &>(*this)) { if (fd.upperCamelCase.toLower() == iName && fd.upperCamelCaseAlt.isEmpty()) return fd; } qCWarning(LOG_KBIBTEX_CONFIG) << "No field description for " << name << "(" << iName << ")"; return FieldDescription {QString(), QString(), {}, QString(), KBibTeX::tfSource, KBibTeX::tfSource, 0, {}, false, false}; } KBibTeX::TypeFlag BibTeXFields::typeFlagFromString(const QString &typeFlagString) { KBibTeX::TypeFlag result = (KBibTeX::TypeFlag)0; if (typeFlagString == QStringLiteral("Text")) result = KBibTeX::tfPlainText; else if (typeFlagString == QStringLiteral("Source")) result = KBibTeX::tfSource; else if (typeFlagString == QStringLiteral("Person")) result = KBibTeX::tfPerson; else if (typeFlagString == QStringLiteral("Keyword")) result = KBibTeX::tfKeyword; else if (typeFlagString == QStringLiteral("Reference")) result = KBibTeX::tfReference; else if (typeFlagString == QStringLiteral("Verbatim")) result = KBibTeX::tfVerbatim; return result; } KBibTeX::TypeFlags BibTeXFields::typeFlagsFromString(const QString &typeFlagsString) { KBibTeX::TypeFlags result; const QStringList list = typeFlagsString.split(';'); for (const QString &s : list) result |= typeFlagFromString(s); return result; } QString BibTeXFields::typeFlagsToString(KBibTeX::TypeFlags typeFlags) { QStringList resultList; if (typeFlags & KBibTeX::tfPlainText) resultList << QStringLiteral("Text"); if (typeFlags & KBibTeX::tfSource) resultList << QStringLiteral("Source"); if (typeFlags & KBibTeX::tfPerson) resultList << QStringLiteral("Person"); if (typeFlags & KBibTeX::tfKeyword) resultList << QStringLiteral("Keyword"); if (typeFlags & KBibTeX::tfReference) resultList << QStringLiteral("Reference"); if (typeFlags & KBibTeX::tfVerbatim) resultList << QStringLiteral("Verbatim"); return resultList.join(QChar(';')); } QString BibTeXFields::typeFlagToString(KBibTeX::TypeFlag typeFlag) { if (typeFlag == KBibTeX::tfPlainText) return QStringLiteral("Text"); if (typeFlag == KBibTeX::tfSource) return QStringLiteral("Source"); if (typeFlag == KBibTeX::tfPerson) return QStringLiteral("Person"); if (typeFlag == KBibTeX::tfKeyword) return QStringLiteral("Keyword"); if (typeFlag == KBibTeX::tfReference) return QStringLiteral("Reference"); if (typeFlag == KBibTeX::tfVerbatim) return QStringLiteral("Verbatim"); return QString(); } diff --git a/src/config/bibtexfields.h b/src/config/bibtexfields.h index 933e47fc..4b17ee67 100644 --- a/src/config/bibtexfields.h +++ b/src/config/bibtexfields.h @@ -1,102 +1,104 @@ /*************************************************************************** * 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_CONFIG_BIBTEXFIELDS_H #define KBIBTEX_CONFIG_BIBTEXFIELDS_H +#ifdef HAVE_KF5 #include "kbibtexconfig_export.h" +#endif // HAVE_KF5 #include <QString> #include <QVector> #include "kbibtex.h" typedef struct { /** * Name of this field in 'upper camel case', e.g. 'BookTitle', but not * 'booktitle', 'BOOKTITLE', or 'BoOkTiTlE'. */ QString upperCamelCase; /** * The 'alternative' field name is used to create 'meta fields', such as * 'author' or 'editor' combined. It shall not be used to define aliases * such as 'pdf' to be an alias for 'file'. */ QString upperCamelCaseAlt; /** * List of aliases for a field. Empty for most fields. Alias for BibLaTeX * must be explictly mentioned in BibLaTeX's official documentation, see * section 'Field Aliases'. * Field aliases shall not be used as alternative field names. */ QStringList upperCamelCaseAliases; /** * Localized (translated) name of this field. */ QString label; KBibTeX::TypeFlag preferredTypeFlag; KBibTeX::TypeFlags typeFlags; int defaultWidth; QMap<QString, bool> visible; bool defaultVisible; bool typeIndependent; } FieldDescription; bool operator==(const FieldDescription &a, const FieldDescription &b); uint qHash(const FieldDescription &a); /** @author Thomas Fischer */ class KBIBTEXCONFIG_EXPORT BibTeXFields : public QVector<FieldDescription> { public: BibTeXFields(const BibTeXFields &other) = delete; BibTeXFields &operator= (const BibTeXFields &other) = delete; ~BibTeXFields(); /** * Only one instance of this class has to be used * @return the class's singleton */ static BibTeXFields &instance(); #ifdef HAVE_KF5 void save(); void resetToDefaults(const QString &treeViewName); #endif // HAVE_KF5 /** * Change the casing of a given field name to one of the predefine formats. */ QString format(const QString &name, KBibTeX::Casing casing) const; static KBibTeX::TypeFlag typeFlagFromString(const QString &typeFlagString); static KBibTeX::TypeFlags typeFlagsFromString(const QString &typeFlagsString); static QString typeFlagToString(KBibTeX::TypeFlag typeFlag); static QString typeFlagsToString(KBibTeX::TypeFlags typeFlags); const FieldDescription find(const QString &name) const; private: explicit BibTeXFields(const QVector<FieldDescription> &other); class BibTeXFieldsPrivate; BibTeXFieldsPrivate *d; }; #endif // KBIBTEX_CONFIG_BIBTEXFIELDS_H diff --git a/src/data/element.h b/src/data/element.h index 3942f3c4..28ae558b 100644 --- a/src/data/element.h +++ b/src/data/element.h @@ -1,41 +1,43 @@ /*************************************************************************** * 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 BIBTEXELEMENT_H #define BIBTEXELEMENT_H #include "file.h" +#ifdef HAVE_KF5 #include "kbibtexdata_export.h" +#endif // HAVE_KF5 /** * Base class for bibliographic elements in a BibTeX file. * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXDATA_EXPORT Element { public: Element(); virtual ~Element() { /* nothing */ } private: int uniqueId; }; #endif diff --git a/src/data/file.h b/src/data/file.h index fc5086b3..3c6ead6a 100644 --- a/src/data/file.h +++ b/src/data/file.h @@ -1,126 +1,128 @@ /*************************************************************************** * 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_FILE_H #define KBIBTEX_IO_FILE_H #include <QList> #include <QStringList> #include <QSharedPointer> #include "element.h" +#ifdef HAVE_KF5 #include "kbibtexdata_export.h" +#endif // HAVE_KF5 class Element; /** * This class represents a bibliographic file such as a BibTeX file * (or any other format after proper conversion). The file's content * can be accessed using the inherited QList interface (for example * list iterators). * @see Element * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXDATA_EXPORT File : public QList<QSharedPointer<Element> > { public: /// enum and flags to differ between entries, macros etc /// used for @see #allKeys() and @see #containsKey() enum ElementType { etEntry = 0x1, etMacro = 0x2, etAll = 0x3 }; Q_DECLARE_FLAGS(ElementTypes, ElementType) /// used for property map const static QString Url; const static QString Encoding; const static QString StringDelimiter; const static QString QuoteComment; const static QString KeywordCasing; const static QString ProtectCasing; const static QString NameFormatting; const static QString ListSeparator; explicit File(); explicit File(const File &other); explicit File(File &&other); ~File(); /// Copy-assignment operator. File &operator= (const File &other); /// Move-assignment operator. File &operator= (File &&other); bool operator== (const File &other) const; bool operator!= (const File &other) const; /** * Check if a given key (e.g. a key for a macro or an id for an entry) * is contained in the file object. * @see #allKeys() const * @return @c the object addressed by the key @c, NULL if no such file has been found */ const QSharedPointer<Element> containsKey(const QString &key, ElementTypes elementTypes = etAll) const; /** * Retrieves a list of all keys for example from macros or entries. * @see #const containsKey(const QString &) const * @return list of keys */ QStringList allKeys(ElementTypes elementTypes = etAll) const; /** * Retrieves a set of all unique values (as text) for a specified * field from all entries * @param fieldName field name to scan, e.g. "volume" * @return list of unique values */ QSet<QString> uniqueEntryValuesSet(const QString &fieldName) const; /** * Retrieves a list of all unique values (as text) for a specified * field from all entries * @param fieldName field name to scan, e.g. "volume" * @return list of unique values */ QStringList uniqueEntryValuesList(const QString &fieldName) const; void setProperty(const QString &key, const QVariant &value); QVariant property(const QString &key) const; QVariant property(const QString &key, const QVariant &defaultValue) const; bool hasProperty(const QString &key) const; void setPropertiesToDefault(); /** * Check if this File object is valid by its own assessment. * No high-level checks, such as on the File instance's content, * are performed. * @return True if validity checks succeed, false otherwise */ bool checkValidity() const; private: class FilePrivate; FilePrivate *d; }; Q_DECLARE_METATYPE(File *) QDebug operator<<(QDebug dbg, const File &file); #endif // KBIBTEX_IO_FILE_H diff --git a/src/data/value.cpp b/src/data/value.cpp index 1133cbaa..6b11a24b 100644 --- a/src/data/value.cpp +++ b/src/data/value.cpp @@ -1,767 +1,769 @@ /*************************************************************************** * 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 "value.h" #include <typeinfo> #include <QSet> #include <QString> #include <QStringList> #include <QDebug> #include <QRegularExpression> #ifdef HAVE_KF5 #include <KSharedConfig> #include <KConfigGroup> #include <KLocalizedString> +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "file.h" #include "preferences.h" quint64 ValueItem::internalIdCounter = 0; uint qHash(const QSharedPointer<ValueItem> &valueItem) { return qHash(valueItem->id()); } const QRegularExpression ValueItem::ignoredInSorting(QStringLiteral("[{}\\\\]+")); ValueItem::ValueItem() : internalId(++internalIdCounter) { /// nothing } ValueItem::~ValueItem() { /// nothing } quint64 ValueItem::id() const { return internalId; } bool ValueItem::operator!=(const ValueItem &other) const { return !operator ==(other); } Keyword::Keyword(const Keyword &other) : m_text(other.m_text) { /// nothing } Keyword::Keyword(const QString &text) : m_text(text) { /// nothing } void Keyword::setText(const QString &text) { m_text = text; } QString Keyword::text() const { return m_text; } void Keyword::replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) { if (replaceMode == ValueItem::AnySubstring) m_text = m_text.replace(before, after); else if (replaceMode == ValueItem::CompleteMatch && m_text == before) m_text = after; } bool Keyword::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const { const QString text = QString(m_text).remove(ignoredInSorting); return text.contains(pattern, caseSensitive); } bool Keyword::operator==(const ValueItem &other) const { const Keyword *otherKeyword = dynamic_cast<const Keyword *>(&other); if (otherKeyword != nullptr) { return otherKeyword->text() == text(); } else return false; } bool Keyword::isKeyword(const ValueItem &other) { return typeid(other) == typeid(Keyword); } Person::Person(const QString &firstName, const QString &lastName, const QString &suffix) : m_firstName(firstName), m_lastName(lastName), m_suffix(suffix) { /// nothing } Person::Person(const Person &other) : m_firstName(other.firstName()), m_lastName(other.lastName()), m_suffix(other.suffix()) { /// nothing } QString Person::firstName() const { return m_firstName; } QString Person::lastName() const { return m_lastName; } QString Person::suffix() const { return m_suffix; } void Person::replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) { if (replaceMode == ValueItem::AnySubstring) { m_firstName = m_firstName.replace(before, after); m_lastName = m_lastName.replace(before, after); m_suffix = m_suffix.replace(before, after); } else if (replaceMode == ValueItem::CompleteMatch) { if (m_firstName == before) m_firstName = after; if (m_lastName == before) m_lastName = after; if (m_suffix == before) m_suffix = after; } } bool Person::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const { const QString firstName = QString(m_firstName).remove(ignoredInSorting); const QString lastName = QString(m_lastName).remove(ignoredInSorting); const QString suffix = QString(m_suffix).remove(ignoredInSorting); return firstName.contains(pattern, caseSensitive) || lastName.contains(pattern, caseSensitive) || suffix.contains(pattern, caseSensitive) || QString(QStringLiteral("%1 %2|%2, %1")).arg(firstName, lastName).contains(pattern, caseSensitive); } bool Person::operator==(const ValueItem &other) const { const Person *otherPerson = dynamic_cast<const Person *>(&other); if (otherPerson != nullptr) { return otherPerson->firstName() == firstName() && otherPerson->lastName() == lastName() && otherPerson->suffix() == suffix(); } else return false; } QString Person::transcribePersonName(const Person *person, const QString &formatting) { return transcribePersonName(formatting, person->firstName(), person->lastName(), person->suffix()); } QString Person::transcribePersonName(const QString &formatting, const QString &firstName, const QString &lastName, const QString &suffix) { QString result = formatting; int p1 = -1, p2 = -1, p3 = -1; while ((p1 = result.indexOf('<')) >= 0 && (p2 = result.indexOf('>', p1 + 1)) >= 0 && (p3 = result.indexOf('%', p1)) >= 0 && p3 < p2) { QString insert; switch (result[p3 + 1].toLatin1()) { case 'f': insert = firstName; break; case 'l': insert = lastName; break; case 's': insert = suffix; break; } if (!insert.isEmpty()) insert = result.mid(p1 + 1, p3 - p1 - 1) + insert + result.mid(p3 + 2, p2 - p3 - 2); result = result.left(p1) + insert + result.mid(p2 + 1); } return result; } bool Person::isPerson(const ValueItem &other) { return typeid(other) == typeid(Person); } QDebug operator<<(QDebug dbg, const Person &person) { dbg.nospace() << "Person " << Person::transcribePersonName(&person, Preferences::defaultPersonNameFormatting); return dbg; } MacroKey::MacroKey(const MacroKey &other) : m_text(other.m_text) { /// nothing } MacroKey::MacroKey(const QString &text) : m_text(text) { /// nothing } void MacroKey::setText(const QString &text) { m_text = text; } QString MacroKey::text() const { return m_text; } bool MacroKey::isValid() { const QString t = text(); static const QRegularExpression validMacroKey(QStringLiteral("^[a-z][-.:/+_a-z0-9]*$|^[0-9]+$"), QRegularExpression::CaseInsensitiveOption); const QRegularExpressionMatch match = validMacroKey.match(t); return match.hasMatch() && match.captured(0) == t; } void MacroKey::replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) { if (replaceMode == ValueItem::AnySubstring) m_text = m_text.replace(before, after); else if (replaceMode == ValueItem::CompleteMatch && m_text == before) m_text = after; } bool MacroKey::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const { const QString text = QString(m_text).remove(ignoredInSorting); return text.contains(pattern, caseSensitive); } bool MacroKey::operator==(const ValueItem &other) const { const MacroKey *otherMacroKey = dynamic_cast<const MacroKey *>(&other); if (otherMacroKey != nullptr) { return otherMacroKey->text() == text(); } else return false; } bool MacroKey::isMacroKey(const ValueItem &other) { return typeid(other) == typeid(MacroKey); } QDebug operator<<(QDebug dbg, const MacroKey &macrokey) { dbg.nospace() << "MacroKey " << macrokey.text(); return dbg; } PlainText::PlainText(const PlainText &other) : m_text(other.text()) { /// nothing } PlainText::PlainText(const QString &text) : m_text(text) { /// nothing } void PlainText::setText(const QString &text) { m_text = text; } QString PlainText::text() const { return m_text; } void PlainText::replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) { if (replaceMode == ValueItem::AnySubstring) m_text = m_text.replace(before, after); else if (replaceMode == ValueItem::CompleteMatch && m_text == before) m_text = after; } bool PlainText::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const { const QString text = QString(m_text).remove(ignoredInSorting); return text.contains(pattern, caseSensitive); } bool PlainText::operator==(const ValueItem &other) const { const PlainText *otherPlainText = dynamic_cast<const PlainText *>(&other); if (otherPlainText != nullptr) { return otherPlainText->text() == text(); } else return false; } bool PlainText::isPlainText(const ValueItem &other) { return typeid(other) == typeid(PlainText); } QDebug operator<<(QDebug dbg, const PlainText &plainText) { dbg.nospace() << "PlainText " << plainText.text(); return dbg; } #ifdef HAVE_KF5 bool VerbatimText::colorLabelPairsInitialized = false; QList<VerbatimText::ColorLabelPair> VerbatimText::colorLabelPairs = QList<VerbatimText::ColorLabelPair>(); #endif // HAVE_KF5 VerbatimText::VerbatimText(const VerbatimText &other) : m_text(other.text()) { /// nothing } VerbatimText::VerbatimText(const QString &text) : m_text(text) { /// nothing } void VerbatimText::setText(const QString &text) { m_text = text; } QString VerbatimText::text() const { return m_text; } void VerbatimText::replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) { if (replaceMode == ValueItem::AnySubstring) m_text = m_text.replace(before, after); else if (replaceMode == ValueItem::CompleteMatch && m_text == before) m_text = after; } bool VerbatimText::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const { const QString text = QString(m_text).remove(ignoredInSorting); #ifdef HAVE_KF5 /// Initialize map of labels to color (hex string) only once // FIXME if user changes colors/labels later, it will not be updated here if (!colorLabelPairsInitialized) { colorLabelPairsInitialized = true; /// Read data from config file KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))); KConfigGroup configGroup(config, Preferences::groupColor); const QStringList colorCodes = configGroup.readEntry(Preferences::keyColorCodes, Preferences::defaultColorCodes); const QStringList colorLabels = configGroup.readEntry(Preferences::keyColorLabels, Preferences::defaultColorLabels); /// Translate data from config file into internal mapping for (QStringList::ConstIterator itc = colorCodes.constBegin(), itl = colorLabels.constBegin(); itc != colorCodes.constEnd() && itl != colorLabels.constEnd(); ++itc, ++itl) { ColorLabelPair clp; clp.hexColor = *itc; clp.label = i18n((*itl).toUtf8().constData()); colorLabelPairs << clp; } } #endif // HAVE_KF5 bool contained = text.contains(pattern, caseSensitive); #ifdef HAVE_KF5 if (!contained) { /// Only if simple text match failed, check color labels /// For a match, the user's pattern has to be the start of the color label /// and this verbatim text has to contain the color as hex string for (const auto &clp : const_cast<const QList<ColorLabelPair> &>(colorLabelPairs)) { contained = text.compare(clp.hexColor, Qt::CaseInsensitive) == 0 && clp.label.contains(pattern, Qt::CaseInsensitive); if (contained) break; } } #endif // HAVE_KF5 return contained; } bool VerbatimText::operator==(const ValueItem &other) const { const VerbatimText *otherVerbatimText = dynamic_cast<const VerbatimText *>(&other); if (otherVerbatimText != nullptr) { return otherVerbatimText->text() == text(); } else return false; } bool VerbatimText::isVerbatimText(const ValueItem &other) { return typeid(other) == typeid(VerbatimText); } QDebug operator<<(QDebug dbg, const VerbatimText &verbatimText) { dbg.nospace() << "VerbatimText " << verbatimText.text(); return dbg; } Value::Value() : QVector<QSharedPointer<ValueItem> >() { /// nothing } Value::Value(const Value &other) : QVector<QSharedPointer<ValueItem> >(other) { /// nothing } Value::Value(Value &&other) : QVector<QSharedPointer<ValueItem> >(other) { /// nothing } Value::~Value() { clear(); } void Value::replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) { QSet<QSharedPointer<ValueItem> > unique; /// Delegate the replace operation to each individual ValueItem /// contained in this Value object for (Value::Iterator it = begin(); it != end();) { (*it)->replace(before, after, replaceMode); bool containedInUnique = false; for (const auto &valueItem : const_cast<const QSet<QSharedPointer<ValueItem> > &>(unique)) { containedInUnique = *valueItem.data() == *(*it).data(); if (containedInUnique) break; } if (containedInUnique) it = erase(it); else { unique.insert(*it); ++it; } } QSet<QString> uniqueValueItemTexts; for (int i = count() - 1; i >= 0; --i) { at(i)->replace(before, after, replaceMode); const QString valueItemText = PlainTextValue::text(*at(i).data()); if (uniqueValueItemTexts.contains(valueItemText)) { /// Due to a replace/delete operation above, an old ValueItem's text /// matches the replaced text. /// Therefore, remove the replaced text to avoid duplicates remove(i); ++i; /// compensate for for-loop's --i } else uniqueValueItemTexts.insert(valueItemText); } } void Value::replace(const QString &before, const QSharedPointer<ValueItem> &after) { const QString valueText = PlainTextValue::text(*this); if (valueText == before) { clear(); append(after); } else { QSet<QString> uniqueValueItemTexts; for (int i = count() - 1; i >= 0; --i) { QString valueItemText = PlainTextValue::text(*at(i).data()); if (valueItemText == before) { /// Perform replacement operation QVector<QSharedPointer<ValueItem> >::replace(i, after); valueItemText = PlainTextValue::text(*after.data()); // uniqueValueItemTexts.insert(PlainTextValue::text(*after.data())); } if (uniqueValueItemTexts.contains(valueItemText)) { /// Due to a previous replace operation, an existingValueItem's /// text matches a text which was inserted as an "after" ValueItem. /// Therefore, remove the old ValueItem to avoid duplicates. remove(i); } else { /// Neither a replacement, nor a duplicate. Keep this /// ValueItem (memorize as unique) and continue. uniqueValueItemTexts.insert(valueItemText); } } } } bool Value::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const { for (const auto &valueItem : const_cast<const Value &>(*this)) { if (valueItem->containsPattern(pattern, caseSensitive)) return true; } return false; } bool Value::contains(const ValueItem &item) const { for (const auto &valueItem : const_cast<const Value &>(*this)) if (valueItem->operator==(item)) return true; return false; } Value &Value::operator=(const Value &rhs) { return static_cast<Value &>(QVector<QSharedPointer<ValueItem> >::operator =((rhs))); } Value &Value::operator=(Value &&rhs) { return static_cast<Value &>(QVector<QSharedPointer<ValueItem> >::operator =((rhs))); } Value &Value::operator<<(const QSharedPointer<ValueItem> &value) { return static_cast<Value &>(QVector<QSharedPointer<ValueItem> >::operator<<((value))); } bool Value::operator==(const Value &rhs) const { const Value &lhs = *this; ///< just for readability to have a 'lhs' matching 'rhs' /// Obviously, both Values must be of same size if (lhs.count() != rhs.count()) return false; /// Synchronously iterate over both Values' ValueItems for (Value::ConstIterator lhsIt = lhs.constBegin(), rhsIt = rhs.constBegin(); lhsIt != lhs.constEnd() && rhsIt != rhs.constEnd(); ++lhsIt, ++rhsIt) { /// Are both ValueItems PlainTexts and are both PlainTexts equal? const QSharedPointer<PlainText> lhsPlainText = lhsIt->dynamicCast<PlainText>(); const QSharedPointer<PlainText> rhsPlainText = rhsIt->dynamicCast<PlainText>(); if ((lhsPlainText.isNull() && !rhsPlainText.isNull()) || (!lhsPlainText.isNull() && rhsPlainText.isNull())) return false; if (!lhsPlainText.isNull() && !rhsPlainText.isNull()) { if (*lhsPlainText.data() != *rhsPlainText.data()) return false; } else { /// Remainder of comparisons is like for PlainText above, just for other descendants of ValueItem const QSharedPointer<MacroKey> lhsMacroKey = lhsIt->dynamicCast<MacroKey>(); const QSharedPointer<MacroKey> rhsMacroKey = rhsIt->dynamicCast<MacroKey>(); if ((lhsMacroKey.isNull() && !rhsMacroKey.isNull()) || (!lhsMacroKey.isNull() && rhsMacroKey.isNull())) return false; if (!lhsMacroKey.isNull() && !rhsMacroKey.isNull()) { if (*lhsMacroKey.data() != *rhsMacroKey.data()) return false; } else { const QSharedPointer<Person> lhsPerson = lhsIt->dynamicCast<Person>(); const QSharedPointer<Person> rhsPerson = rhsIt->dynamicCast<Person>(); if ((lhsPerson.isNull() && !rhsPerson.isNull()) || (!lhsPerson.isNull() && rhsPerson.isNull())) return false; if (!lhsPerson.isNull() && !rhsPerson.isNull()) { if (*lhsPerson.data() != *rhsPerson.data()) return false; } else { const QSharedPointer<VerbatimText> lhsVerbatimText = lhsIt->dynamicCast<VerbatimText>(); const QSharedPointer<VerbatimText> rhsVerbatimText = rhsIt->dynamicCast<VerbatimText>(); if ((lhsVerbatimText.isNull() && !rhsVerbatimText.isNull()) || (!lhsVerbatimText.isNull() && rhsVerbatimText.isNull())) return false; if (!lhsVerbatimText.isNull() && !rhsVerbatimText.isNull()) { if (*lhsVerbatimText.data() != *rhsVerbatimText.data()) return false; } else { const QSharedPointer<Keyword> lhsKeyword = lhsIt->dynamicCast<Keyword>(); const QSharedPointer<Keyword> rhsKeyword = rhsIt->dynamicCast<Keyword>(); if ((lhsKeyword.isNull() && !rhsKeyword.isNull()) || (!lhsKeyword.isNull() && rhsKeyword.isNull())) return false; if (!lhsKeyword.isNull() && !rhsKeyword.isNull()) { if (*lhsKeyword.data() != *rhsKeyword.data()) return false; } else { /// If there are other descendants of ValueItem, add tests here ... return false; } } } } } } /// No check failed, so equalness is proven return true; } bool Value::operator!=(const Value &rhs) const { return !operator ==(rhs); } QDebug operator<<(QDebug dbg, const Value &value) { dbg.nospace() << "Value"; if (value.isEmpty()) dbg << " is empty"; else dbg.nospace() << ": " << PlainTextValue::text(value); return dbg; } QString PlainTextValue::text(const Value &value) { ValueItemType vit = VITOther; ValueItemType lastVit = VITOther; QString result; for (const auto &valueItem : value) { QString nextText = text(*valueItem, vit); if (!nextText.isEmpty()) { if (lastVit == VITPerson && vit == VITPerson) result.append(i18n(" and ")); // TODO proper list of authors/editors, not just joined by "and" else if (lastVit == VITPerson && vit == VITOther && nextText == QStringLiteral("others")) { /// "and others" case: replace text to be appended by translated variant nextText = i18n(" and others"); } else if (lastVit == VITKeyword && vit == VITKeyword) result.append("; "); else if (!result.isEmpty()) result.append(" "); result.append(nextText); lastVit = vit; } } return result; } QString PlainTextValue::text(const QSharedPointer<const ValueItem> &valueItem) { const ValueItem *p = valueItem.data(); return text(*p); } QString PlainTextValue::text(const ValueItem &valueItem) { ValueItemType vit; return text(valueItem, vit); } QString PlainTextValue::text(const ValueItem &valueItem, ValueItemType &vit) { QString result; vit = VITOther; #ifdef HAVE_KF5 /// Required to have static instance of PlainTextValue here /// to initialize @see personNameFormatting from settings /// as well as update @see personNameFormatting upon notification /// from NotificationHub static PlainTextValue ptv; #endif // HAVE_KF5 bool isVerbatim = false; const PlainText *plainText = dynamic_cast<const PlainText *>(&valueItem); if (plainText != nullptr) { result = plainText->text(); } else { const MacroKey *macroKey = dynamic_cast<const MacroKey *>(&valueItem); if (macroKey != nullptr) { result = macroKey->text(); // TODO Use File to resolve key to full text } else { const Person *person = dynamic_cast<const Person *>(&valueItem); if (person != nullptr) { result = Person::transcribePersonName(person, personNameFormatting); vit = VITPerson; } else { const Keyword *keyword = dynamic_cast<const Keyword *>(&valueItem); if (keyword != nullptr) { result = keyword->text(); vit = VITKeyword; } else { const VerbatimText *verbatimText = dynamic_cast<const VerbatimText *>(&valueItem); if (verbatimText != nullptr) { result = verbatimText->text(); isVerbatim = true; } else qWarning() << "Cannot interpret ValueItem to one of its descendants"; } } } } /// clean up result string const int len = result.length(); int j = 0; static const QChar cbo = QLatin1Char('{'), cbc = QLatin1Char('}'), bs = QLatin1Char('\\'), mns = QLatin1Char('-'), comma = QLatin1Char(','), thinspace = QChar(0x2009), tilde = QLatin1Char('~'), nobreakspace = QChar(0x00a0); for (int i = 0; i < len; ++i) { if ((result[i] == cbo || result[i] == cbc) && (i < 1 || result[i - 1] != bs)) { /// hop over curly brackets } else if (i < len - 1 && result[i] == bs && result[i + 1] == mns) { /// hop over hyphenation commands ++i; } else if (i < len - 1 && result[i] == bs && result[i + 1] == comma) { /// place '\,' with a thin space result[j] = thinspace; ++i; ++j; } else if (!isVerbatim && result[i] == tilde && (i < 1 || result[i - 1] != bs)) { /// replace '~' with a non-breaking space, /// except if text was verbatim (e.g. a 'localfile' value /// like '~/document.pdf' or 'document.pdf~') result[j] = nobreakspace; ++j; } else { if (i > j) { /// move individual characters forward in result string result[j] = result[i]; } ++j; } } result.resize(j); return result; } #ifdef HAVE_KF5 PlainTextValue::PlainTextValue() { NotificationHub::registerNotificationListener(this, NotificationHub::EventConfigurationChanged); readConfiguration(); } void PlainTextValue::notificationEvent(int eventId) { if (eventId == NotificationHub::EventConfigurationChanged) readConfiguration(); } void PlainTextValue::readConfiguration() { KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))); KConfigGroup configGroup(config, "General"); personNameFormatting = configGroup.readEntry(Preferences::keyPersonNameFormatting, Preferences::defaultPersonNameFormatting); } QString PlainTextValue::personNameFormatting; #else // HAVE_KF5 const QString PlainTextValue::personNameFormatting = QStringLiteral("<%l><, %s><, %f>"); #endif // HAVE_KF5 diff --git a/src/data/value.h b/src/data/value.h index fcab1600..9b87a3c0 100644 --- a/src/data/value.h +++ b/src/data/value.h @@ -1,312 +1,314 @@ /*************************************************************************** * 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 BIBTEXVALUE_H #define BIBTEXVALUE_H #include <QVector> #include <QVariant> #include <QSharedPointer> +#ifdef HAVE_KF5 #include "kbibtexdata_export.h" +#endif // HAVE_KF5 #ifdef HAVE_KF5 #include "notificationhub.h" #endif // HAVE_KF5 class File; /** * Generic class of an information element in a @see Value object. * In BibTeX, ValueItems are concatenated by "#". */ class KBIBTEXDATA_EXPORT ValueItem { public: enum ReplaceMode {CompleteMatch, AnySubstring}; ValueItem(); virtual ~ValueItem(); virtual void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) = 0; /** * Check if this object contains text pattern @p pattern. * @param pattern Pattern to check for * @param caseSensitive Case sensitivity setting for check * @return TRUE if pattern is contained within this value, otherwise FALSE */ virtual bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const = 0; /** * Compare to instance if they contain the same content. * Subclasses implement under which conditions two instances are equal. * Subclasses of different type are never equal. * @param other other instance to compare with * @return TRUE if both instances are equal */ virtual bool operator==(const ValueItem &other) const = 0; bool operator!=(const ValueItem &other) const; /** * Unique numeric identifier for every ValueItem instance. * @return Unique numeric identifier */ quint64 id() const; protected: /// contains text fragments to be removed before performing a "contains pattern" operation /// includes among other "{" and "}" static const QRegularExpression ignoredInSorting; private: /// Unique numeric identifier const quint64 internalId; /// Keeping track of next available unique numeric identifier static quint64 internalIdCounter; }; class KBIBTEXDATA_EXPORT Keyword: public ValueItem { public: Keyword(const Keyword &other); explicit Keyword(const QString &text); void setText(const QString &text); QString text() const; void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) override; bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const override; bool operator==(const ValueItem &other) const override; /** * Cheap and fast test if another ValueItem is a Keyword object. * @param other another ValueItem object to test * @return true if ValueItem is actually a Keyword */ static bool isKeyword(const ValueItem &other); protected: QString m_text; }; class KBIBTEXDATA_EXPORT Person: public ValueItem { public: /** * Create a representation for a person's name. In bibliographies, * a person is either an author or an editor. The four parameters * cover all common parts of a name. Only first and last name are * mandatory (each person should have those). @param firstName First name of a person. Example: "Peter" @param lastName Last name of a person. Example: "Smith" @param suffix Suffix after a name. Example: "jr." */ Person(const QString &firstName, const QString &lastName, const QString &suffix = QString()); Person(const Person &other); QString firstName() const; QString lastName() const; QString suffix() const; void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) override; bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const override; bool operator==(const ValueItem &other) const override; static QString transcribePersonName(const QString &formatting, const QString &firstName, const QString &lastName, const QString &suffix = QString()); static QString transcribePersonName(const Person *person, const QString &formatting); /** * Cheap and fast test if another ValueItem is a Person object. * @param other another ValueItem object to test * @return true if ValueItem is actually a Person */ static bool isPerson(const ValueItem &other); private: QString m_firstName; QString m_lastName; QString m_suffix; }; QDebug operator<<(QDebug dbg, const Person &person); Q_DECLARE_METATYPE(Person *) class KBIBTEXDATA_EXPORT MacroKey: public ValueItem { public: MacroKey(const MacroKey &other); explicit MacroKey(const QString &text); void setText(const QString &text); QString text() const; bool isValid(); void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) override; bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const override; bool operator==(const ValueItem &other) const override; /** * Cheap and fast test if another ValueItem is a MacroKey object. * @param other another ValueItem object to test * @return true if ValueItem is actually a MacroKey */ static bool isMacroKey(const ValueItem &other); protected: QString m_text; }; QDebug operator<<(QDebug dbg, const MacroKey &macrokey); class KBIBTEXDATA_EXPORT PlainText: public ValueItem { public: PlainText(const PlainText &other); explicit PlainText(const QString &text); void setText(const QString &text); QString text() const; void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) override; bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const override; bool operator==(const ValueItem &other) const override; /** * Cheap and fast test if another ValueItem is a PlainText object. * @param other another ValueItem object to test * @return true if ValueItem is actually a PlainText */ static bool isPlainText(const ValueItem &other); protected: QString m_text; }; QDebug operator<<(QDebug dbg, const PlainText &plainText); class KBIBTEXDATA_EXPORT VerbatimText: public ValueItem { public: VerbatimText(const VerbatimText &other); explicit VerbatimText(const QString &text); void setText(const QString &text); QString text() const; void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) override; bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const override; bool operator==(const ValueItem &other) const override; /** * Cheap and fast test if another ValueItem is a VerbatimText object. * @param other another ValueItem object to test * @return true if ValueItem is actually a VerbatimText */ static bool isVerbatimText(const ValueItem &other); protected: QString m_text; private: #ifdef HAVE_KF5 struct ColorLabelPair { QString hexColor; QString label; }; static QList<ColorLabelPair> colorLabelPairs; static bool colorLabelPairsInitialized; #endif // HAVE_KF5 }; QDebug operator<<(QDebug dbg, const VerbatimText &verbatimText); /** * Container class to hold values of BibTeX entry fields and similar value types in BibTeX file. * A Value object is built from a list of @see ValueItem objects. * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXDATA_EXPORT Value: public QVector<QSharedPointer<ValueItem> > { public: Value(); Value(const Value &other); Value(Value &&other); virtual ~Value(); void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode); void replace(const QString &before, const QSharedPointer<ValueItem> &after); /** * Check if this value contains text pattern @p pattern. * @param pattern Pattern to check for * @param caseSensitive Case sensitivity setting for check * @return TRUE if pattern is contained within this value, otherwise FALSE */ bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const; bool contains(const ValueItem &item) const; Value &operator=(const Value &rhs); Value &operator=(Value &&rhs); Value &operator<<(const QSharedPointer<ValueItem> &value); bool operator==(const Value &rhs) const; bool operator!=(const Value &rhs) const; }; QDebug operator<<(QDebug dbg, const Value &value); class KBIBTEXDATA_EXPORT PlainTextValue #ifdef HAVE_KF5 : private NotificationListener #endif // HAVE_KF5 { public: static QString text(const Value &value); static QString text(const ValueItem &valueItem); static QString text(const QSharedPointer<const ValueItem> &valueItem); #ifdef HAVE_KF5 void notificationEvent(int eventId) override; #endif // HAVE_KF5 private: enum ValueItemType { VITOther = 0, VITPerson, VITKeyword}; #ifdef HAVE_KF5 PlainTextValue(); void readConfiguration(); static QString personNameFormatting; #else // HAVE_KF5 static const QString personNameFormatting; #endif // HAVE_KF5 static QString text(const ValueItem &valueItem, ValueItemType &vit); }; Q_DECLARE_METATYPE(Value) #endif diff --git a/src/global/preferences.cpp b/src/global/preferences.cpp index 36e9ad9e..38014fc3 100644 --- a/src/global/preferences.cpp +++ b/src/global/preferences.cpp @@ -1,102 +1,117 @@ /*************************************************************************** * 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 "preferences.h" #include <QDebug> +#ifdef HAVE_KF5 #include <KLocalizedString> #include <KSharedConfig> #include <KConfigGroup> +#else // HAVE_KF5 +#define I18N_NOOP(text) QObject::tr(text) +#define i18n(text) QObject::tr(text) +#endif // HAVE_KF5 +#ifdef HAVE_KF5 #include "notificationhub.h" +#endif // HAVE_KF5 const QString Preferences::groupColor = QStringLiteral("Color Labels"); const QString Preferences::keyColorCodes = QStringLiteral("colorCodes"); const QStringList Preferences::defaultColorCodes {QStringLiteral("#cc3300"), QStringLiteral("#0033ff"), QStringLiteral("#009966"), QStringLiteral("#f0d000")}; const QString Preferences::keyColorLabels = QStringLiteral("colorLabels"); // FIXME // clazy warns: QString(const char*) being called [-Wclazy-qstring-uneeded-heap-allocations] // ... but using QStringLiteral may break the translation process? const QStringList Preferences::defaultColorLabels {I18N_NOOP("Important"), I18N_NOOP("Unread"), I18N_NOOP("Read"), I18N_NOOP("Watch")}; const QString Preferences::groupGeneral = QStringLiteral("General"); const QString Preferences::keyBackupScope = QStringLiteral("backupScope"); const Preferences::BackupScope Preferences::defaultBackupScope = LocalOnly; const QString Preferences::keyNumberOfBackups = QStringLiteral("numberOfBackups"); const int Preferences::defaultNumberOfBackups = 5; const QString Preferences::groupUserInterface = QStringLiteral("User Interface"); const QString Preferences::keyElementDoubleClickAction = QStringLiteral("elementDoubleClickAction"); const Preferences::ElementDoubleClickAction Preferences::defaultElementDoubleClickAction = ActionOpenEditor; const QString Preferences::keyEncoding = QStringLiteral("encoding"); const QString Preferences::defaultEncoding = QStringLiteral("LaTeX"); const QString Preferences::keyStringDelimiter = QStringLiteral("stringDelimiter"); const QString Preferences::defaultStringDelimiter = QStringLiteral("{}"); const QString Preferences::keyQuoteComment = QStringLiteral("quoteComment"); const Preferences::QuoteComment Preferences::defaultQuoteComment = qcNone; const QString Preferences::keyKeywordCasing = QStringLiteral("keywordCasing"); const KBibTeX::Casing Preferences::defaultKeywordCasing = KBibTeX::cLowerCase; const QString Preferences::keyProtectCasing = QStringLiteral("protectCasing"); const Qt::CheckState Preferences::defaultProtectCasing = Qt::PartiallyChecked; const QString Preferences::keyListSeparator = QStringLiteral("ListSeparator"); const QString Preferences::defaultListSeparator = QStringLiteral("; "); /** * Preferences for Data objects */ const QString Preferences::keyPersonNameFormatting = QStringLiteral("personNameFormatting"); const QString Preferences::personNameFormatLastFirst = QStringLiteral("<%l><, %s><, %f>"); const QString Preferences::personNameFormatFirstLast = QStringLiteral("<%f ><%l>< %s>"); const QString Preferences::defaultPersonNameFormatting = personNameFormatLastFirst; const Preferences::BibliographySystem Preferences::defaultBibliographySystem = Preferences::BibTeX; Preferences::BibliographySystem Preferences::bibliographySystem() { +#ifdef HAVE_KF5 static KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))); static const KConfigGroup configGroup(config, QStringLiteral("General")); config->reparseConfiguration(); const int index = configGroup.readEntry(QStringLiteral("BibliographySystem"), static_cast<int>(defaultBibliographySystem)); if (index != static_cast<int>(Preferences::BibTeX) && index != static_cast<int>(Preferences::BibLaTeX)) { qWarning() << "Configuration file setting for Bibliography System has an invalid value, using default as fallback"; setBibliographySystem(defaultBibliographySystem); return defaultBibliographySystem; } else return static_cast<Preferences::BibliographySystem>(index); +#else // HAVE_KF5 + return defaultBibliographySystem; +#endif // HAVE_KF5 } bool Preferences::setBibliographySystem(const Preferences::BibliographySystem bibliographySystem) { +#ifdef HAVE_KF5 static KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))); static KConfigGroup configGroup(config, QStringLiteral("General")); config->reparseConfiguration(); const int prevIndex = configGroup.readEntry(QStringLiteral("BibliographySystem"), static_cast<int>(defaultBibliographySystem)); const int newIndex = static_cast<int>(bibliographySystem); if (prevIndex == newIndex) return false; /// If old and new bibliography system are the same, return 'false' directly configGroup.writeEntry(QStringLiteral("BibliographySystem"), newIndex); config->sync(); NotificationHub::publishEvent(NotificationHub::EventBibliographySystemChanged); +#else // HAVE_KF5 + Q_UNUSED(bibliographySystem); +#endif // HAVE_KF5 return true; } const QMap<Preferences::BibliographySystem, QString> Preferences::availableBibliographySystems() { static const QMap<Preferences::BibliographySystem, QString> result {{Preferences::BibTeX, i18n("BibTeX")}, {Preferences::BibLaTeX, i18n("BibLaTeX")}}; return result; } diff --git a/src/io/encoderlatex.h b/src/io/encoderlatex.h index 7967df33..f929cbd7 100644 --- a/src/io/encoderlatex.h +++ b/src/io/encoderlatex.h @@ -1,107 +1,109 @@ /*************************************************************************** * 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 ENCODERLATEX_H #define ENCODERLATEX_H +#ifdef HAVE_KF5 #include "kbibtexio_export.h" +#endif // HAVE_KF5 #ifdef HAVE_ICU #include <unicode/translit.h> #endif // HAVE_ICU #include <QIODevice> #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 <fischer@unix-ag.uni-kl.de> */ class KBIBTEXIO_EXPORT EncoderLaTeX: public Encoder { public: QString decode(const QString &text) const override; QString encode(const QString &text, const TargetEncoding targetEncoding) const override; #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); static const EncoderLaTeX &instance(); ~EncoderLaTeX() override; #ifdef BUILD_TESTING static bool writeLaTeXTables(QIODevice &output); #endif // BUILD_TESTING 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; #ifdef HAVE_ICU icu::Transliterator *m_trans; #endif // HAVE_ICU }; #endif // ENCODERLATEX_H diff --git a/src/io/encoderxml.h b/src/io/encoderxml.h index d33270b5..cbe5980b 100644 --- a/src/io/encoderxml.h +++ b/src/io/encoderxml.h @@ -1,53 +1,55 @@ /*************************************************************************** * 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_ENCODERXML_H #define KBIBTEX_IO_ENCODERXML_H +#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 &auml;. * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ 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/fileimporter.h b/src/io/fileimporter.h index a8f95dd2..1ccba3f5 100644 --- a/src/io/fileimporter.h +++ b/src/io/fileimporter.h @@ -1,112 +1,114 @@ /*************************************************************************** * 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_FILEIMPORTER_H #define KBIBTEX_IO_FILEIMPORTER_H +#ifdef HAVE_KF5 #include "kbibtexio_export.h" +#endif // HAVE_KF5 #include <QObject> 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.h b/src/io/fileimporterbibtex.h index 9ffd422a..7faf5523 100644 --- a/src/io/fileimporterbibtex.h +++ b/src/io/fileimporterbibtex.h @@ -1,171 +1,173 @@ /*************************************************************************** * 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_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" 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: static const char *defaultCodecName; 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()); 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/textencoder.h b/src/io/textencoder.h index 882056a5..4fd5c459 100644 --- a/src/io/textencoder.h +++ b/src/io/textencoder.h @@ -1,52 +1,54 @@ /*************************************************************************** * Copyright (C) 2004-2016 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_TEXTENCODER_H #define KBIBTEX_TEXTENCODER_H +#ifdef HAVE_KF5 #include "kbibtexio_export.h" +#endif // HAVE_KF5 class QString; class QByteArray; class QStringList; class QTextCodec; /** * This class is a specialized wrapper around QTextCodec. It will try to encode * all characters not supported by the chosen encoding using the special * "LaTeX" encoding. * Example: When choosing encoding "iso8859-1" (aka Latin-1), you can encode * a-umlaut (a with two dots above), but not en-dash. Therefore, the en-dash * will be rewritten as "--" which is the LaTeX way to write this character. * On the other side, a-umlaut can be kept. * * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXIO_EXPORT TextEncoder { public: static QByteArray encode(const QString &input, const QString &destinationEncoding); static QByteArray encode(const QString &input, const QTextCodec *destinationCodec); static const QStringList encodings; private: explicit TextEncoder(); }; #endif // KBIBTEX_TEXTENCODER_H diff --git a/src/io/xsltransform.h b/src/io/xsltransform.h index cd04b544..98276d05 100644 --- a/src/io/xsltransform.h +++ b/src/io/xsltransform.h @@ -1,60 +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_XSLTRANSFORM_H #define KBIBTEX_XSLTRANSFORM_H +#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_XSLTRANSFORM_H diff --git a/src/networking/internalnetworkaccessmanager.cpp b/src/networking/internalnetworkaccessmanager.cpp index 9e2ead9a..b6e9d28f 100644 --- a/src/networking/internalnetworkaccessmanager.cpp +++ b/src/networking/internalnetworkaccessmanager.cpp @@ -1,243 +1,245 @@ /*************************************************************************** * 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 "internalnetworkaccessmanager.h" #include <ctime> #include <QStringList> #include <QRegularExpression> #include <QNetworkAccessManager> #include <QNetworkCookieJar> #include <QNetworkCookie> #include <QNetworkProxy> #include <QNetworkRequest> #include <QNetworkReply> #include <QtGlobal> #include <QCoreApplication> #include <QTimer> #include <QUrl> #include <QUrlQuery> +#ifdef HAVE_KF5 #include <KProtocolManager> +#endif // HAVE_KF5 #include "logging_networking.h" /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class InternalNetworkAccessManager::HTTPEquivCookieJar: public QNetworkCookieJar { Q_OBJECT public: void mergeHtmlHeadCookies(const QString &htmlCode, const QUrl &url) { static const QRegularExpression cookieContent("^([^\"=; ]+)=([^\"=; ]+).*\\bpath=([^\"=; ]+)", QRegularExpression::CaseInsensitiveOption); int p1 = -1; QRegularExpressionMatch cookieContentRegExpMatch; if ((p1 = htmlCode.toLower().indexOf(QStringLiteral("http-equiv=\"set-cookie\""), 0, Qt::CaseInsensitive)) >= 5 && (p1 = htmlCode.lastIndexOf(QStringLiteral("<meta"), p1, Qt::CaseInsensitive)) >= 0 && (p1 = htmlCode.indexOf(QStringLiteral("content=\""), p1, Qt::CaseInsensitive)) >= 0 && (cookieContentRegExpMatch = cookieContent.match(htmlCode.mid(p1 + 9, 512))).hasMatch()) { const QString key = cookieContentRegExpMatch.captured(1); const QString value = cookieContentRegExpMatch.captured(2); QList<QNetworkCookie> cookies = cookiesForUrl(url); cookies.append(QNetworkCookie(key.toLatin1(), value.toLatin1())); setCookiesFromUrl(cookies, url); } } HTTPEquivCookieJar(QObject *parent = nullptr) : QNetworkCookieJar(parent) { /// nothing } }; QString InternalNetworkAccessManager::userAgentString; InternalNetworkAccessManager::InternalNetworkAccessManager(QObject *parent) : QNetworkAccessManager(parent) { cookieJar = new HTTPEquivCookieJar(this); } void InternalNetworkAccessManager::mergeHtmlHeadCookies(const QString &htmlCode, const QUrl &url) { Q_ASSERT_X(cookieJar != nullptr, "void InternalNetworkAccessManager::mergeHtmlHeadCookies(const QString &htmlCode, const QUrl &url)", "cookieJar is invalid"); cookieJar->mergeHtmlHeadCookies(htmlCode, url); setCookieJar(cookieJar); } InternalNetworkAccessManager &InternalNetworkAccessManager::instance() { static InternalNetworkAccessManager self; return self; } QNetworkReply *InternalNetworkAccessManager::get(QNetworkRequest &request, const QUrl &oldUrl) { #ifdef HAVE_KF5 /// Query the KDE subsystem if a proxy has to be used /// for the host of a given URL QString proxyHostName = KProtocolManager::proxyForUrl(request.url()); if (!proxyHostName.isEmpty() && proxyHostName != QStringLiteral("DIRECT")) { /// Extract both hostname and port number for proxy proxyHostName = proxyHostName.mid(proxyHostName.indexOf(QStringLiteral("://")) + 3); QStringList proxyComponents = proxyHostName.split(QStringLiteral(":"), QString::SkipEmptyParts); if (proxyComponents.length() == 1) { /// Proxy configuration is missing a port number, /// using 8080 as default proxyComponents << QStringLiteral("8080"); } if (proxyComponents.length() == 2) { /// Set proxy to Qt's NetworkAccessManager setProxy(QNetworkProxy(QNetworkProxy::HttpProxy, proxyComponents[0], proxyComponents[1].toInt())); } } else { /// No proxy to be used, clear previous settings setProxy(QNetworkProxy()); } #else // HAVE_KF5 setProxy(QNetworkProxy()); #endif // HAVE_KF5 if (!request.hasRawHeader(QByteArray("Accept"))) request.setRawHeader(QByteArray("Accept"), QByteArray("text/*, */*;q=0.7")); request.setRawHeader(QByteArray("Accept-Charset"), QByteArray("utf-8, us-ascii, ISO-8859-1;q=0.7, ISO-8859-15;q=0.7, windows-1252;q=0.3")); request.setRawHeader(QByteArray("Accept-Language"), QByteArray("en-US, en;q=0.9")); request.setRawHeader(QByteArray("User-Agent"), userAgent().toLatin1()); if (oldUrl.isValid()) request.setRawHeader(QByteArray("Referer"), removeApiKey(oldUrl).toDisplayString().toLatin1()); QNetworkReply *reply = QNetworkAccessManager::get(request); /// Log SSL errors connect(reply, &QNetworkReply::sslErrors, this, &InternalNetworkAccessManager::logSslErrors); return reply; } QNetworkReply *InternalNetworkAccessManager::get(QNetworkRequest &request, const QNetworkReply *oldReply) { return get(request, oldReply == nullptr ? QUrl() : oldReply->url()); } QString InternalNetworkAccessManager::userAgent() { /// Various browser strings to "disguise" origin static const QStringList userAgentList { QStringLiteral("Mozilla/5.0 (Linux; U; Android 2.3.3; en-us; HTC_DesireS_S510e Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"), QStringLiteral("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) QupZilla/2.0.2 Chrome/45.0.2454.101 Safari/537.36"), QStringLiteral("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36 OPR/38.0.2220.41"), QStringLiteral("Mozilla/5.0 (compatible; Yahoo! Slurp China; http://misc.yahoo.com.cn/help.html)"), QStringLiteral("Mozilla/5.0 (compatible; MSIE 9.0; AOL 9.7; AOLBuild 4343.19; Windows NT 6.1; WOW64; Trident/5.0; FunWebProducts)"), QStringLiteral("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.89 Vivaldi/1.0.83.38 Safari/537.36"), QStringLiteral("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/536.5 (KHTML, like Gecko) YaBrowser/1.0.1084.5402 Chrome/19.0.1084.5402 Safari/536.5"), QStringLiteral("Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)"), QStringLiteral("Mozilla/4.0 (compatible; Dillo 2.2)"), QStringLiteral("Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.201.1 Safari/532.0"), QStringLiteral("Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.813.0 Chrome/14.0.813.0 Safari/535.1"), QStringLiteral("Mozilla/5.0 (X11; U; Linux; de-DE) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.8.0"), QStringLiteral("Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.21 Safari/537.36 MMS/1.0.2459.0"), QStringLiteral("Mozilla/5.0 (X11; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) QtCarBrowser Safari/533.3"), QStringLiteral("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.89 Vivaldi/1.0.83.38 Safari/537.36"), QStringLiteral("Opera/9.80 (X11; Linux i686; U; ru) Presto/2.8.131 Version/11.11"), QStringLiteral("Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727) Sleipnir/2.8.4"), QStringLiteral("Mozilla/5.0 (X11; Linux i686; rv:2.2a1pre) Gecko/20110327 SeaMonkey/2.2a1pre"), QStringLiteral("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10"), QStringLiteral("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13) AppleWebKit/603.1.13 (KHTML, like Gecko) Version/10.1 Safari/603.1.13"), QStringLiteral("Mozilla/5.0 (Linux; U; Tizen/1.0 like Android; en-us; AppleWebKit/534.46 (KHTML, like Gecko) Tizen Browser/1.0 Mobile"), QStringLiteral("Emacs-W3/4.0pre.46 URL/p4.0pre.46 (i686-pc-linux; X11)"), QStringLiteral("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"), QStringLiteral("Lynx/2.8 (compatible; iCab 2.9.8; Macintosh; U; 68K)"), QStringLiteral("Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0"), QStringLiteral("msnbot/2.1"), QStringLiteral("Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10"), QStringLiteral("Mozilla/5.0 (Windows; U; ; en-NZ) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.8.0"), QStringLiteral("NCSA Mosaic/3.0 (Windows 95)"), QStringLiteral("Mozilla/5.0 (SymbianOS/9.1; U; [en]; Series60/3.0 NokiaE60/4.06.0) AppleWebKit/413 (KHTML, like Gecko) Safari/413"), QStringLiteral("Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16") }; if (userAgentString.isEmpty()) { qsrand(time(nullptr)); userAgentString = userAgentList[qrand() % userAgentList.length()]; } return userAgentString; } void InternalNetworkAccessManager::setNetworkReplyTimeout(QNetworkReply *reply, int timeOutSec) { QTimer *timer = new QTimer(reply); connect(timer, &QTimer::timeout, this, &InternalNetworkAccessManager::networkReplyTimeout); m_mapTimerToReply.insert(timer, reply); timer->start(timeOutSec * 1000); connect(reply, &QNetworkReply::finished, this, &InternalNetworkAccessManager::networkReplyFinished); } QString InternalNetworkAccessManager::reverseObfuscate(const QByteArray &a) { if (a.length() % 2 != 0 || a.length() == 0) return QString(); QString result; result.reserve(a.length() / 2); for (int p = a.length() - 1; p >= 0; p -= 2) { const QChar c = QLatin1Char(a.at(p) ^ a.at(p - 1)); result.append(c); } return result; } QUrl InternalNetworkAccessManager::removeApiKey(QUrl url) { QUrlQuery urlQuery(url); urlQuery.removeQueryItem(QStringLiteral("apikey")); urlQuery.removeQueryItem(QStringLiteral("api_key")); url.setQuery(urlQuery); return url; } void InternalNetworkAccessManager::networkReplyTimeout() { QTimer *timer = static_cast<QTimer *>(sender()); timer->stop(); QNetworkReply *reply = m_mapTimerToReply[timer]; if (reply != nullptr) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Timeout on reply to " << removeApiKey(reply->url()).toDisplayString(); reply->close(); m_mapTimerToReply.remove(timer); } } void InternalNetworkAccessManager::networkReplyFinished() { QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QTimer *timer = m_mapTimerToReply.key(reply, nullptr); if (timer != nullptr) { disconnect(timer, &QTimer::timeout, this, &InternalNetworkAccessManager::networkReplyTimeout); timer->stop(); m_mapTimerToReply.remove(timer); } } void InternalNetworkAccessManager::logSslErrors(const QList<QSslError> &errors) { QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); qWarning() << QStringLiteral("Got the following SSL errors when querying the following URL: ") << removeApiKey(reply->url()).toDisplayString(); for (const QSslError &error : errors) qWarning() << QStringLiteral(" * ") + error.errorString() << "; Code: " << static_cast<int>(error.error()); } #include "internalnetworkaccessmanager.moc" diff --git a/src/networking/internalnetworkaccessmanager.h b/src/networking/internalnetworkaccessmanager.h index 2dc952ff..4951a516 100644 --- a/src/networking/internalnetworkaccessmanager.h +++ b/src/networking/internalnetworkaccessmanager.h @@ -1,86 +1,88 @@ /*************************************************************************** * 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_NETWORKING_INTERNALNETWORKACCESSMANAGER_H #define KBIBTEX_NETWORKING_INTERNALNETWORKACCESSMANAGER_H +#ifdef HAVE_KF5 #include "kbibtexnetworking_export.h" +#endif // HAVE_KF5 #include <QNetworkAccessManager> #include <QUrl> #include <QMap> class QNetworkAccessManager; class QNetworkReply; class QTimer; class HTTPEquivCookieJar; /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXNETWORKING_EXPORT InternalNetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: static InternalNetworkAccessManager &instance(); QNetworkReply *get(QNetworkRequest &request, const QUrl &oldUrl); QNetworkReply *get(QNetworkRequest &request, const QNetworkReply *oldReply = nullptr); void mergeHtmlHeadCookies(const QString &htmlCode, const QUrl &url); void setNetworkReplyTimeout(QNetworkReply *reply, int timeOutSec = 30); /** * Reverse the obfuscation of an API key. Given a byte * array holding the obfuscated API key, restore and * return the original API key. * * @param obfuscated obfuscated API key * @return restored original API key if succeeded, empty on error */ static QString reverseObfuscate(const QByteArray &obfuscated); /** * Remove API keys from an URL. * * @param url URL where API keys have to be removed * @return a reference to the URL passed to this function */ static QUrl removeApiKey(QUrl url); protected: InternalNetworkAccessManager(QObject *parent = nullptr); class HTTPEquivCookieJar; HTTPEquivCookieJar *cookieJar; private: QMap<QTimer *, QNetworkReply *> m_mapTimerToReply; static QString userAgentString; static QString userAgent(); private slots: void networkReplyTimeout(); void networkReplyFinished(); void logSslErrors(const QList<QSslError> &errors); }; #endif // KBIBTEX_NETWORKING_INTERNALNETWORKACCESSMANAGER_H diff --git a/src/networking/onlinesearch/onlinesearchabstract.h b/src/networking/onlinesearch/onlinesearchabstract.h index fa317e6d..83a8da4d 100644 --- a/src/networking/onlinesearch/onlinesearchabstract.h +++ b/src/networking/onlinesearch/onlinesearchabstract.h @@ -1,226 +1,228 @@ /*************************************************************************** * 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_ONLINESEARCH_ABSTRACT_H #define KBIBTEX_ONLINESEARCH_ABSTRACT_H +#ifdef HAVE_KF5 #include "kbibtexnetworking_export.h" +#endif // HAVE_KF5 #include <QObject> #include <QMap> #include <QString> #ifdef HAVE_QTWIDGETS #include <QWidget> #endif // HAVE_QTWIDGETS #include <QMetaType> #include <QIcon> #include <QUrl> #ifdef HAVE_KF5 #include <KSharedConfig> #endif // HAVE_KF5 #include "entry.h" class QNetworkReply; class QNetworkRequest; class QListWidgetItem; #ifdef HAVE_QTWIDGETS /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXNETWORKING_EXPORT OnlineSearchQueryFormAbstract : public QWidget { Q_OBJECT public: explicit OnlineSearchQueryFormAbstract(QWidget *parent) : QWidget(parent), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))) { // nothing } ~OnlineSearchQueryFormAbstract() override { /// nothing } virtual bool readyToStart() const = 0; virtual void copyFromEntry(const Entry &) = 0; protected: KSharedConfigPtr config; QStringList authorLastNames(const Entry &entry); signals: void returnPressed(); }; Q_DECLARE_METATYPE(OnlineSearchQueryFormAbstract *) #endif // HAVE_QTWIDGETS /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXNETWORKING_EXPORT OnlineSearchAbstract : public QObject { Q_OBJECT Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) public: explicit OnlineSearchAbstract(QObject *parent); static const QString queryKeyFreeText; static const QString queryKeyTitle; static const QString queryKeyAuthor; static const QString queryKeyYear; static const int resultCancelled; static const int resultNoError; static const int resultUnspecifiedError; static const int resultAuthorizationRequired; static const int resultNetworkError; static const int resultInvalidArguments; #ifdef HAVE_QTWIDGETS virtual void startSearchFromForm(); #endif // HAVE_QTWIDGETS virtual void startSearch(const QMap<QString, QString> &query, int numResults) = 0; virtual QString label() const = 0; QString name(); #ifdef HAVE_QTWIDGETS virtual QIcon icon(QListWidgetItem *listWidgetItem = nullptr); virtual OnlineSearchQueryFormAbstract *customWidget(QWidget *parent); #endif // HAVE_QTWIDGETS virtual QUrl homepage() const = 0; virtual bool busy() const; public slots: void cancel(); protected: QObject *m_parent; bool m_hasBeenCanceled; int numSteps, curStep; virtual QString favIconUrl() const = 0; /** * Split a string along spaces, but keep text in quotation marks together */ static QStringList splitRespectingQuotationMarks(const QString &text); /** * Will check for common problems with downloads via QNetworkReply. It will return true * if there is no problem and you may process this job result. If there is a problem, * this function will notify the user if necessary (KMessageBox), emit a * "stoppedSearch" signal (by invoking "stopSearch"), and return false. * @see handleErrors(KJob*) */ bool handleErrors(QNetworkReply *reply); /** * Will check for common problems with downloads via QNetworkReply. It will return true * if there is no problem and you may process this job result. If there is a problem, * this function will notify the user if necessary (KMessageBox), emit a * "stoppedSearch" signal (by invoking "stopSearch"), and return false. * @see handleErrors(KJob*) * @param newUrl will be set to the new URL if reply contains a redirection, otherwise reply's original URL */ bool handleErrors(QNetworkReply *reply, QUrl &newUrl); /** * Encode a text to be HTTP URL save, e.g. replace '=' by '%3D'. */ static QString encodeURL(QString rawText); static QString decodeURL(QString rawText); QMap<QString, QString> formParameters(const QString &htmlText, int startPos) const; void dumpToFile(const QString &filename, const QString &text); /** * Delay sending of stop signal by a few milliseconds. * Necessary if search stops (is cancelled) already in one * of the startSearch functions. */ void delayedStoppedSearch(int returnCode); /** * Correct the most common problems encountered in fetched entries. * This function should be specialized in each descendant of this class. * @param entry Entry to sanitize */ virtual void sanitizeEntry(QSharedPointer<Entry> entry); /** * Perform the following steps: (1) sanitize entry, (2) add name * of search engine that found the entry, (3) send it back to search * result list. * Returns true if a valid entry was passed to this function and all * steps could be performed. * @param entry Entry to publish * @return returns true if a valid entry was passed to this function and all steps could be performed. */ bool publishEntry(QSharedPointer<Entry> entry); void stopSearch(int errorCode); /** * Allows an online search to notify about a change of its busy state, * i.e. that the public function @see busy may return a different value * than before. If the actual busy state has changed compared to previous * invocations of this function, the signal @see busyChanged will be * emitted. * This function here may be called both on changes from active to * inactive as well as vice versa. */ void refreshBusyProperty(); #ifdef HAVE_KF5 void sendVisualNotification(const QString &text, const QString &title, const QString &icon, int timeout); #endif // HAVE_KF5 private: bool m_previousBusyState; QString m_name; static const char *httpUnsafeChars; #ifdef HAVE_QTWIDGETS QMap<QNetworkReply *, QListWidgetItem *> m_iconReplyToListWidgetItem; #endif // HAVE_QTWIDGETS int m_delayedStoppedSearchReturnCode; QString htmlAttribute(const QString &htmlCode, const int startPos, const QString &attribute) const; bool htmlAttributeIsSelected(const QString &htmlCode, const int startPos, const QString &attribute) const; private slots: #ifdef HAVE_QTWIDGETS void iconDownloadFinished(); #endif // HAVE_QTWIDGETS void delayedStoppedSearchTimer(); signals: void foundEntry(QSharedPointer<Entry>); void stoppedSearch(int); void progress(int, int); void busyChanged(); }; #endif // KBIBTEX_ONLINESEARCH_ABSTRACT_H diff --git a/src/networking/onlinesearch/onlinesearchacmportal.cpp b/src/networking/onlinesearch/onlinesearchacmportal.cpp index d731cad0..227b4ddf 100644 --- a/src/networking/onlinesearch/onlinesearchacmportal.cpp +++ b/src/networking/onlinesearch/onlinesearchacmportal.cpp @@ -1,279 +1,281 @@ /*************************************************************************** * 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 "onlinesearchacmportal.h" #include <QBuffer> #include <QNetworkRequest> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QUrlQuery> #include <QRegularExpression> #include <QRegularExpressionMatchIterator> #ifdef HAVE_KF5 #include <KLocalizedString> +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "file.h" #include "entry.h" #include "fileimporterbibtex.h" #include "internalnetworkaccessmanager.h" #include "logging_networking.h" class OnlineSearchAcmPortal::OnlineSearchAcmPortalPrivate { public: QString joinedQueryString; int numExpectedResults, numFoundResults; const QString acmPortalBaseUrl; int currentSearchPosition; QStringList citationUrls; OnlineSearchAcmPortalPrivate(OnlineSearchAcmPortal *) : numExpectedResults(0), numFoundResults(0), acmPortalBaseUrl(QStringLiteral("https://dl.acm.org/")), currentSearchPosition(0) { /// nothing } void sanitizeBibTeXCode(QString &code) { static const QRegularExpression htmlEncodedChar(QStringLiteral("&#(\\d+);")); QRegularExpressionMatch match; while ((match = htmlEncodedChar.match(code)).hasMatch()) { bool ok = false; QChar c(match.captured(1).toInt(&ok)); if (ok) code = code.replace(match.captured(0), c); } /// ACM's BibTeX code does not properly use various commands. /// For example, ACM writes "Ke\ssler" instead of "Ke{\ss}ler" static const QStringList inlineCommands {QStringLiteral("\\ss")}; for (const QString &cmd : inlineCommands) { const QString wrappedCmd = QString(QStringLiteral("{%1}")).arg(cmd); code = code.replace(cmd, wrappedCmd); } } }; OnlineSearchAcmPortal::OnlineSearchAcmPortal(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchAcmPortalPrivate(this)) { /// nothing } OnlineSearchAcmPortal::~OnlineSearchAcmPortal() { delete d; } void OnlineSearchAcmPortal::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; d->joinedQueryString.clear(); d->currentSearchPosition = 1; d->citationUrls.clear(); d->numFoundResults = 0; emit progress(curStep = 0, numSteps = numResults * 2 + 2); for (QMap<QString, QString>::ConstIterator it = query.constBegin(); it != query.constEnd(); ++it) { // FIXME: Is there a need for percent encoding? d->joinedQueryString.append(it.value() + QStringLiteral(" ")); } d->numExpectedResults = numResults; QNetworkRequest request(d->acmPortalBaseUrl); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchAcmPortal::doneFetchingStartPage); refreshBusyProperty(); } QString OnlineSearchAcmPortal::label() const { return i18n("ACM Digital Library"); } QString OnlineSearchAcmPortal::favIconUrl() const { return QStringLiteral("https://dl.acm.org/favicon.ico"); } QUrl OnlineSearchAcmPortal::homepage() const { return QUrl(QStringLiteral("https://dl.acm.org/")); } void OnlineSearchAcmPortal::doneFetchingStartPage() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { const QString htmlSource = QString::fromUtf8(reply->readAll().constData()); int p1 = -1, p2 = -1, p3 = -1; if ((p1 = htmlSource.indexOf(QStringLiteral("<form name=\"qiksearch\""))) >= 0 && (p2 = htmlSource.indexOf(QStringLiteral("action="), p1)) >= 0 && (p3 = htmlSource.indexOf(QStringLiteral("\""), p2 + 8)) >= 0) { const QString body = QString(QStringLiteral("Go.x=0&Go.y=0&query=%1")).arg(d->joinedQueryString).simplified(); const QString action = decodeURL(htmlSource.mid(p2 + 8, p3 - p2 - 8)); const QUrl url(reply->url().resolved(QUrl(action + QStringLiteral("?") + body))); QNetworkRequest request(url); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchAcmPortal::doneFetchingSearchPage); } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "Search using" << label() << "failed."; stopSearch(resultUnspecifiedError); } } refreshBusyProperty(); } void OnlineSearchAcmPortal::doneFetchingSearchPage() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { const QString htmlSource = QString::fromUtf8(reply->readAll().constData()); static const QRegularExpression citationUrlRegExp(QStringLiteral("citation.cfm\\?id=[0-9][0-9.]+[0-9]"), QRegularExpression::CaseInsensitiveOption); QRegularExpressionMatchIterator citationUrlRegExpMatchIt = citationUrlRegExp.globalMatch(htmlSource); while (citationUrlRegExpMatchIt.hasNext()) { const QRegularExpressionMatch citationUrlRegExpMatch = citationUrlRegExpMatchIt.next(); const QString newUrl = d->acmPortalBaseUrl + citationUrlRegExpMatch.captured(0); d->citationUrls << newUrl; } if (d->currentSearchPosition + 20 < d->numExpectedResults) { d->currentSearchPosition += 20; QUrl url(reply->url()); QUrlQuery query(url); query.addQueryItem(QStringLiteral("start"), QString::number(d->currentSearchPosition)); url.setQuery(query); QNetworkRequest request(url); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchAcmPortal::doneFetchingSearchPage); } else if (!d->citationUrls.isEmpty()) { QNetworkRequest request(d->citationUrls.first()); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchAcmPortal::doneFetchingCitation); d->citationUrls.removeFirst(); } else stopSearch(resultNoError); } refreshBusyProperty(); } void OnlineSearchAcmPortal::doneFetchingCitation() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QSet<const QUrl> bibTeXUrls; if (handleErrors(reply)) { const QString htmlSource = QString::fromUtf8(reply->readAll().constData()); static const QRegularExpression idRegExp(QStringLiteral("citation_abstract_html_url\" content=\"http://dl.acm.org/citation.cfm\\?id=([0-9]+)[.]([0-9]+)")); QRegularExpressionMatchIterator idRegExpMatchIt = idRegExp.globalMatch(htmlSource); while (idRegExpMatchIt.hasNext()) { const QRegularExpressionMatch idRegExpMatch = idRegExpMatchIt.next(); const QString parentId = idRegExpMatch.captured(1); const QString id = idRegExpMatch.captured(2); if (!parentId.isEmpty() && !id.isEmpty()) { const QUrl bibTeXUrl(d->acmPortalBaseUrl + QString(QStringLiteral("/downformats.cfm?id=%1&parent_id=%2&expformat=bibtex")).arg(id, parentId)); bibTeXUrls.insert(bibTeXUrl); } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No citation link found in " << reply->url().toDisplayString() << " parentId=" << parentId; stopSearch(resultNoError); emit progress(curStep = numSteps, numSteps); return; } } if (bibTeXUrls.isEmpty()) qCWarning(LOG_KBIBTEX_NETWORKING) << "No citation link found in " << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); } if (bibTeXUrls.isEmpty()) { if (!d->citationUrls.isEmpty()) { QNetworkRequest request(d->citationUrls.first()); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchAcmPortal::doneFetchingCitation); d->citationUrls.removeFirst(); } else stopSearch(resultNoError); } else { numSteps += bibTeXUrls.count() - 1; for (QSet<const QUrl>::ConstIterator it = bibTeXUrls.constBegin(); it != bibTeXUrls.constEnd(); ++it) { const QUrl &bibTeXUrl = *it; QNetworkRequest request(bibTeXUrl); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchAcmPortal::doneFetchingBibTeX); } } refreshBusyProperty(); } void OnlineSearchAcmPortal::doneFetchingBibTeX() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters QString bibTeXcode = QString::fromUtf8(reply->readAll().constData()); FileImporterBibTeX importer(this); d->sanitizeBibTeXCode(bibTeXcode); File *bibtexFile = importer.fromString(bibTeXcode); if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); if (publishEntry(entry)) ++d->numFoundResults; } delete bibtexFile; } if (!d->citationUrls.isEmpty() && d->numFoundResults < d->numExpectedResults) { QNetworkRequest request(d->citationUrls.first()); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchAcmPortal::doneFetchingCitation); d->citationUrls.removeFirst(); } else stopSearch(resultNoError); } refreshBusyProperty(); } diff --git a/src/networking/onlinesearch/onlinesearcharxiv.cpp b/src/networking/onlinesearch/onlinesearcharxiv.cpp index fc915dc9..8a1d5705 100644 --- a/src/networking/onlinesearch/onlinesearcharxiv.cpp +++ b/src/networking/onlinesearch/onlinesearcharxiv.cpp @@ -1,741 +1,740 @@ /*************************************************************************** * 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 "onlinesearcharxiv.h" #include <QNetworkReply> #include <QRegularExpression> #ifdef HAVE_QTWIDGETS #include <QGridLayout> #include <QLabel> #include <QSpinBox> #include <QTextStream> #endif // HAVE_QTWIDGETS -#ifdef HAVE_QTWIDGETS +#ifdef HAVE_KF5 #include <KLineEdit> #include <KMessageBox> -#endif // HAVE_QTWIDGETS - -#ifdef HAVE_KF5 #include <KConfigGroup> #include <KLocalizedString> +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "fileimporterbibtex.h" #include "xsltransform.h" #include "encoderxml.h" #include "internalnetworkaccessmanager.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS class OnlineSearchArXiv::OnlineSearchQueryFormArXiv : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); lineEditFreeText->setText(configGroup.readEntry(QStringLiteral("freeText"), QString())); numResultsField->setValue(configGroup.readEntry(QStringLiteral("numResults"), 10)); } public: KLineEdit *lineEditFreeText; QSpinBox *numResultsField; OnlineSearchQueryFormArXiv(QWidget *parent) : OnlineSearchQueryFormAbstract(parent), configGroupName(QStringLiteral("Search Engine arXiv.org")) { QGridLayout *layout = new QGridLayout(this); layout->setMargin(0); QLabel *label = new QLabel(i18n("Free text:"), this); layout->addWidget(label, 0, 0, 1, 1); lineEditFreeText = new KLineEdit(this); lineEditFreeText->setClearButtonEnabled(true); lineEditFreeText->setFocus(Qt::TabFocusReason); layout->addWidget(lineEditFreeText, 0, 1, 1, 1); label->setBuddy(lineEditFreeText); connect(lineEditFreeText, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormArXiv::returnPressed); label = new QLabel(i18n("Number of Results:"), this); layout->addWidget(label, 1, 0, 1, 1); numResultsField = new QSpinBox(this); numResultsField->setMinimum(3); numResultsField->setMaximum(100); numResultsField->setValue(20); layout->addWidget(numResultsField, 1, 1, 1, 1); label->setBuddy(numResultsField); layout->setRowStretch(2, 100); loadState(); } bool readyToStart() const override { return !lineEditFreeText->text().isEmpty(); } void copyFromEntry(const Entry &entry) override { lineEditFreeText->setText(authorLastNames(entry).join(QStringLiteral(" ")) + QLatin1Char(' ') + PlainTextValue::text(entry[Entry::ftTitle])); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("freeText"), lineEditFreeText->text()); configGroup.writeEntry(QStringLiteral("numResults"), numResultsField->value()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchArXiv::OnlineSearchArXivPrivate { private: static const QString xsltFilenameBase; public: const XSLTransform xslt; #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormArXiv *form; #endif // HAVE_QTWIDGETS const QString arXivQueryBaseUrl; OnlineSearchArXivPrivate(OnlineSearchArXiv *) : xslt(XSLTransform::locateXSLTfile(xsltFilenameBase)), #ifdef HAVE_QTWIDGETS form(nullptr), #endif // HAVE_QTWIDGETS arXivQueryBaseUrl(QStringLiteral("https://export.arxiv.org/api/query?")) { if (!xslt.isValid()) qCWarning(LOG_KBIBTEX_NETWORKING) << "Failed to initialize XSL transformation based on file '" << xsltFilenameBase << "'"; } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { /// format search terms const auto respectingQuotationMarks = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditFreeText->text()); QStringList queryFragments; queryFragments.reserve(respectingQuotationMarks.size()); for (const QString &queryFragment : respectingQuotationMarks) queryFragments.append(OnlineSearchAbstract::encodeURL(queryFragment)); return QUrl(QString(QStringLiteral("%1search_query=all:\"%3\"&start=0&max_results=%2")).arg(arXivQueryBaseUrl).arg(form->numResultsField->value()).arg(queryFragments.join(QStringLiteral("\"+AND+all:\"")))); ///< join search terms with an AND operation } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { /// format search terms QStringList queryFragments; for (QMap<QString, QString>::ConstIterator it = query.constBegin(); it != query.constEnd(); ++it) { const auto respectingQuotationMarks = OnlineSearchAbstract::splitRespectingQuotationMarks(it.value()); for (const auto &queryFragment : respectingQuotationMarks) queryFragments.append(OnlineSearchAbstract::encodeURL(queryFragment)); } return QUrl(QString(QStringLiteral("%1search_query=all:\"%3\"&start=0&max_results=%2")).arg(arXivQueryBaseUrl).arg(numResults).arg(queryFragments.join(QStringLiteral("\"+AND+all:\"")))); ///< join search terms with an AND operation } void interpreteJournal(Entry &entry) { /** * TODO examples * - https://arxiv.org/abs/1111.5338 * Eur. Phys. J. H 36, 183-201 (2011) * - https://arxiv.org/abs/1111.5348 * IJCSI International Journal of Computer Science Issues, Vol. 8, Issue 3, No. 1, 2011, 224-229 * - https://arxiv.org/abs/1110.3379 * IJCSI International Journal of Computer Science Issues, Vol. 8, Issue 5, No 3, September 2011 ISSN (Online): 1694-0814 * - https://arxiv.org/abs/1102.5769 * The International Journal of Multimedia & Its Applications, 3(1), 2011 * - https://arxiv.org/abs/1003.3022 * American Journal of Physics -- April 2010 -- Volume 78, Issue 4, pp. 377-383 */ static const QRegularExpression /** * Structure: * - journal title: letters, space, dot * - optional: spaces * - volume: number * - volume: number * Examples: * Journal of Inefficient Algorithms 5 (2003) 35-39 * New J. Phys. 10 (2008) 033023 * Physics Letters A 297 (2002) 4-8 * Appl.Phys. B75 (2002) 655-665 * JHEP 0611 (2006) 045 * - https://arxiv.org/abs/1105.4915 * Astrophys. J. 736 (2011) 7 * - https://arxiv.org/abs/astro-ph/0209123 * Astrophys.J. 578 (2002) L103-L106 * - https://arxiv.org/abs/quant-ph/0611139 * Journal of Physics: Conference Series 70 (2007) 012003 * Captures: * 1: journal title * 2: volume * 3: year * 4: page start * 6: page end */ journalRef1(QStringLiteral("^([a-z. ]+[a-z.])\\s*(\\d+)\\s+\\((\\d{4})\\)\\s+([0-9A-Z]+)(-([0-9A-Z]+))?$"), QRegularExpression::CaseInsensitiveOption), /** * Examples: * Journal of Inefficient Algorithms, Vol. 93, No. 2 (2009), pp. 42-51 * Stud. Hist. Phil. Mod. Phys., Vol 33 no 3 (2003), pp. 441-468 * - https://arxiv.org/abs/quant-ph/0311028 * International Journal of Quantum Information, Vol. 1, No. 4 (2003) 427-441 * - https://arxiv.org/abs/1003.3521 * Int. J. Geometric Methods in Modern Physics Vol. 8, No. 1 (2011) 155-165 * Captures: * 1: journal title * 2: volume * 3: number * 4: year * 5: page start * 7: page end */ journalRef2(QStringLiteral("^([a-zA-Z. ]+[a-zA-Z.]),\\s+Vol\\.?\\s+(\\d+)[,]?\\s+No\\.?\\s+(\\d+)\\s+\\((\\d{4})\\)[,]?\\s+(pp\\.\\s+)?(\\d+)(-(\\d+))?$"), QRegularExpression::CaseInsensitiveOption), /** * Examples: * Journal of Inefficient Algorithms, volume 4, number 1, pp. 12-21, 2008 * - https://arxiv.org/abs/cs/0601030 * Scientometrics, volume 69, number 3, pp. 669-687, 2006 * Captures: * 1: journal title * 2: volume * 3: number * 4: page start * 6: page end * 7: year */ journalRef3(QStringLiteral("^([a-zA-Z. ]+),\\s+volume\\s+(\\d+),\\s+number\\s+(\\d+),\\s+pp\\.\\s+(\\d+)(-(\\d+))?,\\s+(\\d{4})$"), QRegularExpression::CaseInsensitiveOption), /** * Examples: * Journal of Inefficient Algorithms 4(1): 101-122, 2010 * JHEP0809:131,2008 * Phys.Rev.D78:013004,2008 * Lect.NotesPhys.690:107-127,2006 * Europhys. Letters 70:1-7 (2005) * - https://arxiv.org/abs/quant-ph/0608209 * J.Phys.A40:9025-9032,2007 * - https://arxiv.org/abs/hep-th/9907001 * Phys.Rev.Lett.85:5042-5045,2000 * - https://arxiv.org/abs/physics/0606007 * Journal of Conflict Resolution 51(1): 58 - 88 (2007) * - https://arxiv.org/abs/arxiv:cs/0609126 * Learn.Publ.20:16-22,2007 * - https://arxiv.org/abs/cs/9901005 * Journal of Artificial Intelligence Research (JAIR), 9:247-293 * - https://arxiv.org/abs/hep-ph/0403113 * Nucl.Instrum.Meth.A534:250-259,2004 * Captures: * 1: journal title * 2: volume * 4: number * 5: page start * 7: page end * 9 or 10: year */ journalRef4(QStringLiteral("^([a-z. ()]+)[,]?\\s*(\\d+)(\\((\\d+)\\))?:\\s*(\\d+)(\\s*-\\s*(\\d+))?(,\\s*(\\d{4})|\\s+\\((\\d{4})\\))?$"), QRegularExpression::CaseInsensitiveOption), /** * Examples: * Journal of Inefficient Algorithms vol. 31, 4 2000 * Phys. Rev. A 71, 032339 (2005) * Phys. Rev. Lett. 91, 027901 (2003) * Phys. Rev. A 78, 013620 (2008) * Phys. Rev. E 62, 1842 (2000) * J. Math. Phys. 49, 032105 (2008) * Phys. Rev. Lett. 91, 217905 (2003). * Physical Review B vol. 66, 161320(R) (2002) * - https://arxiv.org/abs/1003.1050 * Phys. Rev. A 82, 012304 (2010) * - https://arxiv.org/abs/quant-ph/0607107 * J. Mod. Opt., 54, 2211 (2007) * - https://arxiv.org/abs/quant-ph/0602069 * New J. Phys. 8, 58 (2006) * - https://arxiv.org/abs/quant-ph/0610030 * Rev. Mod. Phys. 79, 555 (2007) * - https://arxiv.org/abs/1007.2292 * J. Phys. A: Math. Theor. 44, 145304 (2011) * Captures: * 1: journal title * 3: volume * 4: page start * 6: year */ journalRef5(QStringLiteral("^([a-zA-Z. ]+)\\s+(vol\\.\\s+)?(\\d+),\\s+(\\d+)(\\([A-Z]+\\))?\\s+\\((\\d{4})\\)[.]?$")), /** * Examples: * Journal of Inefficient Algorithms, 11(2) (1999) 42-55 * Learned Publishing, 20(1) (January 2007) 16-22 * Captures: * 1: journal title * 2: volume * 3: number * 4: year * 5: page start * 7: page end */ journalRef6(QStringLiteral("^([a-zA-Z. ]+),\\s+(\\d+)\\((\\d+)\\)\\s+(\\(([A-Za-z]+\\s+)?(\\d{4})\\))?\\s+(\\d+)(-(\\d+))?$")), /** * Examples: * Pacific J. Math. 231 (2007), no. 2, 279–291 * Captures: * 1: journal title * 2: volume * 3: year * 4: number * 5: page start * 6: page end */ journalRef7(QStringLiteral("^([a-zA-Z. ]+[a-zA-Z.])\\s+(\\d+)\\s+\\((\\d+)\\), no\\.\\s+(\\d+),\\s+(\\d)[^ ]+(\\d+)$")), generalJour(QStringLiteral("^[a-z0-9]{,3}[a-z. ]+"), QRegularExpression::CaseInsensitiveOption), generalYear(QStringLiteral("\\b(18|19|20)\\d{2}\\b")), generalPages(QStringLiteral("\\b([1-9]\\d{0,2})\\s*[-]+\\s*([1-9]\\d{0,2})\\b")); const QString journalText = PlainTextValue::text(entry.value(Entry::ftJournal)); /// nothing to do on empty journal text if (journalText.isEmpty()) return; else entry.remove(Entry::ftJournal); QRegularExpressionMatch match; if ((match = journalRef1.match(journalText)).hasMatch()) { /** * Captures: * 1: journal title * 2: volume * 3: year * 4: page start * 6: page end */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(3)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } if (!(text = match.captured(4)).isEmpty()) { const QString endPage = match.captured(6); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1--%2")).arg(text, endPage)))); entry.insert(Entry::ftPages, v); } } } else if ((match = journalRef2.match(journalText)).hasMatch()) { /** * Captures: * 1: journal title * 2: volume * 3: number * 4: year * 5: page start * 7: page end */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(3)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftNumber, v); } if (!(text = match.captured(4)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } if (!(text = match.captured(5)).isEmpty()) { const QString endPage = match.captured(7); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1%3%2")).arg(text, endPage, QChar(0x2013))))); entry.insert(Entry::ftPages, v); } } } else if ((match = journalRef3.match(journalText)).hasMatch()) { /** * Captures: * 1: journal title * 2: volume * 4: number * 5: page start * 7: page end * 9 or 10: year */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(4)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftNumber, v); } if (!(text = match.captured(9)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); if (!(text = match.captured(10)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } if (!(text = match.captured(5)).isEmpty()) { const QString endPage = match.captured(7); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1%3%2")).arg(text, endPage, QChar(0x2013))))); entry.insert(Entry::ftPages, v); } } } } else if ((match = journalRef4.match(journalText)).hasMatch()) { /** * Captures: * 1: journal title * 2: volume * 4: number * 5: page start * 7: page end * 9 or 10: year */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(5)).isEmpty()) { const QString endPage = match.captured(7); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text.append(QChar(0x2013)).append(endPage)))); entry.insert(Entry::ftPages, v); } } if (!(text = match.captured(9)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } } else if ((match = journalRef5.match(journalText)).hasMatch()) { /** Captures: * 1: journal title * 3: volume * 4: page start * 6: year */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(3)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(4)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } if (!(text = match.captured(6)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } } else if ((match = journalRef6.match(journalText)).hasMatch()) { /** Captures: * 1: journal title * 2: volume * 3: number * 4: year * 5: page start * 7: page end */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(3)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftNumber, v); } if (!(text = match.captured(4)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } if (!(text = match.captured(5)).isEmpty()) { const QString endPage = match.captured(7); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1%3%2")).arg(text, endPage, QChar(0x2013))))); entry.insert(Entry::ftPages, v); } } } else if ((match = journalRef7.match(journalText)).hasMatch()) { /** Captures: * 1: journal title * 2: volume * 3: year * 4: number * 5: page start * 6: page end */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(3)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } if (!(text = match.captured(4)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftNumber, v); } if (!(text = match.captured(5)).isEmpty()) { const QString endPage = match.captured(6); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1%3%2")).arg(text, endPage, QChar(0x2013))))); entry.insert(Entry::ftPages, v); } } } else { if ((match = generalJour.match(journalText)).hasMatch()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(match.captured(0)))); entry.insert(Entry::ftPages, v); } if ((match = generalYear.match(journalText)).hasMatch()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(match.captured(0)))); entry.insert(Entry::ftYear, v); } if ((match = generalPages.match(journalText)).hasMatch()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(match.captured(0)))); entry.insert(Entry::ftPages, v); } } } }; const QString OnlineSearchArXiv::OnlineSearchArXivPrivate::xsltFilenameBase = QStringLiteral("arxiv2bibtex.xsl"); OnlineSearchArXiv::OnlineSearchArXiv(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchArXiv::OnlineSearchArXivPrivate(this)) { /// nothing } OnlineSearchArXiv::~OnlineSearchArXiv() { delete d; } #ifdef HAVE_QTWIDGETS void OnlineSearchArXiv::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl()); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchArXiv::downloadDone); d->form->saveState(); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS void OnlineSearchArXiv::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl(query, numResults)); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchArXiv::downloadDone); refreshBusyProperty(); } QString OnlineSearchArXiv::label() const { return i18n("arXiv.org"); } QString OnlineSearchArXiv::favIconUrl() const { return QStringLiteral("https://arxiv.org/favicon.ico"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchArXiv::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchArXiv::OnlineSearchQueryFormArXiv(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchArXiv::homepage() const { return QUrl(QStringLiteral("https://arxiv.org/")); } void OnlineSearchArXiv::downloadDone() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters QString result = QString::fromUtf8(reply->readAll().constData()); result = result.remove(QStringLiteral("xmlns=\"http://www.w3.org/2005/Atom\"")); // FIXME fix arxiv2bibtex.xsl to handle namespace /// use XSL transformation to get BibTeX document from XML result const QString bibTeXcode = EncoderXML::instance().decode(d->xslt.transform(result)); if (bibTeXcode.isEmpty()) { qCWarning(LOG_KBIBTEX_NETWORKING) << "XSL tranformation failed for data from " << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultInvalidArguments); } else { FileImporterBibTeX importer(this); File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } } refreshBusyProperty(); } void OnlineSearchArXiv::sanitizeEntry(QSharedPointer<Entry> entry) { OnlineSearchAbstract::sanitizeEntry(entry); d->interpreteJournal(*entry); } #include "onlinesearcharxiv.moc" diff --git a/src/networking/onlinesearch/onlinesearchbibsonomy.cpp b/src/networking/onlinesearch/onlinesearchbibsonomy.cpp index ca374fef..042daecb 100644 --- a/src/networking/onlinesearch/onlinesearchbibsonomy.cpp +++ b/src/networking/onlinesearch/onlinesearchbibsonomy.cpp @@ -1,285 +1,285 @@ /*************************************************************************** * 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 "onlinesearchbibsonomy.h" #include <QBuffer> #ifdef HAVE_QTWIDGETS #include <QLayout> #include <QSpinBox> #include <QLabel> #include <QIcon> #endif // HAVE_QTWIDGETS #include <QNetworkReply> #ifdef HAVE_KF5 #include <KLocalizedString> #include <KConfigGroup> -#endif // HAVE_KF5 -#ifdef HAVE_QTWIDGETS #include <KComboBox> #include <KMessageBox> #include <KLineEdit> -#endif // HAVE_QTWIDGETS +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) +#endif // HAVE_KF5 #include "fileimporterbibtex.h" #include "file.h" #include "entry.h" #include "internalnetworkaccessmanager.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS class OnlineSearchBibsonomy::OnlineSearchQueryFormBibsonomy : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); comboBoxSearchWhere->setCurrentIndex(configGroup.readEntry(QStringLiteral("searchWhere"), 0)); lineEditSearchTerm->setText(configGroup.readEntry(QStringLiteral("searchTerm"), QString())); numResultsField->setValue(configGroup.readEntry(QStringLiteral("numResults"), 10)); } public: KComboBox *comboBoxSearchWhere; KLineEdit *lineEditSearchTerm; QSpinBox *numResultsField; OnlineSearchQueryFormBibsonomy(QWidget *widget) : OnlineSearchQueryFormAbstract(widget), configGroupName(QStringLiteral("Search Engine Bibsonomy")) { QGridLayout *layout = new QGridLayout(this); layout->setMargin(0); comboBoxSearchWhere = new KComboBox(false, this); layout->addWidget(comboBoxSearchWhere, 0, 0, 1, 1); comboBoxSearchWhere->addItem(i18n("Tag"), "tag"); comboBoxSearchWhere->addItem(i18n("User"), "user"); comboBoxSearchWhere->addItem(i18n("Group"), "group"); comboBoxSearchWhere->addItem(i18n("Author"), "author"); comboBoxSearchWhere->addItem(i18n("Concept"), "concept/tag"); comboBoxSearchWhere->addItem(i18n("BibTeX Key"), "bibtexkey"); comboBoxSearchWhere->addItem(i18n("Everywhere"), "search"); comboBoxSearchWhere->setCurrentIndex(comboBoxSearchWhere->count() - 1); lineEditSearchTerm = new KLineEdit(this); layout->addWidget(lineEditSearchTerm, 0, 1, 1, 1); lineEditSearchTerm->setClearButtonEnabled(true); connect(lineEditSearchTerm, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormBibsonomy::returnPressed); QLabel *label = new QLabel(i18n("Number of Results:"), this); layout->addWidget(label, 1, 0, 1, 1); numResultsField = new QSpinBox(this); numResultsField->setMinimum(3); numResultsField->setMaximum(100); numResultsField->setValue(20); layout->addWidget(numResultsField, 1, 1, 1, 1); label->setBuddy(numResultsField); layout->setRowStretch(2, 100); lineEditSearchTerm->setFocus(Qt::TabFocusReason); loadState(); } bool readyToStart() const override { return !lineEditSearchTerm->text().isEmpty(); } void copyFromEntry(const Entry &entry) override { comboBoxSearchWhere->setCurrentIndex(comboBoxSearchWhere->count() - 1); lineEditSearchTerm->setText(authorLastNames(entry).join(QStringLiteral(" ")) + QLatin1Char(' ') + PlainTextValue::text(entry[Entry::ftTitle])); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("searchWhere"), comboBoxSearchWhere->currentIndex()); configGroup.writeEntry(QStringLiteral("searchTerm"), lineEditSearchTerm->text()); configGroup.writeEntry(QStringLiteral("numResults"), numResultsField->value()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchBibsonomy::OnlineSearchBibsonomyPrivate { public: #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormBibsonomy *form; #endif // HAVE_QTWIDGETS OnlineSearchBibsonomyPrivate(OnlineSearchBibsonomy *) #ifdef HAVE_QTWIDGETS : form(nullptr) #endif // HAVE_QTWIDGETS { /// nothing } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { if (form == nullptr) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Cannot build query url if no form is specified"; return QUrl(); } QString queryString = OnlineSearchAbstract::encodeURL(form->lineEditSearchTerm->text()); return QUrl(QStringLiteral("https://www.bibsonomy.org/bib/") + form->comboBoxSearchWhere->itemData(form->comboBoxSearchWhere->currentIndex()).toString() + QStringLiteral("/") + queryString + QString(QStringLiteral("?items=%1")).arg(form->numResultsField->value())); } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { QString url = QStringLiteral("https://www.bibsonomy.org/bib/"); const bool hasFreeText = !query[queryKeyFreeText].isEmpty(); const bool hasTitle = !query[queryKeyTitle].isEmpty(); const bool hasAuthor = !query[queryKeyAuthor].isEmpty(); const bool hasYear = !query[queryKeyYear].isEmpty(); QString searchType = QStringLiteral("search"); if (hasAuthor && !hasFreeText && !hasTitle && !hasYear) { /// if only the author field is used, a special author search /// on BibSonomy can be used searchType = QStringLiteral("author"); } QStringList queryFragments; for (QMap<QString, QString>::ConstIterator it = query.constBegin(); it != query.constEnd(); ++it) { queryFragments << OnlineSearchAbstract::encodeURL(it.value()); } QString queryString = queryFragments.join(QStringLiteral("%20")); url.append(searchType + QLatin1Char('/') + queryString + QString(QStringLiteral("?items=%1")).arg(numResults)); return QUrl(url); } void sanitizeEntry(QSharedPointer<Entry> entry) { /// if entry contains a description field but no abstract, /// rename description field to abstract const QString ftDescription = QStringLiteral("description"); if (!entry->contains(Entry::ftAbstract) && entry->contains(ftDescription)) { Value v = entry->value(QStringLiteral("description")); entry->insert(Entry::ftAbstract, v); entry->remove(ftDescription); } } }; OnlineSearchBibsonomy::OnlineSearchBibsonomy(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchBibsonomyPrivate(this)) { /// nothing } OnlineSearchBibsonomy::~OnlineSearchBibsonomy() { delete d; } void OnlineSearchBibsonomy::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl(query, numResults)); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchBibsonomy::downloadDone); refreshBusyProperty(); } #ifdef HAVE_QTWIDGETS void OnlineSearchBibsonomy::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl()); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchBibsonomy::downloadDone); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS QString OnlineSearchBibsonomy::label() const { return i18n("Bibsonomy"); } QString OnlineSearchBibsonomy::favIconUrl() const { return QStringLiteral("https://www.bibsonomy.org/resources/image/favicon.png"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchBibsonomy::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchBibsonomy::OnlineSearchQueryFormBibsonomy(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchBibsonomy::homepage() const { return QUrl(QStringLiteral("https://www.bibsonomy.org/")); } void OnlineSearchBibsonomy::downloadDone() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters QString bibTeXcode = QString::fromUtf8(reply->readAll().constData()); if (!bibTeXcode.isEmpty()) { FileImporterBibTeX importer(this); const File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } else { /// returned file is empty stopSearch(resultNoError); } } refreshBusyProperty(); } #include "onlinesearchbibsonomy.moc" diff --git a/src/networking/onlinesearch/onlinesearchbibsonomy.h b/src/networking/onlinesearch/onlinesearchbibsonomy.h index 3951faed..da8bc991 100644 --- a/src/networking/onlinesearch/onlinesearchbibsonomy.h +++ b/src/networking/onlinesearch/onlinesearchbibsonomy.h @@ -1,63 +1,67 @@ /*************************************************************************** * 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_ONLINESEARCH_BIBSONOMY_H #define KBIBTEX_ONLINESEARCH_BIBSONOMY_H #include "onlinesearchabstract.h" +#ifdef HAVE_QTWIDGETS class QSpinBox; + +#ifdef HAVE_KF5 class KComboBox; class KLineEdit; - +#endif // HAVE_KF5 +#endif // HAVE_QTWIDGETS /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXNETWORKING_EXPORT OnlineSearchBibsonomy : public OnlineSearchAbstract { Q_OBJECT public: explicit OnlineSearchBibsonomy(QObject *parent); ~OnlineSearchBibsonomy() override; #ifdef HAVE_QTWIDGETS void startSearchFromForm() override; #endif // HAVE_QTWIDGETS void startSearch(const QMap<QString, QString> &query, int numResults) override; QString label() const override; #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *customWidget(QWidget *parent) override; #endif // HAVE_QTWIDGETS QUrl homepage() const override; protected: QString favIconUrl() const override; private: #ifdef HAVE_QTWIDGETS class OnlineSearchQueryFormBibsonomy; #endif // HAVE_QTWIDGETS class OnlineSearchBibsonomyPrivate; OnlineSearchBibsonomyPrivate *d; private slots: void downloadDone(); }; #endif // KBIBTEX_ONLINESEARCH_BIBSONOMY_H diff --git a/src/networking/onlinesearch/onlinesearchdoi.cpp b/src/networking/onlinesearch/onlinesearchdoi.cpp index 65db7407..371009a2 100644 --- a/src/networking/onlinesearch/onlinesearchdoi.cpp +++ b/src/networking/onlinesearch/onlinesearchdoi.cpp @@ -1,251 +1,251 @@ /*************************************************************************** * 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 "onlinesearchdoi.h" #ifdef HAVE_QTWIDGETS #include <QLabel> #include <QGridLayout> #endif // HAVE_QTWIDGETS #include <QNetworkRequest> #include <QNetworkReply> #include <QRegularExpression> -#ifdef HAVE_QTWIDGETS +#ifdef HAVE_KF5 #include <KLineEdit> #include <KConfigGroup> -#endif // HAVE_QTWIDGETS #include <KLocalizedString> +#endif // HAVE_KF5 #include "kbibtex.h" #include "internalnetworkaccessmanager.h" #include "fileimporterbibtex.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS class OnlineSearchDOI::OnlineSearchQueryFormDOI : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); lineEditDoiNumber->setText(configGroup.readEntry(QStringLiteral("doiNumber"), QString())); } public: KLineEdit *lineEditDoiNumber; OnlineSearchQueryFormDOI(QWidget *widget) : OnlineSearchQueryFormAbstract(widget), configGroupName(QStringLiteral("Search Engine DOI")) { QGridLayout *layout = new QGridLayout(this); layout->setMargin(0); QLabel *label = new QLabel(i18n("DOI:"), this); layout->addWidget(label, 0, 0, 1, 1); lineEditDoiNumber = new KLineEdit(this); layout->addWidget(lineEditDoiNumber, 0, 1, 1, 1); lineEditDoiNumber->setClearButtonEnabled(true); connect(lineEditDoiNumber, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormDOI::returnPressed); layout->setRowStretch(1, 100); lineEditDoiNumber->setFocus(Qt::TabFocusReason); loadState(); } bool readyToStart() const override { return !lineEditDoiNumber->text().isEmpty(); } void copyFromEntry(const Entry &entry) override { lineEditDoiNumber->setText(PlainTextValue::text(entry[Entry::ftDOI])); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("doiNumber"), lineEditDoiNumber->text()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchDOI::OnlineSearchDOIPrivate { public: #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormDOI *form; #endif // HAVE_QTWIDGETS OnlineSearchDOIPrivate(OnlineSearchDOI *parent) #ifdef HAVE_QTWIDGETS : form(nullptr) #endif // HAVE_QTWIDGETS { Q_UNUSED(parent) } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { if (form == nullptr) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Cannot build query url if no form is specified"; return QUrl(); } return QUrl(QStringLiteral("https://dx.doi.org/") + form->lineEditDoiNumber->text()); } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { Q_UNUSED(numResults) const QRegularExpressionMatch doiRegExpMatch = KBibTeX::doiRegExp.match(query[queryKeyFreeText]); if (doiRegExpMatch.hasMatch()) { return QUrl(QStringLiteral("https://dx.doi.org/") + doiRegExpMatch.captured(0)); } return QUrl(); } }; OnlineSearchDOI::OnlineSearchDOI(QWidget *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchDOIPrivate(this)) { // TODO } OnlineSearchDOI::~OnlineSearchDOI() { delete d; } #ifdef HAVE_QTWIDGETS void OnlineSearchDOI::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); const QUrl url = d->buildQueryUrl(); if (url.isValid()) { QNetworkRequest request(url); request.setRawHeader(QByteArray("Accept"), QByteArray("text/bibliography; style=bibtex")); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchDOI::downloadDone); d->form->saveState(); } else delayedStoppedSearch(resultNoError); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS void OnlineSearchDOI::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); const QUrl url = d->buildQueryUrl(query, numResults); if (url.isValid()) { QNetworkRequest request(url); request.setRawHeader(QByteArray("Accept"), QByteArray("text/bibliography; style=bibtex")); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchDOI::downloadDone); refreshBusyProperty(); } else delayedStoppedSearch(resultNoError); } QString OnlineSearchDOI::label() const { return i18n("DOI"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchDOI::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchDOI::OnlineSearchQueryFormDOI(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchDOI::homepage() const { return QUrl(QStringLiteral("https://dx.doi.org/")); } QString OnlineSearchDOI::favIconUrl() const { return QStringLiteral("https://dx.doi.org/favicon.ico"); } void OnlineSearchDOI::downloadDone() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QUrl redirUrl; if (handleErrors(reply, redirUrl)) { if (redirUrl.isValid()) { /// redirection to another url ++numSteps; QNetworkRequest request(redirUrl); request.setRawHeader(QByteArray("Accept"), QByteArray("text/bibliography; style=bibtex")); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchDOI::downloadDone); } else { /// ensure proper treatment of UTF-8 characters const QString bibTeXcode = QString::fromUtf8(reply->readAll().constData()); if (!bibTeXcode.isEmpty()) { FileImporterBibTeX importer(this); File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } else { /// returned file is empty stopSearch(resultNoError); } } } refreshBusyProperty(); } #include "onlinesearchdoi.moc" diff --git a/src/networking/onlinesearch/onlinesearchgooglescholar.cpp b/src/networking/onlinesearch/onlinesearchgooglescholar.cpp index e5bf873e..e9660430 100644 --- a/src/networking/onlinesearch/onlinesearchgooglescholar.cpp +++ b/src/networking/onlinesearch/onlinesearchgooglescholar.cpp @@ -1,467 +1,469 @@ /*************************************************************************** * 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 "onlinesearchgooglescholar.h" #include <QNetworkReply> #include <QIcon> #include <QUrlQuery> #include <QRegularExpression> #include <QTimer> #ifdef HAVE_KF5 #include <KLocalizedString> +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "fileimporterbibtex.h" #include "internalnetworkaccessmanager.h" #include "logging_networking.h" class OnlineSearchGoogleScholar::OnlineSearchGoogleScholarPrivate { public: int numResults; QMap<QString, QPair<QString, QString>> listBibTeXurls; QString queryFreetext, queryAuthor, queryYear; QString startPageUrl; QString advancedSearchPageUrl; QString queryPageUrl; FileImporterBibTeX *importer; OnlineSearchGoogleScholarPrivate(OnlineSearchGoogleScholar *parent) : numResults(0) { importer = new FileImporterBibTeX(parent); startPageUrl = QStringLiteral("http://scholar.google.com/"); queryPageUrl = QStringLiteral("http://%1/scholar"); } ~OnlineSearchGoogleScholarPrivate() { delete importer; } QString documentUrlForBibTeXEntry(const QString &htmlText, int bibLinkPos) { /// Regular expression to detect text of a link to a document static const QRegularExpression documentLinkIndicator(QStringLiteral("\\[(PDF|HTML)\\]"), QRegularExpression::CaseInsensitiveOption); /// Text for link is *before* the BibTeX link in Google's HTML code int posDocumentLinkText = htmlText.lastIndexOf(documentLinkIndicator, bibLinkPos); /// Check position of previous BibTeX link to not extract the wrong document link int posPreviousBib = htmlText.lastIndexOf(QStringLiteral("/scholar.bib"), bibLinkPos - 3); if (posPreviousBib < 0) posPreviousBib = 0; /// no previous BibTeX entry? /// If all found position values look reasonable ... if (posDocumentLinkText > posPreviousBib) { /// There is a [PDF] or [HTML] link for this BibTeX entry, so find URL /// Variables p1 and p2 are used to close in to the document's URL int p1 = htmlText.lastIndexOf(QStringLiteral("<a "), posDocumentLinkText); if (p1 > 0) { p1 = htmlText.indexOf(QStringLiteral("href=\""), p1); if (p1 > 0) { int p2 = htmlText.indexOf(QLatin1Char('"'), p1 + 7); if (p2 > 0) return htmlText.mid(p1 + 6, p2 - p1 - 6).replace(QStringLiteral("&amp;"), QStringLiteral("&")); } } } return QString(); } QString mainUrlForBibTeXEntry(const QString &htmlText, int bibLinkPos) { /// Text for link is *before* the BibTeX link in Google's HTML code int posH3 = htmlText.lastIndexOf(QStringLiteral("<h3 "), bibLinkPos); /// Check position of previous BibTeX link to not extract the wrong document link int posPreviousBib = htmlText.lastIndexOf(QStringLiteral("/scholar.bib"), bibLinkPos - 3); if (posPreviousBib < 0) posPreviousBib = 0; /// no previous BibTeX entry? /// If all found position values look reasonable ... if (posH3 > posPreviousBib) { /// There is a h3 tag for this BibTeX entry, so find URL /// Variables p1 and p2 are used to close in to the document's URL int p1 = htmlText.indexOf(QStringLiteral("href=\""), posH3); if (p1 > 0) { int p2 = htmlText.indexOf(QLatin1Char('"'), p1 + 7); if (p2 > 0) return htmlText.mid(p1 + 6, p2 - p1 - 6).replace(QStringLiteral("&amp;"), QStringLiteral("&")); } } return QString(); } }; OnlineSearchGoogleScholar::OnlineSearchGoogleScholar(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchGoogleScholar::OnlineSearchGoogleScholarPrivate(this)) { /// nothing } OnlineSearchGoogleScholar::~OnlineSearchGoogleScholar() { delete d; } void OnlineSearchGoogleScholar::startSearch(const QMap<QString, QString> &query, int numResults) { d->numResults = numResults; m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = numResults + 4); const auto respectingQuotationMarksFreeText = splitRespectingQuotationMarks(query[queryKeyFreeText]); const auto respectingQuotationMarksTitle = splitRespectingQuotationMarks(query[queryKeyTitle]); QStringList queryFragments; queryFragments.reserve(respectingQuotationMarksFreeText.size() + respectingQuotationMarksTitle.size()); for (const QString &queryFragment : respectingQuotationMarksFreeText) { queryFragments.append(encodeURL(queryFragment)); } for (const QString &queryFragment : respectingQuotationMarksTitle) { queryFragments.append(encodeURL(queryFragment)); } d->queryFreetext = queryFragments.join(QStringLiteral("+")); const auto respectingQuotationMarksAuthor = splitRespectingQuotationMarks(query[queryKeyAuthor]); queryFragments.clear(); queryFragments.reserve(respectingQuotationMarksAuthor.size()); for (const QString &queryFragment : respectingQuotationMarksAuthor) { queryFragments.append(encodeURL(queryFragment)); } d->queryAuthor = queryFragments.join(QStringLiteral("+")); d->queryYear = encodeURL(query[queryKeyYear]); QUrl url(d->startPageUrl); QNetworkRequest request(url); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingStartPage); refreshBusyProperty(); } void OnlineSearchGoogleScholar::doneFetchingStartPage() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QUrl newDomainUrl; if (handleErrors(reply, newDomainUrl)) { if (newDomainUrl.isValid() && newDomainUrl != reply->url()) { /// following redirection to country-specific domain ++numSteps; QNetworkRequest request(newDomainUrl); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingStartPage); } else { /// landed on country-specific domain static const QRegularExpression pathToSettingsPage(QStringLiteral(" href=\"(/scholar_settings[^ \"]*)")); const QString htmlCode = QString::fromUtf8(reply->readAll()); // dumpToFile(QStringLiteral("01-doneFetchingStartPage.html"),htmlCode); const QRegularExpressionMatch pathToSettingsPageMatch = pathToSettingsPage.match(htmlCode); if (!pathToSettingsPageMatch.hasMatch() || pathToSettingsPageMatch.captured(1).isEmpty()) { qCWarning(LOG_KBIBTEX_NETWORKING) << "No link to Google Scholar settings found"; stopSearch(resultNoError); return; } QUrl url = reply->url().resolved(QUrl(decodeURL(pathToSettingsPageMatch.captured(1)))); QUrlQuery query(url); query.removeQueryItem(QStringLiteral("hl")); query.addQueryItem(QStringLiteral("hl"), QStringLiteral("en")); query.removeQueryItem(QStringLiteral("as_sdt")); query.addQueryItem(QStringLiteral("as_sdt"), QStringLiteral("0,5")); url.setQuery(query); const QUrl replyUrl = reply->url(); QTimer::singleShot(250, [this, url, replyUrl]() { QNetworkRequest request(url); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, replyUrl); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingConfigPage); }); } } refreshBusyProperty(); } void OnlineSearchGoogleScholar::doneFetchingConfigPage() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QUrl redirUrl; if (handleErrors(reply, redirUrl)) { if (redirUrl.isValid()) { /// Redirection to another url ++numSteps; QNetworkRequest request(redirUrl); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingConfigPage); } else { const QString htmlText = QString::fromUtf8(reply->readAll().constData()); // dumpToFile(QStringLiteral("02-doneFetchingConfigPage.html"), htmlText); static const QRegularExpression formOpeningTag(QStringLiteral("<form [^>]+action=\"([^\"]*scholar_setprefs[^\"]*)")); const QRegularExpressionMatch formOpeningTagMatch = formOpeningTag.match(htmlText); const int formOpeningTagPos = formOpeningTagMatch.capturedStart(0); if (formOpeningTagPos < 0) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Could not find opening tag for form:" << formOpeningTag.pattern(); stopSearch(resultNoError); return; } QMap<QString, QString> inputMap = formParameters(htmlText, formOpeningTagPos); inputMap[QStringLiteral("hl")] = QStringLiteral("en"); inputMap[QStringLiteral("scis")] = QStringLiteral("yes"); inputMap[QStringLiteral("scisf")] = QStringLiteral("4"); inputMap[QStringLiteral("num")] = QString::number(d->numResults); inputMap[QStringLiteral("submit")] = QString(); QUrl url = reply->url().resolved(QUrl(decodeURL(formOpeningTagMatch.captured(1)))); QUrlQuery query(url); for (QMap<QString, QString>::ConstIterator it = inputMap.constBegin(); it != inputMap.constEnd(); ++it) { query.removeQueryItem(it.key()); query.addQueryItem(it.key(), it.value()); } url.setQuery(query); QTimer::singleShot(250, [this, url, reply]() { QNetworkRequest request(url); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingSetConfigPage); }); } } refreshBusyProperty(); } void OnlineSearchGoogleScholar::doneFetchingSetConfigPage() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QUrl redirUrl; if (handleErrors(reply, redirUrl)) { if (redirUrl.isValid()) { /// Redirection to another url ++numSteps; QNetworkRequest request(redirUrl); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingSetConfigPage); } else { // const QString htmlText = QString::fromUtf8(reply->readAll().constData()); // dumpToFile(QStringLiteral("03-doneFetchingSetConfigPage.html"), htmlText); QUrl url(QString(d->queryPageUrl).arg(reply->url().host())); QUrlQuery query(url); query.addQueryItem(QStringLiteral("as_q"), d->queryFreetext); query.addQueryItem(QStringLiteral("as_sauthors"), d->queryAuthor); query.addQueryItem(QStringLiteral("as_ylo"), d->queryYear); query.addQueryItem(QStringLiteral("as_yhi"), d->queryYear); query.addQueryItem(QStringLiteral("as_vis"), QStringLiteral("1")); ///< include citations query.addQueryItem(QStringLiteral("num"), QString::number(d->numResults)); query.addQueryItem(QStringLiteral("btnG"), QStringLiteral("Search Scholar")); url.setQuery(query); QTimer::singleShot(250, [this, url, reply]() { QNetworkRequest request(url); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingQueryPage); }); } } refreshBusyProperty(); } void OnlineSearchGoogleScholar::doneFetchingQueryPage() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QUrl redirUrl; if (handleErrors(reply, redirUrl)) { if (redirUrl.isValid()) { /// Redirection to another url ++numSteps; QNetworkRequest request(redirUrl); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingQueryPage); } else { const QString htmlText = QString::fromUtf8(reply->readAll().constData()); // dumpToFile(QStringLiteral("04-doneFetchingQueryPage.html"), htmlText); d->listBibTeXurls.clear(); #ifdef HAVE_KF5 if (htmlText.contains(QStringLiteral("enable JavaScript")) || htmlText.contains(QStringLiteral("re not a robot"))) { qCInfo(LOG_KBIBTEX_NETWORKING) << "'Google Scholar' denied scrapping data because it thinks KBibTeX is a robot."; sendVisualNotification(i18n("'Google Scholar' denied scrapping data because it thinks you are a robot."), label(), QStringLiteral("kbibtex"), 7 * 1000); } else { #endif // HAVE_KF5 static const QRegularExpression linkToBib("/scholar.bib\\?[^\" >]+"); QRegularExpressionMatchIterator linkToBibMatchIterator = linkToBib.globalMatch(htmlText); while (linkToBibMatchIterator.hasNext()) { const QRegularExpressionMatch linkToBibMatch = linkToBibMatchIterator.next(); const int pos = linkToBibMatch.capturedStart(); /// Try to figure out [PDF] or [HTML] link associated with BibTeX entry const QString documentUrl = d->documentUrlForBibTeXEntry(htmlText, pos); /// Extract primary link associated with BibTeX entry const QString primaryUrl = d->mainUrlForBibTeXEntry(htmlText, pos); const QString bibtexUrl(QStringLiteral("https://") + reply->url().host() + linkToBibMatch.captured().replace(QStringLiteral("&amp;"), QStringLiteral("&"))); d->listBibTeXurls.insert(bibtexUrl, qMakePair(primaryUrl, documentUrl)); } #ifdef HAVE_KF5 } #endif // HAVE_KF5 if (!d->listBibTeXurls.isEmpty()) { const auto listBibTeXurlsFront = d->listBibTeXurls.begin(); const QString bibtexUrl = listBibTeXurlsFront.key(); const QString primaryUrl = listBibTeXurlsFront.value().first; const QString documentUrl = listBibTeXurlsFront.value().second; QTimer::singleShot(250, [this, bibtexUrl, primaryUrl, documentUrl, reply]() { QNetworkRequest request(bibtexUrl); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); if (!primaryUrl.isEmpty()) { /// Store primary URL as a property of the request/reply newReply->setProperty("primaryurl", QVariant::fromValue<QString>(primaryUrl)); } if (!documentUrl.isEmpty()) { /// Store URL to document as a property of the request/reply newReply->setProperty("documenturl", QVariant::fromValue<QString>(documentUrl)); } InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingBibTeX); }); d->listBibTeXurls.erase(listBibTeXurlsFront); } else stopSearch(resultNoError); } } refreshBusyProperty(); } void OnlineSearchGoogleScholar::doneFetchingBibTeX() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); /// Extract previously stored URLs from reply const QString primaryUrl = reply->property("primaryurl").toString(); const QString documentUrl = reply->property("documenturl").toString(); QUrl newDomainUrl; if (handleErrors(reply, newDomainUrl)) { if (newDomainUrl.isValid() && newDomainUrl != reply->url()) { /// following redirection to country-specific domain ++numSteps; QNetworkRequest request(newDomainUrl); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingBibTeX); } else { /// ensure proper treatment of UTF-8 characters const QString rawText = QString::fromUtf8(reply->readAll()); // dumpToFile(QStringLiteral("05-doneFetchingBibTeX.bib"),rawText); File *bibtexFile = d->importer->fromString(rawText); bool hasEntry = false; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); if (!entry.isNull()) { Value v; v.append(QSharedPointer<VerbatimText>(new VerbatimText(label()))); entry->insert(QStringLiteral("x-fetchedfrom"), v); if (!primaryUrl.isEmpty()) { /// There is an external document associated with this BibTeX entry Value urlValue = entry->value(Entry::ftUrl); urlValue.append(QSharedPointer<VerbatimText>(new VerbatimText(primaryUrl))); entry->insert(Entry::ftUrl, urlValue); } if (!documentUrl.isEmpty() && primaryUrl != documentUrl /** avoid duplicates */) { /// There is a web page associated with this BibTeX entry Value urlValue = entry->value(Entry::ftUrl); urlValue.append(QSharedPointer<VerbatimText>(new VerbatimText(documentUrl))); entry->insert(Entry::ftUrl, urlValue); } emit foundEntry(entry); hasEntry = true; } } delete bibtexFile; } if (!hasEntry) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Searching" << label() << "resulted in invalid BibTeX data:" << rawText; stopSearch(resultUnspecifiedError); } else if (!d->listBibTeXurls.isEmpty()) { const auto listBibTeXurlsFront = d->listBibTeXurls.begin(); const QString bibtexUrl = listBibTeXurlsFront.key(); const QString primaryUrl = listBibTeXurlsFront.value().first; const QString documentUrl = listBibTeXurlsFront.value().second; QNetworkRequest request(bibtexUrl); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); if (!primaryUrl.isEmpty()) { /// Store primary URL as a property of the request/reply newReply->setProperty("primaryurl", QVariant::fromValue<QString>(primaryUrl)); } if (!documentUrl.isEmpty()) { /// Store URL to document as a property of the request/reply newReply->setProperty("documenturl", QVariant::fromValue<QString>(documentUrl)); } connect(newReply, &QNetworkReply::finished, this, &OnlineSearchGoogleScholar::doneFetchingBibTeX); d->listBibTeXurls.erase(listBibTeXurlsFront); } else stopSearch(resultNoError); } } refreshBusyProperty(); } QString OnlineSearchGoogleScholar::label() const { return i18n("Google Scholar"); } QString OnlineSearchGoogleScholar::favIconUrl() const { return QStringLiteral("https://scholar.google.com/favicon-png.ico"); } QUrl OnlineSearchGoogleScholar::homepage() const { return QUrl(QStringLiteral("https://scholar.google.com/")); } diff --git a/src/networking/onlinesearch/onlinesearchieeexplore.cpp b/src/networking/onlinesearch/onlinesearchieeexplore.cpp index 090814ee..f8d15de6 100644 --- a/src/networking/onlinesearch/onlinesearchieeexplore.cpp +++ b/src/networking/onlinesearch/onlinesearchieeexplore.cpp @@ -1,192 +1,194 @@ /*************************************************************************** * 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 "onlinesearchieeexplore.h" #include <QNetworkReply> #include <QUrl> #include <QUrlQuery> #ifdef HAVE_KF5 #include <KLocalizedString> +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "internalnetworkaccessmanager.h" #include "xsltransform.h" #include "encoderxml.h" #include "fileimporterbibtex.h" #include "logging_networking.h" class OnlineSearchIEEEXplore::OnlineSearchIEEEXplorePrivate { private: static const QString xsltFilenameBase; public: static const QUrl apiUrl; const XSLTransform xslt; OnlineSearchIEEEXplorePrivate(OnlineSearchIEEEXplore *) : xslt(XSLTransform::locateXSLTfile(xsltFilenameBase)) { if (!xslt.isValid()) qCWarning(LOG_KBIBTEX_NETWORKING) << "Failed to initialize XSL transformation based on file '" << xsltFilenameBase << "'"; } QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { QUrl queryUrl = apiUrl; QUrlQuery q(queryUrl.query()); /// Free text const QStringList freeTextFragments = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyFreeText]); if (!freeTextFragments.isEmpty()) q.addQueryItem(QStringLiteral("querytext"), QStringLiteral("\"") + freeTextFragments.join(QStringLiteral("\"+\"")) + QStringLiteral("\"")); /// Title const QStringList title = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyTitle]); if (!title.isEmpty()) q.addQueryItem(QStringLiteral("article_title"), QStringLiteral("\"") + title.join(QStringLiteral("\"+\"")) + QStringLiteral("\"")); /// Author const QStringList authors = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyAuthor]); if (!authors.isEmpty()) q.addQueryItem(QStringLiteral("author"), QStringLiteral("\"") + authors.join(QStringLiteral("\"+\"")) + QStringLiteral("\"")); /// Year if (!query[queryKeyYear].isEmpty()) { q.addQueryItem(QStringLiteral("start_year"), query[queryKeyYear]); q.addQueryItem(QStringLiteral("end_year"), query[queryKeyYear]); } /// Sort order of results: newest publications first q.addQueryItem(QStringLiteral("sort_field"), QStringLiteral("publication_year")); q.addQueryItem(QStringLiteral("sort_order"), QStringLiteral("desc")); /// Request numResults many entries q.addQueryItem(QStringLiteral("start_record"), QStringLiteral("1")); q.addQueryItem(QStringLiteral("max_records"), QString::number(numResults)); queryUrl.setQuery(q); return queryUrl; } }; const QString OnlineSearchIEEEXplore::OnlineSearchIEEEXplorePrivate::xsltFilenameBase = QStringLiteral("ieeexploreapiv1-to-bibtex.xsl"); const QUrl OnlineSearchIEEEXplore::OnlineSearchIEEEXplorePrivate::apiUrl(QStringLiteral("https://ieeexploreapi.ieee.org/api/v1/search/articles?format=xml&apikey=") + InternalNetworkAccessManager::reverseObfuscate("\x15\x65\x4b\x2a\x37\x5f\x78\x12\x44\x70\xf8\x8e\x85\xe0\xdb\xae\xb\x7a\x7e\x46\xab\x93\xbc\xc8\xdb\xa8\xa5\xd2\xee\x96\x7e\x7\x37\x54\xa3\xd4\x2b\x5e\x81\xe6\x6f\x17\xb3\xd6\x7b\x1f\x1a\x60")); OnlineSearchIEEEXplore::OnlineSearchIEEEXplore(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchIEEEXplore::OnlineSearchIEEEXplorePrivate(this)) { /// nothing } OnlineSearchIEEEXplore::~OnlineSearchIEEEXplore() { delete d; } void OnlineSearchIEEEXplore::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl(query, numResults)); // FIXME 'ieeexploreapi.ieee.org' uses a SSL/TLS certificate only valid for 'mashery.com' // TODO re-enable certificate validation once problem has been fix (already reported) QSslConfiguration requestSslConfig = request.sslConfiguration(); requestSslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); request.setSslConfiguration(requestSslConfig); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchIEEEXplore::doneFetchingXML); refreshBusyProperty(); } void OnlineSearchIEEEXplore::doneFetchingXML() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QUrl redirUrl; if (handleErrors(reply, redirUrl)) { if (redirUrl.isValid()) { /// redirection to another url ++numSteps; QNetworkRequest request(redirUrl); // FIXME 'ieeexploreapi.ieee.org' uses a SSL/TLS certificate only valid for 'mashery.com' // TODO re-enable certificate validation once problem has been fix (already reported) QSslConfiguration requestSslConfig = request.sslConfiguration(); requestSslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); request.setSslConfiguration(requestSslConfig); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchIEEEXplore::doneFetchingXML); } else { /// ensure proper treatment of UTF-8 characters const QString xmlCode = QString::fromUtf8(reply->readAll().constData()); /// use XSL transformation to get BibTeX document from XML result const QString bibTeXcode = EncoderXML::instance().decode(d->xslt.transform(xmlCode)); if (bibTeXcode.isEmpty()) { qCWarning(LOG_KBIBTEX_NETWORKING) << "XSL tranformation failed for data from " << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultInvalidArguments); } else { FileImporterBibTeX importer(this); File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } } } refreshBusyProperty(); } QString OnlineSearchIEEEXplore::label() const { return i18n("IEEEXplore"); } QString OnlineSearchIEEEXplore::favIconUrl() const { return QStringLiteral("http://ieeexplore.ieee.org/favicon.ico"); } QUrl OnlineSearchIEEEXplore::homepage() const { return QUrl(QStringLiteral("https://ieeexplore.ieee.org/")); } diff --git a/src/networking/onlinesearch/onlinesearchingentaconnect.cpp b/src/networking/onlinesearch/onlinesearchingentaconnect.cpp index 3bd76c01..0550c5a3 100644 --- a/src/networking/onlinesearch/onlinesearchingentaconnect.cpp +++ b/src/networking/onlinesearch/onlinesearchingentaconnect.cpp @@ -1,419 +1,419 @@ /*************************************************************************** * 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 "onlinesearchingentaconnect.h" #include <QBuffer> #ifdef HAVE_QTWIDGETS #include <QLabel> #include <QFormLayout> #include <QSpinBox> #include <QIcon> #endif // HAVE_QTWIDGETS #include <QNetworkReply> #include <QUrlQuery> #ifdef HAVE_KF5 #include <KLocalizedString> #include <KConfigGroup> -#endif // HAVE_KF5 -#ifdef HAVE_QTWIDGETS #include <KLineEdit> -#endif // HAVE_QTWIDGETS +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) +#endif // HAVE_KF5 #include "file.h" #include "entry.h" #include "fileimporterbibtex.h" #include "internalnetworkaccessmanager.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS class OnlineSearchIngentaConnect::OnlineSearchQueryFormIngentaConnect : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); lineEditFullText->setText(configGroup.readEntry(QStringLiteral("fullText"), QString())); lineEditTitle->setText(configGroup.readEntry(QStringLiteral("title"), QString())); lineEditAuthor->setText(configGroup.readEntry(QStringLiteral("author"), QString())); lineEditAbstractKeywords->setText(configGroup.readEntry(QStringLiteral("abstractKeywords"), QString())); lineEditPublication->setText(configGroup.readEntry(QStringLiteral("publication"), QString())); lineEditISSNDOIISBN->setText(configGroup.readEntry(QStringLiteral("ISSNDOIISBN"), QString())); lineEditVolume->setText(configGroup.readEntry(QStringLiteral("volume"), QString())); lineEditIssue->setText(configGroup.readEntry(QStringLiteral("issue"), QString())); numResultsField->setValue(configGroup.readEntry(QStringLiteral("numResults"), 10)); } public: KLineEdit *lineEditFullText; KLineEdit *lineEditTitle; KLineEdit *lineEditAuthor; KLineEdit *lineEditAbstractKeywords; KLineEdit *lineEditPublication; KLineEdit *lineEditISSNDOIISBN; KLineEdit *lineEditVolume; KLineEdit *lineEditIssue; QSpinBox *numResultsField; OnlineSearchQueryFormIngentaConnect(QWidget *widget) : OnlineSearchQueryFormAbstract(widget), configGroupName(QStringLiteral("Search Engine IngentaConnect")) { QFormLayout *layout = new QFormLayout(this); layout->setMargin(0); lineEditFullText = new KLineEdit(this); lineEditFullText->setClearButtonEnabled(true); layout->addRow(i18n("Full text:"), lineEditFullText); connect(lineEditFullText, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); lineEditTitle = new KLineEdit(this); lineEditTitle->setClearButtonEnabled(true); layout->addRow(i18n("Title:"), lineEditTitle); connect(lineEditTitle, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); lineEditAuthor = new KLineEdit(this); lineEditAuthor->setClearButtonEnabled(true); layout->addRow(i18n("Author:"), lineEditAuthor); connect(lineEditAuthor, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); lineEditAbstractKeywords = new KLineEdit(this); lineEditAbstractKeywords->setClearButtonEnabled(true); layout->addRow(i18n("Abstract/Keywords:"), lineEditAbstractKeywords); connect(lineEditAbstractKeywords, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); lineEditPublication = new KLineEdit(this); lineEditPublication->setClearButtonEnabled(true); layout->addRow(i18n("Publication:"), lineEditPublication); connect(lineEditPublication, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); lineEditISSNDOIISBN = new KLineEdit(this); lineEditISSNDOIISBN->setClearButtonEnabled(true); layout->addRow(i18n("ISSN/ISBN/DOI:"), lineEditISSNDOIISBN); connect(lineEditISSNDOIISBN, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); lineEditVolume = new KLineEdit(this); lineEditVolume->setClearButtonEnabled(true); layout->addRow(i18n("Volume:"), lineEditVolume); connect(lineEditVolume, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); lineEditIssue = new KLineEdit(this); lineEditIssue->setClearButtonEnabled(true); layout->addRow(i18n("Issue/Number:"), lineEditIssue); connect(lineEditIssue, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); numResultsField = new QSpinBox(this); layout->addRow(i18n("Number of Results:"), numResultsField); numResultsField->setMinimum(3); numResultsField->setMaximum(100); numResultsField->setValue(10); } bool readyToStart() const override { return !(lineEditFullText->text().isEmpty() && lineEditTitle->text().isEmpty() && lineEditAuthor->text().isEmpty() && lineEditAbstractKeywords->text().isEmpty() && lineEditPublication->text().isEmpty() && lineEditISSNDOIISBN->text().isEmpty() && lineEditVolume->text().isEmpty() && lineEditIssue->text().isEmpty()); } void copyFromEntry(const Entry &entry) override { lineEditTitle->setText(PlainTextValue::text(entry[Entry::ftTitle])); lineEditAuthor->setText(authorLastNames(entry).join(QStringLiteral(" "))); lineEditVolume->setText(PlainTextValue::text(entry[Entry::ftVolume])); lineEditIssue->setText(PlainTextValue::text(entry[Entry::ftNumber])); QString issnDoiIsbn = PlainTextValue::text(entry[Entry::ftDOI]); if (issnDoiIsbn.isEmpty()) issnDoiIsbn = PlainTextValue::text(entry[Entry::ftISBN]); if (issnDoiIsbn.isEmpty()) issnDoiIsbn = PlainTextValue::text(entry[Entry::ftISSN]); lineEditISSNDOIISBN->setText(issnDoiIsbn); QString publication = PlainTextValue::text(entry[Entry::ftJournal]); if (publication.isEmpty()) publication = PlainTextValue::text(entry[Entry::ftBookTitle]); lineEditPublication->setText(publication); // TODO lineEditAbstractKeywords->setText(QString()); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("fullText"), lineEditFullText->text()); configGroup.writeEntry(QStringLiteral("title"), lineEditTitle->text()); configGroup.writeEntry(QStringLiteral("author"), lineEditAuthor->text()); configGroup.writeEntry(QStringLiteral("abstractKeywords"), lineEditAbstractKeywords->text()); configGroup.writeEntry(QStringLiteral("publication"), lineEditPublication->text()); configGroup.writeEntry(QStringLiteral("ISSNDOIISBN"), lineEditISSNDOIISBN->text()); configGroup.writeEntry(QStringLiteral("volume"), lineEditVolume->text()); configGroup.writeEntry(QStringLiteral("issue"), lineEditIssue->text()); configGroup.writeEntry(QStringLiteral("numResults"), numResultsField->value()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchIngentaConnect::OnlineSearchIngentaConnectPrivate { private: const QString ingentaConnectBaseUrl; public: #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormIngentaConnect *form; #endif // HAVE_QTWIDGETS OnlineSearchIngentaConnectPrivate(OnlineSearchIngentaConnect *) : ingentaConnectBaseUrl(QStringLiteral("https://www.ingentaconnect.com/search?format=bib")) #ifdef HAVE_QTWIDGETS , form(nullptr) #endif // HAVE_QTWIDGETS { /// nothing } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { if (form == nullptr) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Cannot build query url if no form is specified"; return QUrl(); } QUrl queryUrl(ingentaConnectBaseUrl); QUrlQuery query(queryUrl); int index = 1; const QStringList chunksFullText = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditFullText->text()); for (const QString &chunk : chunksFullText) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); ///< join search terms with an AND operation query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("fulltext")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksAuthor = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditAuthor->text()); for (const QString &chunk : chunksAuthor) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("author")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksTitle = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditTitle->text()); for (const QString &chunk : chunksTitle) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("title")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksPublication = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditPublication->text()); for (const QString &chunk : chunksPublication) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("journalbooktitle")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksIssue = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditIssue->text()); for (const QString &chunk : chunksIssue) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("issue")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksVolume = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditVolume->text()); for (const QString &chunk : chunksVolume) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("volume")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksKeywords = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditAbstractKeywords->text()); for (const QString &chunk : chunksKeywords) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("tka")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } query.addQueryItem(QStringLiteral("pageSize"), QString::number(form->numResultsField->value())); query.addQueryItem(QStringLiteral("sortDescending"), QStringLiteral("true")); query.addQueryItem(QStringLiteral("subscribed"), QStringLiteral("false")); query.addQueryItem(QStringLiteral("sortField"), QStringLiteral("default")); queryUrl.setQuery(query); return queryUrl; } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { QUrl queryUrl(ingentaConnectBaseUrl); QUrlQuery q(queryUrl); int index = 1; const QStringList chunksFreeText = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyFreeText]); for (const QString &chunk : chunksFreeText) { if (index > 1) q.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); q.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("fulltext")); q.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksAuthor = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyAuthor]); for (const QString &chunk : chunksAuthor) { if (index > 1) q.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); q.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("author")); q.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksTitle = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyTitle]); for (const QString &chunk : chunksTitle) { if (index > 1) q.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); q.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("title")); q.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } /// Field "year" not supported in IngentaConnect's search q.addQueryItem(QStringLiteral("pageSize"), QString::number(numResults)); q.addQueryItem(QStringLiteral("sortDescending"), QStringLiteral("true")); q.addQueryItem(QStringLiteral("subscribed"), QStringLiteral("false")); q.addQueryItem(QStringLiteral("sortField"), QStringLiteral("default")); queryUrl.setQuery(q); return queryUrl; } }; OnlineSearchIngentaConnect::OnlineSearchIngentaConnect(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchIngentaConnectPrivate(this)) { /// nothing } OnlineSearchIngentaConnect::~OnlineSearchIngentaConnect() { delete d; } void OnlineSearchIngentaConnect::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl(query, numResults)); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchIngentaConnect::downloadDone); refreshBusyProperty(); } #ifdef HAVE_QTWIDGETS void OnlineSearchIngentaConnect::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl()); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchIngentaConnect::downloadDone); d->form->saveState(); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS QString OnlineSearchIngentaConnect::label() const { return i18n("IngentaConnect"); } QString OnlineSearchIngentaConnect::favIconUrl() const { return QStringLiteral("http://www.ingentaconnect.com/favicon.ico"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchIngentaConnect::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchIngentaConnect::OnlineSearchQueryFormIngentaConnect(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchIngentaConnect::homepage() const { return QUrl(QStringLiteral("https://www.ingentaconnect.com/")); } void OnlineSearchIngentaConnect::downloadDone() { emit progress(curStep = numSteps, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters QString bibTeXcode = QString::fromUtf8(reply->readAll().constData()); if (!bibTeXcode.isEmpty()) { FileImporterBibTeX importer(this); File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } else { /// returned file is empty stopSearch(resultNoError); } } refreshBusyProperty(); } #include "onlinesearchingentaconnect.moc" diff --git a/src/networking/onlinesearch/onlinesearchingentaconnect.h b/src/networking/onlinesearch/onlinesearchingentaconnect.h index 26bd2508..08ac1f9e 100644 --- a/src/networking/onlinesearch/onlinesearchingentaconnect.h +++ b/src/networking/onlinesearch/onlinesearchingentaconnect.h @@ -1,66 +1,69 @@ /*************************************************************************** * 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_ONLINESEARCH_INGENTACONNECT_H #define KBIBTEX_ONLINESEARCH_INGENTACONNECT_H #include <QByteArray> #include "onlinesearchabstract.h" #ifdef HAVE_QTWIDGETS class QSpinBox; + +#ifdef HAVE_KF5 class KComboBox; class KLineEdit; +#endif // HAVE_KF5 #endif // HAVE_QTWIDGETS /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXNETWORKING_EXPORT OnlineSearchIngentaConnect : public OnlineSearchAbstract { Q_OBJECT public: explicit OnlineSearchIngentaConnect(QObject *parent); ~OnlineSearchIngentaConnect() override; #ifdef HAVE_QTWIDGETS void startSearchFromForm() override; #endif // HAVE_QTWIDGETS void startSearch(const QMap<QString, QString> &query, int numResults) override; QString label() const override; #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *customWidget(QWidget *parent) override; #endif // HAVE_QTWIDGETS QUrl homepage() const override; protected: QString favIconUrl() const override; private: #ifdef HAVE_QTWIDGETS class OnlineSearchQueryFormIngentaConnect; #endif // HAVE_QTWIDGETS class OnlineSearchIngentaConnectPrivate; OnlineSearchIngentaConnectPrivate *d; private slots: void downloadDone(); }; #endif // KBIBTEX_ONLINESEARCH_INGENTACONNECT_H diff --git a/src/networking/onlinesearch/onlinesearchjstor.cpp b/src/networking/onlinesearch/onlinesearchjstor.cpp index 0f407eee..6af6e4a3 100644 --- a/src/networking/onlinesearch/onlinesearchjstor.cpp +++ b/src/networking/onlinesearch/onlinesearchjstor.cpp @@ -1,300 +1,302 @@ /*************************************************************************** * 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 "onlinesearchjstor.h" #include <QNetworkRequest> #include <QNetworkReply> #include <QUrlQuery> #include <QRegularExpression> #ifdef HAVE_KF5 #include <KLocalizedString> +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "internalnetworkaccessmanager.h" #include "fileimporterbibtex.h" #include "logging_networking.h" class OnlineSearchJStor::OnlineSearchJStorPrivate { public: int numExpectedResults; static const QString jstorBaseUrl; QUrl queryUrl; OnlineSearchJStorPrivate(OnlineSearchJStor *) : numExpectedResults(0) { /// nothing } }; const QString OnlineSearchJStor::OnlineSearchJStorPrivate::jstorBaseUrl = QStringLiteral("https://www.jstor.org/"); OnlineSearchJStor::OnlineSearchJStor(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchJStorPrivate(this)) { /// nothing } OnlineSearchJStor::~OnlineSearchJStor() { delete d; } void OnlineSearchJStor::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 3); d->numExpectedResults = numResults; /// Build search URL, to be used in the second step /// after fetching the start page d->queryUrl = QUrl(OnlineSearchJStorPrivate::jstorBaseUrl); QUrlQuery q(d->queryUrl); d->queryUrl.setPath(QStringLiteral("/action/doAdvancedSearch")); q.addQueryItem(QStringLiteral("Search"), QStringLiteral("Search")); q.addQueryItem(QStringLiteral("wc"), QStringLiteral("on")); /// include external references, too q.addQueryItem(QStringLiteral("la"), QString()); /// no specific language q.addQueryItem(QStringLiteral("jo"), QString()); /// no specific journal q.addQueryItem(QStringLiteral("hp"), QString::number(numResults)); /// hits per page int queryNumber = 0; const QStringList elementsTitle = splitRespectingQuotationMarks(query[queryKeyTitle]); for (const QString &element : elementsTitle) { if (queryNumber > 0) q.addQueryItem(QString(QStringLiteral("c%1")).arg(queryNumber), QStringLiteral("AND")); ///< join search terms with an AND operation q.addQueryItem(QString(QStringLiteral("f%1")).arg(queryNumber), QStringLiteral("ti")); q.addQueryItem(QString(QStringLiteral("q%1")).arg(queryNumber), element); ++queryNumber; } const QStringList elementsAuthor = splitRespectingQuotationMarks(query[queryKeyAuthor]); for (const QString &element : elementsAuthor) { if (queryNumber > 0) q.addQueryItem(QString(QStringLiteral("c%1")).arg(queryNumber), QStringLiteral("AND")); ///< join search terms with an AND operation q.addQueryItem(QString(QStringLiteral("f%1")).arg(queryNumber), QStringLiteral("au")); q.addQueryItem(QString(QStringLiteral("q%1")).arg(queryNumber), element); ++queryNumber; } const QStringList elementsFreeText = splitRespectingQuotationMarks(query[queryKeyFreeText]); for (const QString &element : elementsFreeText) { if (queryNumber > 0) q.addQueryItem(QString(QStringLiteral("c%1")).arg(queryNumber), QStringLiteral("AND")); ///< join search terms with an AND operation q.addQueryItem(QString(QStringLiteral("f%1")).arg(queryNumber), QStringLiteral("all")); q.addQueryItem(QString(QStringLiteral("q%1")).arg(queryNumber), element); ++queryNumber; } if (!query[queryKeyYear].isEmpty()) { q.addQueryItem(QStringLiteral("sd"), query[queryKeyYear]); q.addQueryItem(QStringLiteral("ed"), query[queryKeyYear]); } d->queryUrl.setQuery(q); QNetworkRequest request(OnlineSearchJStorPrivate::jstorBaseUrl); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchJStor::doneFetchingStartPage); refreshBusyProperty(); } QString OnlineSearchJStor::label() const { return i18n("JSTOR"); } QString OnlineSearchJStor::favIconUrl() const { return QStringLiteral("https://www.jstor.org/assets/search_20151218T0921/files/search/images/favicon.ico"); } QUrl OnlineSearchJStor::homepage() const { return QUrl(QStringLiteral("https://www.jstor.org/")); } void OnlineSearchJStor::doneFetchingStartPage() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QUrl redirUrl; if (handleErrors(reply, redirUrl)) { if (redirUrl.isValid()) { ++numSteps; /// redirection to another url QNetworkRequest request(redirUrl); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply->url()); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchJStor::doneFetchingStartPage); } else { QNetworkRequest request(d->queryUrl); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchJStor::doneFetchingResultPage); } } refreshBusyProperty(); } void OnlineSearchJStor::doneFetchingResultPage() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters QString htmlText = QString::fromUtf8(reply->readAll().constData()); /// extract all unique DOI from HTML code QStringList uniqueDOIs; QRegularExpressionMatchIterator doiRegExpMatchIt = KBibTeX::doiRegExp.globalMatch(htmlText); while (doiRegExpMatchIt.hasNext()) { const QRegularExpressionMatch doiRegExpMatch = doiRegExpMatchIt.next(); QString doi = doiRegExpMatch.captured(0); // FIXME DOI RegExp accepts DOIs with question marks, causes problems here const int p = doi.indexOf(QLatin1Char('?')); if (p > 0) doi = doi.left(p); if (!uniqueDOIs.contains(doi)) uniqueDOIs.append(doi); } if (uniqueDOIs.isEmpty()) { /// No results found stopSearch(resultNoError); } else { /// Build POST request that should return a BibTeX document QString body; int numResults = 0; for (QStringList::ConstIterator it = uniqueDOIs.constBegin(); numResults < d->numExpectedResults && it != uniqueDOIs.constEnd(); ++it, ++numResults) { if (!body.isEmpty()) body.append(QStringLiteral("&")); body.append(QStringLiteral("citations=") + encodeURL(*it)); } QUrl bibTeXUrl = QUrl(OnlineSearchJStorPrivate::jstorBaseUrl); bibTeXUrl.setPath(QStringLiteral("/citation/bulk/text")); QNetworkRequest request(bibTeXUrl); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QNetworkReply *newReply = InternalNetworkAccessManager::instance().post(request, body.toUtf8()); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchJStor::doneFetchingBibTeXCode); } } refreshBusyProperty(); } void OnlineSearchJStor::doneFetchingBibTeXCode() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters const QString bibTeXcode = QString::fromUtf8(reply->readAll().constData()); FileImporterBibTeX importer(this); File *bibtexFile = importer.fromString(bibTeXcode); int numFoundResults = 0; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); if (publishEntry(entry)) ++numFoundResults; } delete bibtexFile; } stopSearch(numFoundResults > 0 ? resultNoError : resultUnspecifiedError); } refreshBusyProperty(); } void OnlineSearchJStor::sanitizeEntry(QSharedPointer<Entry> entry) { OnlineSearchAbstract::sanitizeEntry(entry); const QRegularExpressionMatch doiRegExpMatch = KBibTeX::doiRegExp.match(entry->id()); if (doiRegExpMatch.hasMatch()) { /// entry ID is a DOI Value v; v.append(QSharedPointer<VerbatimText>(new VerbatimText(doiRegExpMatch.captured(0)))); entry->insert(Entry::ftDOI, v); } QString url = PlainTextValue::text(entry->value(Entry::ftUrl)); if (url.startsWith(QStringLiteral("https://www.jstor.org/stable/"))) { /// use JSTOR's own stable ID for entry ID entry->setId("jstor" + url.mid(28).replace(QLatin1Char(','), QString())); /// store JSTOR's own stable ID Value v; v.append(QSharedPointer<VerbatimText>(new VerbatimText(url.mid(28)))); entry->insert(QStringLiteral("jstor_id"), v); } const QString formattedDateKey = QStringLiteral("jstor_formatteddate"); const QString formattedDate = PlainTextValue::text(entry->value(formattedDateKey)); /// first, try to guess month by inspecting the beginning /// the jstor_formatteddate field const QString formattedDateLower = formattedDate.toLower(); int i; for (i = 0; i < 12; ++i) if (formattedDateLower.startsWith(KBibTeX::MonthsTriple[i])) break; entry->remove(formattedDateKey); if (i < 12) { Value v; v.append(QSharedPointer<MacroKey>(new MacroKey(KBibTeX::MonthsTriple[i]))); entry->insert(Entry::ftMonth, v); } /// guessing failed, therefore extract first part if it exists else if ((i = formattedDate.indexOf(QStringLiteral(","))) >= 0) { /// text may be a season ("Winter") Value v; v.append(QSharedPointer<PlainText>(new PlainText(formattedDate.left(i)))); entry->insert(Entry::ftMonth, v); } else { /// this case happens if the field only contains a year //qCDebug(LOG_KBIBTEX_NETWORKING) << "Cannot extract month/season from date" << formattedDate; } /// page field may start with "pp. ", remove that QString pages = PlainTextValue::text(entry->value(Entry::ftPages)).toLower(); if (pages.startsWith(QStringLiteral("pp. "))) { pages = pages.mid(4); entry->remove(Entry::ftPages); Value v; v.append(QSharedPointer<PlainText>(new PlainText(pages))); entry->insert(Entry::ftPages, v); } /// Some entries may have no 'author' field, but a 'bookauthor' field instead if (!entry->contains(Entry::ftAuthor) && entry->contains(QStringLiteral("bookauthor"))) { Value v = entry->value(QStringLiteral("bookauthor")); entry->remove(QStringLiteral("bookauthor")); entry->insert(Entry::ftAuthor, v); } for (QMap<QString, Value>::Iterator it = entry->begin(); it != entry->end();) { if (PlainTextValue::text(it.value()).isEmpty()) it = entry->erase(it); else ++it; } } diff --git a/src/networking/onlinesearch/onlinesearchpubmed.cpp b/src/networking/onlinesearch/onlinesearchpubmed.cpp index f177cb2f..d407d043 100644 --- a/src/networking/onlinesearch/onlinesearchpubmed.cpp +++ b/src/networking/onlinesearch/onlinesearchpubmed.cpp @@ -1,238 +1,240 @@ /*************************************************************************** * 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 "onlinesearchpubmed.h" #include <QNetworkReply> #include <QDateTime> #include <QTimer> #include <QRegularExpression> #ifdef HAVE_KF5 #include <KLocalizedString> #include <KMessageBox> +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "xsltransform.h" #include "encoderxml.h" #include "fileimporterbibtex.h" #include "internalnetworkaccessmanager.h" #include "logging_networking.h" const int OnlineSearchPubMed::maxNumResults = 25; const uint OnlineSearchPubMed::queryChokeTimeout = 10; /// 10 seconds uint OnlineSearchPubMed::lastQueryEpoch = 0; class OnlineSearchPubMed::OnlineSearchPubMedPrivate { private: const QString pubMedUrlPrefix; static const QString xsltFilenameBase; public: const XSLTransform xslt; OnlineSearchPubMedPrivate(OnlineSearchPubMed *) : pubMedUrlPrefix(QStringLiteral("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/")), xslt(XSLTransform::locateXSLTfile(xsltFilenameBase)) { if (!xslt.isValid()) qCWarning(LOG_KBIBTEX_NETWORKING) << "Failed to initialize XSL transformation based on file '" << xsltFilenameBase << "'"; } QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { /// used to auto-detect PMIDs (unique identifiers for documents) in free text search static const QRegularExpression pmidRegExp(QStringLiteral("^[0-9]{6,}$")); QString url = pubMedUrlPrefix + QStringLiteral("esearch.fcgi?db=pubmed&tool=kbibtex&term="); const QStringList freeTextWords = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyFreeText]); const QStringList yearWords = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyYear]); const QStringList titleWords = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyTitle]); const QStringList authorWords = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyAuthor]); /// append search terms QStringList queryFragments; queryFragments.reserve(freeTextWords.size() + yearWords.size() + titleWords.size() + authorWords.size()); /// add words from "free text" field, but auto-detect PMIDs for (const QString &text : freeTextWords) queryFragments.append(text + (pmidRegExp.match(text).hasMatch() ? QString() : QStringLiteral("[All Fields]"))); /// add words from "year" field for (const QString &text : yearWords) queryFragments.append(text); /// add words from "title" field for (const QString &text : titleWords) queryFragments.append(text + QStringLiteral("[Title]")); /// add words from "author" field for (const QString &text : authorWords) queryFragments.append(text + QStringLiteral("[Author]")); /// Join all search terms with an AND operation url.append(queryFragments.join(QStringLiteral("+AND+"))); url = url.replace(QLatin1Char('"'), QStringLiteral("%22")); /// set number of expected results url.append(QString(QStringLiteral("&retstart=0&retmax=%1&retmode=xml")).arg(numResults)); return QUrl::fromUserInput(url); } QUrl buildFetchIdUrl(const QStringList &idList) { const QString urlText = pubMedUrlPrefix + QStringLiteral("efetch.fcgi?retmode=xml&db=pubmed&id=") + idList.join(QStringLiteral(",")); return QUrl::fromUserInput(urlText); } }; const QString OnlineSearchPubMed::OnlineSearchPubMedPrivate::xsltFilenameBase = QStringLiteral("pubmed2bibtex.xsl"); OnlineSearchPubMed::OnlineSearchPubMed(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchPubMed::OnlineSearchPubMedPrivate(this)) { /// nothing } OnlineSearchPubMed::~OnlineSearchPubMed() { delete d; } void OnlineSearchPubMed::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 2); /// enforcing limit on number of results numResults = qMin(maxNumResults, numResults); /// enforcing choke on number of searchs per time if (QDateTime::currentDateTimeUtc().toTime_t() - lastQueryEpoch < queryChokeTimeout) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Too many search queries per time; choke enforces pause of" << queryChokeTimeout << "seconds between queries"; delayedStoppedSearch(resultNoError); return; } QNetworkRequest request(d->buildQueryUrl(query, numResults)); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchPubMed::eSearchDone); refreshBusyProperty(); } QString OnlineSearchPubMed::label() const { return i18n("PubMed"); } QString OnlineSearchPubMed::favIconUrl() const { return QStringLiteral("https://www.ncbi.nlm.nih.gov/favicon.ico"); } QUrl OnlineSearchPubMed::homepage() const { return QUrl(QStringLiteral("https://www.ncbi.nlm.nih.gov/pubmed/")); } void OnlineSearchPubMed::eSearchDone() { emit progress(++curStep, numSteps); lastQueryEpoch = QDateTime::currentDateTimeUtc().toTime_t(); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { const QString result = QString::fromUtf8(reply->readAll().constData()); if (!result.contains(QStringLiteral("<Count>0</Count>"))) { /// without parsing XML text correctly, just extract all PubMed ids QStringList idList; int p1, p2 = 0; /// All IDs are within <IdList>...</IdList> if ((p1 = result.indexOf(QStringLiteral("<IdList>"))) > 0 && (p2 = result.indexOf(QStringLiteral("</IdList>"), p1)) > 0) { int p3, p4 = p1; /// Search for each <Id>...</Id> while ((p3 = result.indexOf(QStringLiteral("<Id>"), p4)) > 0 && (p4 = result.indexOf(QStringLiteral("</Id>"), p3)) > 0 && p4 < p2) { /// Extract ID and add it to list const QString id = result.mid(p3 + 4, p4 - p3 - 4); idList << id; } } if (idList.isEmpty()) { stopSearch(resultUnspecifiedError); } else { /// fetch full bibliographic details for found PubMed ids QNetworkRequest request(d->buildFetchIdUrl(idList)); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request, reply); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchPubMed::eFetchDone); } } else { /// search resulted in no hits (and PubMed told so) stopSearch(resultNoError); } } refreshBusyProperty(); } void OnlineSearchPubMed::eFetchDone() { emit progress(++curStep, numSteps); lastQueryEpoch = QDateTime::currentDateTimeUtc().toTime_t(); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters QString input = QString::fromUtf8(reply->readAll().constData()); /// use XSL transformation to get BibTeX document from XML result QString bibTeXcode = EncoderXML::instance().decode(d->xslt.transform(input)); if (bibTeXcode.isEmpty()) { qCWarning(LOG_KBIBTEX_NETWORKING) << "XSL tranformation failed for data from " << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultInvalidArguments); } else { /// remove XML header if (bibTeXcode[0] == '<') bibTeXcode = bibTeXcode.mid(bibTeXcode.indexOf(QStringLiteral(">")) + 1); FileImporterBibTeX importer(this); File *bibtexFile = importer.fromString(bibTeXcode); if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { stopSearch(resultUnspecifiedError); } } } refreshBusyProperty(); } diff --git a/src/networking/onlinesearch/onlinesearchsciencedirect.cpp b/src/networking/onlinesearch/onlinesearchsciencedirect.cpp index 16432661..a0ecdfc3 100644 --- a/src/networking/onlinesearch/onlinesearchsciencedirect.cpp +++ b/src/networking/onlinesearch/onlinesearchsciencedirect.cpp @@ -1,285 +1,287 @@ /*************************************************************************** * 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 "onlinesearchsciencedirect.h" #include <QNetworkReply> #include <QUrlQuery> #include <QCoreApplication> #include <QStandardPaths> #include <QRegularExpression> #include <QJsonDocument> #include <QJsonObject> #include <QJsonValue> #include <QJsonArray> #ifdef HAVE_KF5 #include <KLocalizedString> +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "fileimporterbibtex.h" #include "encoderxml.h" #include "xsltransform.h" #include "internalnetworkaccessmanager.h" #include "kbibtex.h" #include "logging_networking.h" class OnlineSearchScienceDirect::OnlineSearchScienceDirectPrivate { public: static const QUrl apiUrl; static const QString apiKey; OnlineSearchScienceDirectPrivate(OnlineSearchScienceDirect *) { /// nothing } int normalizeNumberOfResults(int requestedNumResults) const { if (requestedNumResults <= 10) return 10; else if (requestedNumResults <= 25) return 25; else if (requestedNumResults <= 50) return 50; else return 100; } QByteArray buildJsonQuery(const QMap<QString, QString> &query, int numResults) const { QString jsonQueryText = QStringLiteral("{\n"); /// Free text const QStringList freeTextFragments = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyFreeText]); if (!freeTextFragments.isEmpty()) jsonQueryText.append(QStringLiteral(" \"qs\": \"\\\"") + freeTextFragments.join(QStringLiteral("\\\" AND \\\"")) + QStringLiteral("\\\"\"")); /// Title const QStringList title = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyTitle]); if (!title.isEmpty()) { if (jsonQueryText != QStringLiteral("{\n")) jsonQueryText.append(QStringLiteral(",\n")); jsonQueryText.append(QStringLiteral(" \"title\": \"\\\"") + title.join(QStringLiteral("\\\" AND \\\"")) + QStringLiteral("\\\"\"")); } /// Authors const QStringList authors = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyAuthor]); if (!authors.isEmpty()) { if (jsonQueryText != QStringLiteral("{\n")) jsonQueryText.append(QStringLiteral(",\n")); jsonQueryText.append(QStringLiteral(" \"authors\": \"\\\"") + authors.join(QStringLiteral("\\\" AND \\\"")) + QStringLiteral("\\\"\"")); } /// Year static const QRegularExpression yearRangeRegExp(QStringLiteral("(18|19|20)[0-9]{2}(-+(18|19|20)[0-9]{2})?")); const QRegularExpressionMatch yearRangeRegExpMatch = yearRangeRegExp.match(query[queryKeyYear]); if (yearRangeRegExpMatch.hasMatch()) { if (jsonQueryText != QStringLiteral("{\n")) jsonQueryText.append(QStringLiteral(",\n")); jsonQueryText.append(QStringLiteral(" \"date\": \"") + yearRangeRegExpMatch.captured() + QStringLiteral("\"")); } /// Request numResults many entries if (jsonQueryText != QStringLiteral("{\n")) jsonQueryText.append(QStringLiteral(",\n")); jsonQueryText.append(QStringLiteral(" \"display\": {\n \"show\": ") + QString::number(normalizeNumberOfResults(numResults)) + QStringLiteral("\n }")); jsonQueryText.append(QStringLiteral("\n}\n")); return jsonQueryText.toUtf8(); } Entry *entryFromJsonObject(const QJsonObject &object) const { const QString title = object.value(QStringLiteral("title")).toString(); const QString pii = object.value(QStringLiteral("pii")).toString(); /// Basic sanity check if (title.isEmpty() || pii.isEmpty()) return nullptr; Entry *entry = new Entry(Entry::etArticle, QStringLiteral("ScienceDirect:") + pii); entry->insert(Entry::ftTitle, Value() << QSharedPointer<PlainText>(new PlainText(title))); entry->insert(QStringLiteral("pii"), Value() << QSharedPointer<VerbatimText>(new VerbatimText(pii))); const QString doi = object.value(QStringLiteral("doi")).toString(); const QRegularExpressionMatch doiRegExpMatch = KBibTeX::doiRegExp.match(doi); if (doiRegExpMatch.hasMatch()) entry->insert(Entry::ftDOI, Value() << QSharedPointer<VerbatimText>(new VerbatimText(doiRegExpMatch.captured()))); const QString url = object.value(QStringLiteral("uri")).toString().remove(QStringLiteral("?dgcid=api_sd_search-api-endpoint")); if (!url.isEmpty()) entry->insert(Entry::ftUrl, Value() << QSharedPointer<VerbatimText>(new VerbatimText(url))); const QJsonObject pages = object.value(QStringLiteral("pages")).toObject(); bool firstPageOk = false, lastPageOk = false;; const int firstPage = pages.value(QStringLiteral("first")).toString().toInt(&firstPageOk); const int lastPage = firstPageOk ? pages.value(QStringLiteral("last")).toString().toInt(&lastPageOk) : -1; if (firstPageOk && lastPageOk && firstPage <= lastPage) entry->insert(Entry::ftPages, Value() << QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1%2%3")).arg(firstPage).arg(QChar(0x2013)).arg(lastPage)))); static const QRegularExpression dateRegExp(QStringLiteral("^((17|18|19|20)[0-9]{2})(-(0[1-9]|1[012]))?")); const QString publicationDate = object.value(QStringLiteral("publicationDate")).toString(); const QRegularExpressionMatch dateRegExpMatch = dateRegExp.match(publicationDate); if (dateRegExpMatch.hasMatch()) { entry->insert(Entry::ftYear, Value() << QSharedPointer<PlainText>(new PlainText(dateRegExpMatch.captured(1)))); bool monthOk = false; const int month = dateRegExpMatch.captured(4).toInt(&monthOk); if (monthOk && month >= 1 && month <= 12) entry->insert(Entry::ftMonth, Value() << QSharedPointer<MacroKey>(new MacroKey(KBibTeX::MonthsTriple[month - 1]))); } const QJsonArray authorArray = object.value(QStringLiteral("authors")).toArray(); QMap<int, QString> authorMap; int maxOrder = -1; for (const QJsonValue &author : authorArray) { const QString name = author.toObject().value(QStringLiteral("name")).toString(); const int order = author.toObject().value(QStringLiteral("order")).toInt(-1); if (order >= 0 && !name.isEmpty()) { if (order > maxOrder) maxOrder = order; authorMap.insert(order, name); } } Value authors; for (int i = 0; i <= maxOrder; ++i) { QStringList components = authorMap.value(i, QString()).split(QStringLiteral(" ")); if (components.isEmpty()) continue; const QString lastName = components.last(); components.pop_back(); const QString firstName = components.join(QStringLiteral(" ")); authors.append(QSharedPointer<Person>(new Person(firstName, lastName))); } if (!authors.isEmpty()) entry->insert(Entry::ftAuthor, authors); const QString sourceTitle = object.value(QStringLiteral("sourceTitle")).toString(); if (!sourceTitle.isEmpty()) entry->insert(Entry::ftJournal, Value() << QSharedPointer<PlainText>(new PlainText(sourceTitle))); const QString volumeIssue = object.value(QStringLiteral("volumeIssue")).toString(); static const QRegularExpression volumeRegExp(QStringLiteral("olume\\s+([1-9][0-9]*)")); const QRegularExpressionMatch volumeRegExpMatch = volumeRegExp.match(volumeIssue); if (volumeRegExpMatch.hasMatch()) entry->insert(Entry::ftVolume, Value() << QSharedPointer<PlainText>(new PlainText(volumeRegExpMatch.captured(1)))); static const QRegularExpression issueRegExp(QStringLiteral("ssue\\s+([1-9][0-9]*)")); const QRegularExpressionMatch issueRegExpMatch = issueRegExp.match(volumeIssue); if (issueRegExpMatch.hasMatch()) entry->insert(Entry::ftNumber, Value() << QSharedPointer<PlainText>(new PlainText(issueRegExpMatch.captured(1)))); return entry; } }; const QUrl OnlineSearchScienceDirect::OnlineSearchScienceDirectPrivate::apiUrl(QStringLiteral("https://api.elsevier.com/content/search/sciencedirect")); const QString OnlineSearchScienceDirect::OnlineSearchScienceDirectPrivate::apiKey(InternalNetworkAccessManager::reverseObfuscate("\x43\x74\x9a\xa9\x6f\x5d\xa9\x9f\xeb\xda\xb9\xd8\x1b\x2b\x80\xe1\x3f\x5e\x29\x1c\xab\xc8\x54\x63\x58\x61\x13\x71\xca\xa9\xf1\xc4\xe4\xd3\xc9\xaa\x14\x70\xef\xdc\xb\x69\xff\xc6\xd5\xb6\x4a\x7d\x10\x27\xbb\xde\x92\xaa\xb0\xd6\xb9\x80\xd\x34\x48\x7e\x9d\xff")); OnlineSearchScienceDirect::OnlineSearchScienceDirect(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchScienceDirectPrivate(this)) { /// nothing } OnlineSearchScienceDirect::~OnlineSearchScienceDirect() { delete d; } void OnlineSearchScienceDirect::startSearch(const QMap<QString, QString> &query, int numResults) { emit progress(curStep = 0, numSteps = 1); QUrl u(OnlineSearchScienceDirectPrivate::apiUrl); QNetworkRequest request(u); request.setRawHeader(QByteArray("X-ELS-APIKey"), d->apiKey.toLatin1()); request.setRawHeader(QByteArray("Accept"), QByteArray("application/json")); request.setRawHeader(QByteArray("Content-Type"), QByteArray("application/json")); const QByteArray jsonData = d->buildJsonQuery(query, numResults); QNetworkReply *reply = InternalNetworkAccessManager::instance().put(request, jsonData); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchScienceDirect::doneFetchingJSON); refreshBusyProperty(); } QString OnlineSearchScienceDirect::label() const { return i18n("ScienceDirect"); } QString OnlineSearchScienceDirect::favIconUrl() const { return QStringLiteral("https://sdfestaticassets-us-east-1.sciencedirectassets.com/shared-assets/11/images/favSD.ico"); } QUrl OnlineSearchScienceDirect::homepage() const { return QUrl(QStringLiteral("https://www.sciencedirect.com/")); } void OnlineSearchScienceDirect::doneFetchingJSON() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { QJsonParseError parseError; const QJsonDocument document = QJsonDocument::fromJson(reply->readAll(), &parseError); if (parseError.error == QJsonParseError::NoError) { if (document.isObject()) { const int resultsFound = document.object().value(QStringLiteral("resultsFound")).toInt(-1); if (resultsFound > 0) { const QJsonValue resultArrayValue = document.object().value(QStringLiteral("results")); if (resultArrayValue.isArray()) { const QJsonArray resultArray = resultArrayValue.toArray(); bool encounteredUnexpectedData = false; for (const QJsonValue &resultValue : resultArray) { if (resultValue.isObject()) { Entry *entry = d->entryFromJsonObject(resultValue.toObject()); if (entry != nullptr) publishEntry(QSharedPointer<Entry>(entry)); else { qCWarning(LOG_KBIBTEX_NETWORKING) << "Problem with JSON data from ScienceDirect: Data could not be interpreted as a bibliographic entry"; encounteredUnexpectedData = true; break; } } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "Problem with JSON data from ScienceDirect: No object found in 'results' array where expected"; encounteredUnexpectedData = true; break; } } if (encounteredUnexpectedData) stopSearch(resultUnspecifiedError); else stopSearch(resultNoError); } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "Problem with JSON data from ScienceDirect: No 'results' array found"; stopSearch(resultUnspecifiedError); } } else if (resultsFound == 0) { qCDebug(LOG_KBIBTEX_NETWORKING) << "No results found by ScienceDirect"; stopSearch(resultNoError); } else { /// resultsFound < 0 --> no 'resultsFound' field in JSON data qCWarning(LOG_KBIBTEX_NETWORKING) << "Problem with JSON data from ScienceDirect: No 'resultsFound' field found"; stopSearch(resultUnspecifiedError); } } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "Problem with JSON data from ScienceDirect: Document is not an object"; stopSearch(resultUnspecifiedError); } } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "Problem with JSON data from ScienceDirect: " << parseError.errorString(); stopSearch(resultUnspecifiedError); } } refreshBusyProperty(); } diff --git a/src/networking/onlinesearch/onlinesearchspringerlink.cpp b/src/networking/onlinesearch/onlinesearchspringerlink.cpp index 8ea43b36..6ba97160 100644 --- a/src/networking/onlinesearch/onlinesearchspringerlink.cpp +++ b/src/networking/onlinesearch/onlinesearchspringerlink.cpp @@ -1,352 +1,352 @@ /*************************************************************************** * 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 "onlinesearchspringerlink.h" #ifdef HAVE_QTWIDGETS #include <QFormLayout> #include <QSpinBox> #include <QLabel> #endif // HAVE_QTWIDGETS #include <QRegularExpression> #include <QNetworkRequest> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QUrlQuery> #ifdef HAVE_KF5 #include <KLocalizedString> -#endif // HAVE_KF5 -#ifdef HAVE_QTWIDGETS #include <KLineEdit> #include <KConfigGroup> -#endif // HAVE_QTWIDGETS +#else // HAVE_KF5 +#define i18n(text) QObject::tr(text) +#endif // HAVE_KF5 #include "internalnetworkaccessmanager.h" #include "encoderlatex.h" #include "encoderxml.h" #include "fileimporterbibtex.h" #include "xsltransform.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class OnlineSearchSpringerLink::OnlineSearchQueryFormSpringerLink : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); lineEditFreeText->setText(configGroup.readEntry(QStringLiteral("free"), QString())); lineEditTitle->setText(configGroup.readEntry(QStringLiteral("title"), QString())); lineEditBookTitle->setText(configGroup.readEntry(QStringLiteral("bookTitle"), QString())); lineEditAuthorEditor->setText(configGroup.readEntry(QStringLiteral("authorEditor"), QString())); lineEditYear->setText(configGroup.readEntry(QStringLiteral("year"), QString())); numResultsField->setValue(configGroup.readEntry(QStringLiteral("numResults"), 10)); } public: KLineEdit *lineEditFreeText, *lineEditTitle, *lineEditBookTitle, *lineEditAuthorEditor, *lineEditYear; QSpinBox *numResultsField; OnlineSearchQueryFormSpringerLink(QWidget *parent) : OnlineSearchQueryFormAbstract(parent), configGroupName(QStringLiteral("Search Engine SpringerLink")) { QFormLayout *layout = new QFormLayout(this); layout->setMargin(0); lineEditFreeText = new KLineEdit(this); lineEditFreeText->setClearButtonEnabled(true); QLabel *label = new QLabel(i18n("Free Text:"), this); label->setBuddy(lineEditFreeText); layout->addRow(label, lineEditFreeText); connect(lineEditFreeText, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); lineEditTitle = new KLineEdit(this); lineEditTitle->setClearButtonEnabled(true); label = new QLabel(i18n("Title:"), this); label->setBuddy(lineEditTitle); layout->addRow(label, lineEditTitle); connect(lineEditTitle, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); lineEditBookTitle = new KLineEdit(this); lineEditBookTitle->setClearButtonEnabled(true); label = new QLabel(i18n("Book/Journal title:"), this); label->setBuddy(lineEditBookTitle); layout->addRow(label, lineEditBookTitle); connect(lineEditBookTitle, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); lineEditAuthorEditor = new KLineEdit(this); lineEditAuthorEditor->setClearButtonEnabled(true); label = new QLabel(i18n("Author or Editor:"), this); label->setBuddy(lineEditAuthorEditor); layout->addRow(label, lineEditAuthorEditor); connect(lineEditAuthorEditor, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); lineEditYear = new KLineEdit(this); lineEditYear->setClearButtonEnabled(true); label = new QLabel(i18n("Year:"), this); label->setBuddy(lineEditYear); layout->addRow(label, lineEditYear); connect(lineEditYear, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); numResultsField = new QSpinBox(this); label = new QLabel(i18n("Number of Results:"), this); label->setBuddy(numResultsField); layout->addRow(label, numResultsField); numResultsField->setMinimum(3); numResultsField->setMaximum(100); lineEditFreeText->setFocus(Qt::TabFocusReason); loadState(); } bool readyToStart() const override { return !(lineEditFreeText->text().isEmpty() && lineEditTitle->text().isEmpty() && lineEditBookTitle->text().isEmpty() && lineEditAuthorEditor->text().isEmpty()); } void copyFromEntry(const Entry &entry) override { lineEditTitle->setText(PlainTextValue::text(entry[Entry::ftTitle])); QString bookTitle = PlainTextValue::text(entry[Entry::ftBookTitle]); if (bookTitle.isEmpty()) bookTitle = PlainTextValue::text(entry[Entry::ftJournal]); lineEditBookTitle->setText(bookTitle); lineEditAuthorEditor->setText(authorLastNames(entry).join(QStringLiteral(" "))); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("free"), lineEditFreeText->text()); configGroup.writeEntry(QStringLiteral("title"), lineEditTitle->text()); configGroup.writeEntry(QStringLiteral("bookTitle"), lineEditBookTitle->text()); configGroup.writeEntry(QStringLiteral("authorEditor"), lineEditAuthorEditor->text()); configGroup.writeEntry(QStringLiteral("year"), lineEditYear->text()); configGroup.writeEntry(QStringLiteral("numResults"), numResultsField->value()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate { private: static const QString xsltFilenameBase; public: static const QString springerMetadataKey; const XSLTransform xslt; #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormSpringerLink *form; #endif // HAVE_QTWIDGETS OnlineSearchSpringerLinkPrivate(OnlineSearchSpringerLink *) : xslt(XSLTransform::locateXSLTfile(xsltFilenameBase)) #ifdef HAVE_QTWIDGETS , form(nullptr) #endif // HAVE_QTWIDGETS { if (!xslt.isValid()) qCWarning(LOG_KBIBTEX_NETWORKING) << "Failed to initialize XSL transformation based on file '" << xsltFilenameBase << "'"; } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { if (form == nullptr) return QUrl(); QUrl queryUrl = QUrl(QString(QStringLiteral("http://api.springer.com/metadata/pam/?api_key=")).append(springerMetadataKey)); QString queryString = form->lineEditFreeText->text(); const QStringList titleChunks = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditTitle->text()); for (const QString &titleChunk : titleChunks) { queryString += QString(QStringLiteral(" title:%1")).arg(EncoderLaTeX::instance().convertToPlainAscii(titleChunk)); } const QStringList bookTitleChunks = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditBookTitle->text()); for (const QString &titleChunk : bookTitleChunks) { queryString += QString(QStringLiteral(" ( journal:%1 OR book:%1 )")).arg(EncoderLaTeX::instance().convertToPlainAscii(titleChunk)); } const QStringList authors = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditAuthorEditor->text()); for (const QString &author : authors) { queryString += QString(QStringLiteral(" name:%1")).arg(EncoderLaTeX::instance().convertToPlainAscii(author)); } const QString year = form->lineEditYear->text(); if (!year.isEmpty()) queryString += QString(QStringLiteral(" year:%1")).arg(year); queryString = queryString.simplified(); QUrlQuery query(queryUrl); query.addQueryItem(QStringLiteral("q"), queryString); queryUrl.setQuery(query); return queryUrl; } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query) { QUrl queryUrl = QUrl(QString(QStringLiteral("http://api.springer.com/metadata/pam/?api_key=")).append(springerMetadataKey)); QString queryString = query[queryKeyFreeText]; const QStringList titleChunks = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyTitle]); for (const QString &titleChunk : titleChunks) { queryString += QString(QStringLiteral(" title:%1")).arg(EncoderLaTeX::instance().convertToPlainAscii(titleChunk)); } const QStringList authors = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyAuthor]); for (const QString &author : authors) { queryString += QString(QStringLiteral(" name:%1")).arg(EncoderLaTeX::instance().convertToPlainAscii(author)); } QString year = query[queryKeyYear]; if (!year.isEmpty()) { static const QRegularExpression yearRegExp("\\b(18|19|20)[0-9]{2}\\b"); const QRegularExpressionMatch yearRegExpMatch = yearRegExp.match(year); if (yearRegExpMatch.hasMatch()) { year = yearRegExpMatch.captured(0); queryString += QString(QStringLiteral(" year:%1")).arg(year); } } queryString = queryString.simplified(); QUrlQuery q(queryUrl); q.addQueryItem(QStringLiteral("q"), queryString); queryUrl.setQuery(q); return queryUrl; } }; const QString OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate::xsltFilenameBase = QStringLiteral("pam2bibtex.xsl"); const QString OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate::springerMetadataKey(InternalNetworkAccessManager::reverseObfuscate("\xce\xb8\x4d\x2c\x8d\xba\xa9\xc4\x61\x9\x58\x6c\xbb\xde\x86\xb5\xb1\xc6\x15\x71\x76\x45\xd\x79\x12\x65\x95\xe1\x5d\x2f\x1d\x24\x10\x72\x2a\x5e\x69\x4\xdc\xba\xab\xc3\x28\x58\x8a\xfa\x5e\x69")); OnlineSearchSpringerLink::OnlineSearchSpringerLink(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate(this)) { /// nothing } OnlineSearchSpringerLink::~OnlineSearchSpringerLink() { delete d; } #ifdef HAVE_QTWIDGETS void OnlineSearchSpringerLink::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QUrl springerLinkSearchUrl = d->buildQueryUrl(); QNetworkRequest request(springerLinkSearchUrl); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchSpringerLink::doneFetchingPAM); if (d->form != nullptr) d->form->saveState(); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS void OnlineSearchSpringerLink::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; QUrl springerLinkSearchUrl = d->buildQueryUrl(query); QUrlQuery q(springerLinkSearchUrl); q.addQueryItem(QStringLiteral("p"), QString::number(numResults)); springerLinkSearchUrl.setQuery(q); emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(springerLinkSearchUrl); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchSpringerLink::doneFetchingPAM); refreshBusyProperty(); } QString OnlineSearchSpringerLink::label() const { return i18n("SpringerLink"); } QString OnlineSearchSpringerLink::favIconUrl() const { return QStringLiteral("http://link.springer.com/static/0.6623/sites/link/images/favicon.ico"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchSpringerLink::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchQueryFormSpringerLink(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchSpringerLink::homepage() const { return QUrl(QStringLiteral("http://www.springerlink.com/")); } void OnlineSearchSpringerLink::doneFetchingPAM() { QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters const QString xmlSource = QString::fromUtf8(reply->readAll().constData()); const QString bibTeXcode = EncoderXML::instance().decode(d->xslt.transform(xmlSource).remove(QStringLiteral("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"))); if (bibTeXcode.isEmpty()) { qCWarning(LOG_KBIBTEX_NETWORKING) << "XSL tranformation failed for data from " << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultInvalidArguments); } else { FileImporterBibTeX importer(this); const File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const QSharedPointer<Element> &element : *bibtexFile) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } } refreshBusyProperty(); } #include "onlinesearchspringerlink.moc"