Allow for multiple use of same key in qualified citation lists

File format change

Fixes: #11618, #11632
This commit is contained in:
Juergen Spitzmueller 2019-08-07 13:00:29 +02:00
parent af7f2e9f58
commit 1386a3d8fd
11 changed files with 281 additions and 59 deletions

View File

@ -7,6 +7,9 @@ changes happened in particular if possible. A good example would be
-----------------------
2019-08-07 Jürgen Spitzmüller <spitz@lyx.org>
* Format incremented to 586: Allow for duplicate keys in qualified citation lists
2019-08-06 Jürgen Spitzmüller <spitz@lyx.org>
* Format incremented to 585:
- Add more page sizes to KOMA and memoir.

View File

@ -3215,6 +3215,148 @@ def revert_komafontsizes(document):
return
document.header[i] = document.header[i] + "," + fsize
def revert_dupqualicites(document):
" Revert qualified citation list commands with duplicate keys to ERT "
# LyX 2.3 only supports qualified citation lists with unique keys. Thus,
# we need to revert those with multiple uses of the same key.
# Get cite engine
engine = "basic"
i = find_token(document.header, "\\cite_engine", 0)
if i == -1:
document.warning("Malformed document! Missing \\cite_engine")
else:
engine = get_value(document.header, "\\cite_engine", i)
if not engine in ["biblatex", "biblatex-natbib"]:
return
# Citation insets that support qualified lists, with their LaTeX code
ql_citations = {
"cite" : "cites",
"Cite" : "Cites",
"citet" : "textcites",
"Citet" : "Textcites",
"citep" : "parencites",
"Citep" : "Parencites",
"Footcite" : "Smartcites",
"footcite" : "smartcites",
"Autocite" : "Autocites",
"autocite" : "autocites",
}
i = 0
while (True):
i = find_token(document.body, "\\begin_inset CommandInset citation", i)
if i == -1:
break
j = find_end_of_inset(document.body, i)
if j == -1:
document.warning("Can't find end of citation inset at line %d!!" %(i))
i += 1
continue
k = find_token(document.body, "LatexCommand", i, j)
if k == -1:
document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
i = j + 1
continue
cmd = get_value(document.body, "LatexCommand", k)
if not cmd in list(ql_citations.keys()):
i = j + 1
continue
pres = find_token(document.body, "pretextlist", i, j)
posts = find_token(document.body, "posttextlist", i, j)
if pres == -1 and posts == -1:
# nothing to do.
i = j + 1
continue
key = get_quoted_value(document.body, "key", i, j)
if not key:
document.warning("Citation inset at line %d does not have a key!" %(i))
i = j + 1
continue
keys = key.split(",")
ukeys = list(set(keys))
if len(keys) == len(ukeys):
# no duplicates.
i = j + 1
continue
pretexts = get_quoted_value(document.body, "pretextlist", pres)
posttexts = get_quoted_value(document.body, "posttextlist", posts)
pre = get_quoted_value(document.body, "before", i, j)
post = get_quoted_value(document.body, "after", i, j)
prelist = pretexts.split("\t")
premap = dict()
for pp in prelist:
ppp = pp.split(" ", 1)
val = ""
if len(ppp) > 1:
val = ppp[1]
else:
val = ""
if ppp[0] in premap:
premap[ppp[0]] = premap[ppp[0]] + "\t" + val
else:
premap[ppp[0]] = val
postlist = posttexts.split("\t")
postmap = dict()
num = 1
for pp in postlist:
ppp = pp.split(" ", 1)
val = ""
if len(ppp) > 1:
val = ppp[1]
else:
val = ""
if ppp[0] in postmap:
postmap[ppp[0]] = postmap[ppp[0]] + "\t" + val
else:
postmap[ppp[0]] = val
# Replace known new commands with ERT
if "(" in pre or ")" in pre:
pre = "{" + pre + "}"
if "(" in post or ")" in post:
post = "{" + post + "}"
res = "\\" + ql_citations[cmd]
if pre:
res += "(" + pre + ")"
if post:
res += "(" + post + ")"
elif pre:
res += "()"
for kk in keys:
if premap.get(kk, "") != "":
akeys = premap[kk].split("\t", 1)
akey = akeys[0]
if akey != "":
res += "[" + akey + "]"
if len(akeys) > 1:
premap[kk] = "\t".join(akeys[1:])
else:
premap[kk] = ""
if postmap.get(kk, "") != "":
akeys = postmap[kk].split("\t", 1)
akey = akeys[0]
if akey != "":
res += "[" + akey + "]"
if len(akeys) > 1:
postmap[kk] = "\t".join(akeys[1:])
else:
postmap[kk] = ""
elif premap.get(kk, "") != "":
res += "[]"
res += "{" + kk + "}"
document.body[i:j+1] = put_cmd_in_ert([res])
##
@ -3263,10 +3405,12 @@ convert = [
[582, [convert_AdobeFonts,convert_latexFonts,convert_notoFonts,convert_CantarellFont,convert_FiraFont]],# old font re-converterted due to extra options
[583, [convert_ChivoFont,convert_Semibolds,convert_NotoRegulars,convert_CrimsonProFont]],
[584, []],
[585, [convert_pagesizes]]
[585, [convert_pagesizes]],
[586, []]
]
revert = [[584, [revert_pagesizes,revert_komafontsizes]],
revert = [[585, [revert_dupqualicites]],
[584, [revert_pagesizes,revert_komafontsizes]],
[583, [revert_vcsinfo_rev_abbrev]],
[582, [revert_ChivoFont,revert_CrimsonProFont]],
[581, [revert_CantarellFont,revert_FiraFont]],

View File

@ -488,7 +488,7 @@ docstring processRichtext(docstring const & str, bool richtext)
//////////////////////////////////////////////////////////////////////
BibTeXInfo::BibTeXInfo(docstring const & key, docstring const & type)
: is_bibtex_(true), bib_key_(key), entry_type_(type), info_(),
: is_bibtex_(true), bib_key_(key), num_bib_key_(0), entry_type_(type), info_(),
modifier_(0)
{}
@ -1129,11 +1129,33 @@ docstring BibTeXInfo::getValueForKey(string const & oldkey, Buffer const & buf,
ret = ci.textBefore;
else if (key == "textafter")
ret = ci.textAfter;
else if (key == "curpretext")
ret = ci.getPretexts()[bib_key_];
else if (key == "curposttext")
ret = ci.getPosttexts()[bib_key_];
else if (key == "year")
else if (key == "curpretext") {
vector<pair<docstring, docstring>> pres = ci.getPretexts();
vector<pair<docstring, docstring>>::iterator it = pres.begin();
int numkey = 1;
for (; it != pres.end() ; ++it) {
if ((*it).first == bib_key_ && numkey == num_bib_key_) {
ret = (*it).second;
pres.erase(it);
break;
}
if ((*it).first == bib_key_)
++numkey;
}
} else if (key == "curposttext") {
vector<pair<docstring, docstring>> posts = ci.getPosttexts();
vector<pair<docstring, docstring>>::iterator it = posts.begin();
int numkey = 1;
for (; it != posts.end() ; ++it) {
if ((*it).first == bib_key_ && numkey == num_bib_key_) {
ret = (*it).second;
posts.erase(it);
break;
}
if ((*it).first == bib_key_)
++numkey;
}
} else if (key == "year")
ret = getYear();
}
@ -1342,7 +1364,14 @@ docstring const BiblioInfo::getLabel(vector<docstring> keys,
docstring ret = format;
vector<docstring>::const_iterator key = keys.begin();
vector<docstring>::const_iterator ken = keys.end();
vector<docstring> handled_keys;
for (int i = 0; key != ken; ++key, ++i) {
handled_keys.push_back(*key);
int n = 0;
for (auto const k : handled_keys) {
if (k == *key)
++n;
}
BiblioInfo::const_iterator it = find(*key);
BibTeXInfo empty_data;
empty_data.key(*key);
@ -1356,6 +1385,7 @@ docstring const BiblioInfo::getLabel(vector<docstring> keys,
xrefptrs.push_back(&(xrefit->second));
}
}
data.numKey(n);
ret = data.getLabel(xrefptrs, buf, ret, ci, key + 1 != ken, i == 1);
}

View File

@ -97,6 +97,9 @@ public:
void label(docstring const & d) { label_= d; }
///
void key(docstring const & d) { bib_key_= d; }
/// Record the number of occurences of the same key
/// (duplicates are allowed with qualified citation lists)
void numKey(int const i) { num_bib_key_ = i; }
///
docstring const & label() const { return label_; }
///
@ -145,6 +148,8 @@ private:
bool is_bibtex_;
/// the BibTeX key for this entry
docstring bib_key_;
/// Number of occurences of the same key
int num_bib_key_;
/// the label that will appear in citations
/// this is easily set from bibliography environments, but has
/// to be calculated for entries we get from BibTeX

View File

@ -13,8 +13,8 @@
#define CITATION_H
#include "support/docstring.h"
#include <map>
#include <string>
#include <vector>
namespace lyx {
@ -87,14 +87,16 @@ public:
docstring textAfter;
/// text before the citation
docstring textBefore;
///
typedef std::vector<std::pair<docstring, docstring>> QualifiedList;
/// Qualified lists's pre texts
std::map<docstring, docstring> pretexts;
QualifiedList pretexts;
///
std::map<docstring, docstring> getPretexts() const { return pretexts; }
QualifiedList getPretexts() const { return pretexts; }
/// Qualified lists's post texts
std::map<docstring, docstring> posttexts;
QualifiedList posttexts;
///
std::map<docstring, docstring> getPosttexts() const { return posttexts; }
QualifiedList getPosttexts() const { return posttexts; }
/// the maximum display size as a label
size_t max_size;
/// the maximum size of the processed keys

View File

@ -694,6 +694,8 @@ QStringList GuiCitation::selectedKeys()
void GuiCitation::setPreTexts(vector<docstring> const m)
{
// account for multiple use of the same keys
QList<QModelIndex> handled;
for (docstring const & s: m) {
QStandardItem * si = new QStandardItem();
docstring key;
@ -701,11 +703,17 @@ void GuiCitation::setPreTexts(vector<docstring> const m)
si->setData(toqstr(pre));
si->setText(toqstr(pre));
QModelIndexList qmil =
selected_model_.match(selected_model_.index(0, 1),
Qt::DisplayRole, toqstr(key), 1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
if (!qmil.empty())
selected_model_.setItem(qmil.front().row(), 0, si);
selected_model_.match(selected_model_.index(0, 1),
Qt::DisplayRole, toqstr(key), -1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
for (int i = 0; i < qmil.size(); ++i){
QModelIndex idx = qmil[i];
if (!handled.contains(idx)) {
selected_model_.setItem(idx.row(), 0, si);
handled.append(idx);
break;
}
}
}
}
@ -716,7 +724,7 @@ vector<docstring> GuiCitation::getPreTexts()
for (int i = 0; i != selected_model_.rowCount(); ++i) {
QStandardItem const * key = selected_model_.item(i, 1);
QStandardItem const * pre = selected_model_.item(i, 0);
if (key && pre && !key->text().isEmpty() && !pre->text().isEmpty())
if (key && pre && !key->text().isEmpty())
res.push_back(qstring_to_ucs4(key->text()) + " " + qstring_to_ucs4(pre->text()));
}
return res;
@ -725,6 +733,8 @@ vector<docstring> GuiCitation::getPreTexts()
void GuiCitation::setPostTexts(vector<docstring> const m)
{
// account for multiple use of the same keys
QList<QModelIndex> handled;
for (docstring const & s: m) {
QStandardItem * si = new QStandardItem();
docstring key;
@ -732,11 +742,17 @@ void GuiCitation::setPostTexts(vector<docstring> const m)
si->setData(toqstr(post));
si->setText(toqstr(post));
QModelIndexList qmil =
selected_model_.match(selected_model_.index(0, 1),
Qt::DisplayRole, toqstr(key), 1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
if (!qmil.empty())
selected_model_.setItem(qmil.front().row(), 2, si);
selected_model_.match(selected_model_.index(0, 1),
Qt::DisplayRole, toqstr(key), -1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
for (int i = 0; i < qmil.size(); ++i){
QModelIndex idx = qmil[i];
if (!handled.contains(idx)) {
selected_model_.setItem(idx.row(), 2, si);
handled.append(idx);
break;
}
}
}
}
@ -747,7 +763,7 @@ vector<docstring> GuiCitation::getPostTexts()
for (int i = 0; i != selected_model_.rowCount(); ++i) {
QStandardItem const * key = selected_model_.item(i, 1);
QStandardItem const * post = selected_model_.item(i, 2);
if (key && post && !key->text().isEmpty() && !post->text().isEmpty())
if (key && post && !key->text().isEmpty())
res.push_back(qstring_to_ucs4(key->text()) + " " + qstring_to_ucs4(post->text()));
}
return res;
@ -894,17 +910,17 @@ BiblioInfo::CiteStringMap GuiCitation::citationStyles(BiblioInfo const & bi, siz
&& (selectedLV->model()->rowCount() > 1
|| !pretexts.empty()
|| !posttexts.empty());
std::map<docstring, docstring> pres;
vector<pair<docstring, docstring>> pres;
for (docstring const & s: pretexts) {
docstring key;
docstring val = split(s, key, ' ');
pres[key] = val;
pres.push_back(make_pair(key, val));
}
std::map<docstring, docstring> posts;
vector<pair<docstring, docstring>> posts;
for (docstring const & s: posttexts) {
docstring key;
docstring val = split(s, key, ' ');
posts[key] = val;
posts.push_back(make_pair(key, val));
}
CiteItem ci;
ci.textBefore = qstring_to_ucs4(textBeforeED->text());

View File

@ -1609,9 +1609,9 @@ void MenuDefinition::expandCiteStyles(BufferView const * bv)
&& (keys.size() > 1
|| !citinset->getParam("pretextlist").empty()
|| !citinset->getParam("posttextlist").empty());
std::map<docstring, docstring> pres =
vector<pair<docstring, docstring>> pres =
citinset->getQualifiedLists(citinset->getParam("pretextlist"));
std::map<docstring, docstring> posts =
vector<pair<docstring, docstring>> posts =
citinset->getQualifiedLists(citinset->getParam("posttextlist"));
CiteItem ci;

View File

@ -329,15 +329,17 @@ inline docstring wrapCitation(docstring const & key,
} // anonymous namespace
map<docstring, docstring> InsetCitation::getQualifiedLists(docstring const p) const
vector<pair<docstring, docstring>> InsetCitation::getQualifiedLists(docstring const p) const
{
vector<docstring> ps =
getVectorFromString(p, from_ascii("\t"));
std::map<docstring, docstring> res;
QualifiedList res;
for (docstring const & s: ps) {
docstring key;
docstring val = split(s, key, ' ');
res[key] = val;
docstring key = s;
docstring val;
if (contains(s, ' '))
val = split(s, key, ' ');
res.push_back(make_pair(key, val));
}
return res;
}
@ -399,8 +401,8 @@ docstring InsetCitation::complexLabel(bool for_xhtml) const
&& (keys.size() > 1
|| !getParam("pretextlist").empty()
|| !getParam("posttextlist").empty());
map<docstring, docstring> pres = getQualifiedLists(getParam("pretextlist"));
map<docstring, docstring> posts = getQualifiedLists(getParam("posttextlist"));
QualifiedList pres = getQualifiedLists(getParam("pretextlist"));
QualifiedList posts = getQualifiedLists(getParam("posttextlist"));
CiteItem ci;
ci.textBefore = getParam("before");
@ -611,12 +613,30 @@ void InsetCitation::latex(otexstream & os, OutputParams const & runparams) const
os << '{' << escape(cleanupWhitespace(key)) << '}';
else {
if (qualified) {
map<docstring, docstring> pres = getQualifiedLists(getParam("pretextlist"));
map<docstring, docstring> posts = getQualifiedLists(getParam("posttextlist"));
for (docstring const & k: keys) {
docstring bef = params().prepareCommand(runparams, pres[k],
QualifiedList pres = getQualifiedLists(getParam("pretextlist"));
QualifiedList posts = getQualifiedLists(getParam("posttextlist"));
for (docstring const & k : keys) {
docstring prenote;
QualifiedList::iterator it = pres.begin();
for (; it != pres.end() ; ++it) {
if ((*it).first == k) {
prenote = (*it).second;
pres.erase(it);
break;
}
}
docstring bef = params().prepareCommand(runparams, prenote,
pinfo["pretextlist"].handling());
docstring aft = params().prepareCommand(runparams, posts[k],
docstring postnote;
QualifiedList::iterator pit = posts.begin();
for (; pit != posts.end() ; ++pit) {
if ((*pit).first == k) {
postnote = (*pit).second;
posts.erase(pit);
break;
}
}
docstring aft = params().prepareCommand(runparams, postnote,
pinfo["posttextlist"].handling());
if (!bef.empty())
os << '[' << protectArgument(bef)

View File

@ -82,12 +82,14 @@ public:
static bool isCompatibleCommand(std::string const &);
//@}
///
typedef std::vector<std::pair<docstring, docstring>> QualifiedList;
///
void redoLabel() { cache.recalculate = true; }
///
CitationStyle getCitationStyle(BufferParams const & bp, std::string const & input,
std::vector<CitationStyle> const & valid_styles) const;
///
std::map<docstring, docstring> getQualifiedLists(docstring const p) const;
QualifiedList getQualifiedLists(docstring const p) const;
///
static bool last_literal;

View File

@ -4461,13 +4461,13 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
}
string keys, pretextlist, posttextlist;
if (qualified) {
map<string, string> pres, posts, preslit, postslit;
vector<pair<string, string>> pres, posts, preslit, postslit;
vector<string> lkeys;
// text before the citation
string lbefore, lbeforelit;
// text after the citation
string lafter, lafterlit;
string lkey;
string lkey;
pair<bool, string> laft, lbef;
while (true) {
get_cite_arguments(p, true, lbefore, lafter);
@ -4478,7 +4478,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
laft = convert_latexed_command_inset_arg(lafter);
literal |= !laft.first;
lafter = laft.second;
lafterlit = subst(lbefore, "\n", " ");
lafterlit = subst(lafter, "\n", " ");
}
if (!lbefore.empty()) {
lbefore.erase(0, 1);
@ -4503,14 +4503,10 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
lkey = p.getArg('{', '}');
if (lkey.empty())
break;
if (!lbefore.empty()) {
pres.insert(make_pair(lkey, lbefore));
preslit.insert(make_pair(lkey, lbeforelit));
}
if (!lafter.empty()) {
posts.insert(make_pair(lkey, lafter));
postslit.insert(make_pair(lkey, lafterlit));
}
pres.push_back(make_pair(lkey, lbefore));
preslit.push_back(make_pair(lkey, lbeforelit));
posts.push_back(make_pair(lkey, lafter));
postslit.push_back(make_pair(lkey, lafterlit));
lkeys.push_back(lkey);
}
keys = convert_literate_command_inset_arg(getStringFromVector(lkeys));
@ -4521,12 +4517,16 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
for (auto const & ptl : pres) {
if (!pretextlist.empty())
pretextlist += '\t';
pretextlist += ptl.first + " " + ptl.second;
pretextlist += ptl.first;
if (!ptl.second.empty())
pretextlist += " " + ptl.second;
}
for (auto const & potl : posts) {
if (!posttextlist.empty())
posttextlist += '\t';
posttextlist += potl.first + " " + potl.second;
posttextlist += potl.first;
if (!potl.second.empty())
posttextlist += " " + potl.second;
}
} else
keys = convert_literate_command_inset_arg(p.verbatim_item());

View File

@ -32,8 +32,8 @@ extern char const * const lyx_version_info;
// Do not remove the comment below, so we get merge conflict in
// independent branches. Instead add your own.
#define LYX_FORMAT_LYX 585 // spitz: add more page sizes to KOMA and memoir
#define LYX_FORMAT_TEX2LYX 585
#define LYX_FORMAT_LYX 586 // spitz: allow duplicate keys in qualified citation lists
#define LYX_FORMAT_TEX2LYX 586
#if LYX_FORMAT_TEX2LYX != LYX_FORMAT_LYX
#ifndef _MSC_VER