Allow LyX to find pdfs and urls of citation references and follow them from context menu.

Currently tested:
- url & doi fields for bibtex.
- all documented eprinttypes of biblatex
- absolute paths of first entry of 'file' field for jabref and kbibtex
- external script searching for author + year pdf

Additional polishing will follow.

https://www.mail-archive.com/lyx-devel@lists.lyx.org/msg212505.html
This commit is contained in:
Pavel Sanda 2020-08-20 08:33:40 +02:00
parent 4bb00e99fb
commit e648202e7e
10 changed files with 169 additions and 0 deletions

View File

@ -128,6 +128,7 @@ Menuset
CiteStyles
Separator
Item "Settings...|S" "inset-settings"
Item "Open Citation Content|O" "inset-edit"
End

View File

@ -651,6 +651,75 @@ docstring const BibTeXInfo::getYear() const
}
void BibTeXInfo::getLocators(docstring & doi, docstring & url, docstring & file) const
{
if (is_bibtex_) {
// get "doi" entry from citation record
doi = operator[]("doi");
if (!doi.empty() && !prefixIs(doi,from_ascii("http")))
doi = "https://doi.org/" + doi;
// get "url" entry from citation record
url = operator[]("url");
// get "file" entry from citation record
file = operator[]("file");
// Jabref case, field has a format:
// Description:Location:Filetype;Description:Location:Filetype...
// We will grab only first pdf
if (!file.empty()) {
docstring ret, filedest, tmp;
ret = split(file, tmp, ':');
tmp = split(ret, filedest, ':');
//TODO howto deal with relative directories?
FileName f(to_utf8(filedest));
if (f.exists())
file = "file:///" + filedest;
}
// kbibtex case, format:
// file1.pdf;file2.pdf
// We will grab only first pdf
docstring kfile;
if (file.empty())
kfile = operator[]("localfile");
if (!kfile.empty()) {
docstring filedest, tmp;
tmp = split(kfile, filedest, ';');
//TODO howto deal with relative directories?
FileName f(to_utf8(filedest));
if (f.exists())
file = "file:///" + filedest;
}
if (!url.empty())
return;
// try biblatex specific fields, see its manual
// 3.13.7 "Electronic Publishing Informationl"
docstring eprinttype = operator[]("eprinttype");
docstring eprint = operator[]("eprint");
if (eprint.empty())
return;
if (eprinttype == "arxiv")
url = "https://arxiv.org/abs/" + eprint;
if (eprinttype == "jstor")
url = "https://www.jstor.org/stable/" + eprint;
if (eprinttype == "pubmed")
url = "http://www.ncbi.nlm.nih.gov/pubmed/" + eprint;
if (eprinttype == "hdl")
url = "https://hdl.handle.net/" + eprint;
if (eprinttype == "googlebooks")
url = "http://books.google.com/books?id=" + eprint;
return;
}
// Here can be handled the bibliography environment. All one could do
// here is let LyX scan the entry for URL or HRef insets.
}
namespace {
docstring parseOptions(docstring const & format, string & optkey,
@ -1269,6 +1338,15 @@ docstring const BiblioInfo::getCiteNumber(docstring const & key) const
return data.citeNumber();
}
void BiblioInfo::getLocators(docstring const & key, docstring & doi, docstring & url, docstring & file) const
{
BiblioInfo::const_iterator it = find(key);
if (it == end())
return;
BibTeXInfo const & data = it->second;
data.getLocators(doi,url,file);
}
docstring const BiblioInfo::getYear(docstring const & key, bool use_modifier) const
{

View File

@ -70,6 +70,8 @@ public:
bool const allnames = false, bool const beginning = true) const;
///
docstring const getYear() const;
///
void getLocators(docstring & doi, docstring & url, docstring & file) const;
/// \return formatted BibTeX data suitable for framing.
/// \param vector of pointers to crossref/xdata information
docstring const & getInfo(BibTeXInfoList const & xrefs,
@ -213,6 +215,9 @@ public:
/// language.
docstring const getYear(docstring const & key, Buffer const & buf,
bool use_modifier = false) const;
/// get either local pdf or web location of the citation referenced by key.
/// DOI/file are prefixed so they form proper URL for generic qt handler
void getLocators(docstring const & key, docstring & doi, docstring & url, docstring & file) const;
///
docstring const getCiteNumber(docstring const & key) const;
/// \return formatted BibTeX data associated with a given key.

View File

@ -490,6 +490,7 @@ enum FuncCode
LFUN_MASTER_BUFFER_FORALL, // spitz 20191231
LFUN_IF_RELATIVES, // spitz 20200102
LFUN_WINDOW_RAISE, // forenr, 20202104
LFUN_CITATION_OPEN, // sanda, 20200815
LFUN_LASTACTION // end of the table
};

View File

@ -1239,6 +1239,18 @@ void LyXAction::init()
* \endvar
*/
{ LFUN_CITATION_INSERT, "citation-insert", Noop, Edit },
/*!
* \var lyx::FuncCode lyx::LFUN_CITATION_OPEN
* \li Action: Opens the corresponding pdf/url for a given citation inset.
* \li Syntax: citation-open [EXTERNAL] TARGET
* \li Params: <TARGET>: URL (https:,file:) of the document. \n
<EXTERNAL>: Use external executable script for finding target \n
and launching viewer. In this case TARGET consists of author and year \n
and will be passed as an input argument to the script.
* \li Origin: Sanda, 16 Aug 2020
* \endvar
*/
{ LFUN_CITATION_OPEN, "citation-open", ReadOnly | NoUpdate | Argument, Edit },
/*!
* \var lyx::FuncCode lyx::LFUN_CLIPBOARD_PASTE

View File

@ -2371,6 +2371,10 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
flag.setOnOff(lyxrc.spellcheck_continuously);
break;
case LFUN_CITATION_OPEN:
enable = true;
break;
default:
return false;
}
@ -4620,6 +4624,10 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
dr.screenUpdate(Update::Force);
break;
case LFUN_CITATION_OPEN: {
frontend::showTarget(argument);
}
default:
// The LFUN must be for one of BufferView, Buffer or Cursor;
// let's try that:

View File

@ -21,7 +21,10 @@
#include "BufferParams.h"
#include "FloatList.h"
#include "FuncRequest.h"
#include "Language.h"
#include "LyX.h"
#include "LyXAction.h"
#include "TextClass.h"
#include "support/convert.h"
@ -293,6 +296,18 @@ void showDirectory(FileName const & directory)
if (!QDesktopServices::openUrl(qurl))
LYXERR0("Unable to open QUrl even though dir exists!");
}
void showTarget(string const & target){
LYXERR(Debug::INSETS, "Showtarget:" << target << "\n");
if (prefixIs(target,"EXTERNAL ")) {
string tmp,tar;
tar = split(target, tmp, ' ');
FuncRequest cmd = FuncRequest(LFUN_VC_COMMAND,"U . \"lyxpaperview " + tar + "\"");
lyx::dispatch(cmd);
return;
}
QDesktopServices::openUrl(QUrl(toqstr(target), QUrl::TolerantMode));
}
} // namespace frontend
QString const qt_(char const * str, const char *)

View File

@ -101,6 +101,9 @@ void setSectionResizeMode(QHeaderView * view,
QHeaderView::ResizeMode mode);
/// Shows a directory in OSs file browser
void showDirectory(support::FileName const & directory);
/// handle request for showing citation content - shows pdf or
/// web page in target; external script can be used for pdf view
void showTarget(std::string const & target);
} // namespace frontend

View File

@ -23,6 +23,7 @@
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "LaTeXFeatures.h"
#include "LyX.h"
#include "output_xhtml.h"
#include "output_docbook.h"
#include "ParIterator.h"
@ -133,6 +134,9 @@ CitationStyle InsetCitation::getCitationStyle(BufferParams const & bp, string co
void InsetCitation::doDispatch(Cursor & cur, FuncRequest & cmd)
{
switch (cmd.action()) {
case LFUN_INSET_EDIT:
openCitation();
break;
case LFUN_INSET_MODIFY: {
buffer().removeBiblioTempFiles();
cache.recalculate = true;
@ -165,6 +169,44 @@ void InsetCitation::doDispatch(Cursor & cur, FuncRequest & cmd)
}
void InsetCitation::openCitation(){
Buffer const & buf = *buffer_;
// Only after the buffer is loaded from file...
if (!buf.isFullyLoaded())
return;
BiblioInfo const & bi = buf.masterBibInfo();
if (bi.empty())
return;
docstring const & key = getParam("key");
if (key.empty())
return;
vector<docstring> keys = getVectorFromString(key);
docstring year, author, doi, url, file;
for (docstring const & kvar : keys) {
year = bi.getYear(kvar, buffer(), false);
author = bi.getAuthorOrEditorList(kvar, buffer());
bi.getLocators(kvar, doi, url, file);
LYXERR(Debug::INSETS, "Locators: doi:" << doi << " url:"
<< url << " file:" << file << " author:" << author << " year:" << year);
docstring locator;
if (!file.empty()) {
locator = file;
} else if (!doi.empty()) {
locator = doi;
} else if (!url.empty()) {
locator = url;
} else {
locator = "EXTERNAL " + year + " " + author;
}
FuncRequest cmd = FuncRequest(LFUN_CITATION_OPEN, locator);
lyx::dispatch(cmd);
}
}
bool InsetCitation::getStatus(Cursor & cur, FuncRequest const & cmd,
FuncStatus & status) const
{
@ -203,6 +245,8 @@ bool InsetCitation::getStatus(Cursor & cur, FuncRequest const & cmd,
}
}
return true;
case LFUN_INSET_EDIT:
return true;
default:
return InsetCommand::getStatus(cur, cmd, status);
}

View File

@ -94,6 +94,8 @@ public:
QualifiedList getQualifiedLists(docstring const & p) const;
///
static bool last_literal;
///
void openCitation();
private:
/// tries to make a pretty label and makes a basic one if not