-----------------------
+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.
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])
+
##
[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]],
//////////////////////////////////////////////////////////////////////
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)
{}
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();
}
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);
xrefptrs.push_back(&(xrefit->second));
}
}
+ data.numKey(n);
ret = data.getLabel(xrefptrs, buf, ret, ci, key + 1 != ken, i == 1);
}
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_; }
///
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
#define CITATION_H
#include "support/docstring.h"
-#include <map>
#include <string>
+#include <vector>
namespace lyx {
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
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;
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;
+ }
+ }
}
}
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;
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;
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;
+ }
+ }
}
}
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;
&& (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());
&& (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;
} // 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;
}
&& (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");
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)
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;
}
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);
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);
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));
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());
// 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