From 1386a3d8fdae3a0e6c8d70b89cbf910e120c8e61 Mon Sep 17 00:00:00 2001 From: Juergen Spitzmueller Date: Wed, 7 Aug 2019 13:00:29 +0200 Subject: [PATCH] Allow for multiple use of same key in qualified citation lists File format change Fixes: #11618, #11632 --- development/FORMAT | 3 + lib/lyx2lyx/lyx_2_4.py | 148 ++++++++++++++++++++++++++++++- src/BiblioInfo.cpp | 42 +++++++-- src/BiblioInfo.h | 5 ++ src/Citation.h | 12 +-- src/frontends/qt/GuiCitation.cpp | 48 ++++++---- src/frontends/qt/Menus.cpp | 4 +- src/insets/InsetCitation.cpp | 44 ++++++--- src/insets/InsetCitation.h | 4 +- src/tex2lyx/text.cpp | 26 +++--- src/version.h | 4 +- 11 files changed, 281 insertions(+), 59 deletions(-) diff --git a/development/FORMAT b/development/FORMAT index 4bf18da61e..340d009e99 100644 --- a/development/FORMAT +++ b/development/FORMAT @@ -7,6 +7,9 @@ changes happened in particular if possible. A good example would be ----------------------- +2019-08-07 Jürgen Spitzmüller + * Format incremented to 586: Allow for duplicate keys in qualified citation lists + 2019-08-06 Jürgen Spitzmüller * Format incremented to 585: - Add more page sizes to KOMA and memoir. diff --git a/lib/lyx2lyx/lyx_2_4.py b/lib/lyx2lyx/lyx_2_4.py index 47311b67c7..3869e07b49 100644 --- a/lib/lyx2lyx/lyx_2_4.py +++ b/lib/lyx2lyx/lyx_2_4.py @@ -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]], diff --git a/src/BiblioInfo.cpp b/src/BiblioInfo.cpp index b0f64bd0e3..c723b9ad39 100644 --- a/src/BiblioInfo.cpp +++ b/src/BiblioInfo.cpp @@ -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> pres = ci.getPretexts(); + vector>::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> posts = ci.getPosttexts(); + vector>::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 keys, docstring ret = format; vector::const_iterator key = keys.begin(); vector::const_iterator ken = keys.end(); + vector 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 keys, xrefptrs.push_back(&(xrefit->second)); } } + data.numKey(n); ret = data.getLabel(xrefptrs, buf, ret, ci, key + 1 != ken, i == 1); } diff --git a/src/BiblioInfo.h b/src/BiblioInfo.h index 61e525d2d9..c348a23541 100644 --- a/src/BiblioInfo.h +++ b/src/BiblioInfo.h @@ -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 diff --git a/src/Citation.h b/src/Citation.h index 600ba6ca97..8aeb56c3e5 100644 --- a/src/Citation.h +++ b/src/Citation.h @@ -13,8 +13,8 @@ #define CITATION_H #include "support/docstring.h" -#include #include +#include namespace lyx { @@ -87,14 +87,16 @@ public: docstring textAfter; /// text before the citation docstring textBefore; + /// + typedef std::vector> QualifiedList; /// Qualified lists's pre texts - std::map pretexts; + QualifiedList pretexts; /// - std::map getPretexts() const { return pretexts; } + QualifiedList getPretexts() const { return pretexts; } /// Qualified lists's post texts - std::map posttexts; + QualifiedList posttexts; /// - std::map 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 diff --git a/src/frontends/qt/GuiCitation.cpp b/src/frontends/qt/GuiCitation.cpp index 3ef4bb77f3..e5610589c8 100644 --- a/src/frontends/qt/GuiCitation.cpp +++ b/src/frontends/qt/GuiCitation.cpp @@ -694,6 +694,8 @@ QStringList GuiCitation::selectedKeys() void GuiCitation::setPreTexts(vector const m) { + // account for multiple use of the same keys + QList handled; for (docstring const & s: m) { QStandardItem * si = new QStandardItem(); docstring key; @@ -701,11 +703,17 @@ void GuiCitation::setPreTexts(vector 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 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 GuiCitation::getPreTexts() void GuiCitation::setPostTexts(vector const m) { + // account for multiple use of the same keys + QList handled; for (docstring const & s: m) { QStandardItem * si = new QStandardItem(); docstring key; @@ -732,11 +742,17 @@ void GuiCitation::setPostTexts(vector 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 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 pres; + vector> 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 posts; + vector> 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()); diff --git a/src/frontends/qt/Menus.cpp b/src/frontends/qt/Menus.cpp index f8979cfa09..498f53d00f 100644 --- a/src/frontends/qt/Menus.cpp +++ b/src/frontends/qt/Menus.cpp @@ -1609,9 +1609,9 @@ void MenuDefinition::expandCiteStyles(BufferView const * bv) && (keys.size() > 1 || !citinset->getParam("pretextlist").empty() || !citinset->getParam("posttextlist").empty()); - std::map pres = + vector> pres = citinset->getQualifiedLists(citinset->getParam("pretextlist")); - std::map posts = + vector> posts = citinset->getQualifiedLists(citinset->getParam("posttextlist")); CiteItem ci; diff --git a/src/insets/InsetCitation.cpp b/src/insets/InsetCitation.cpp index 6d5c381df8..401d0142b5 100644 --- a/src/insets/InsetCitation.cpp +++ b/src/insets/InsetCitation.cpp @@ -329,15 +329,17 @@ inline docstring wrapCitation(docstring const & key, } // anonymous namespace -map InsetCitation::getQualifiedLists(docstring const p) const +vector> InsetCitation::getQualifiedLists(docstring const p) const { vector ps = getVectorFromString(p, from_ascii("\t")); - std::map 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 pres = getQualifiedLists(getParam("pretextlist")); - map 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 pres = getQualifiedLists(getParam("pretextlist")); - map 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) diff --git a/src/insets/InsetCitation.h b/src/insets/InsetCitation.h index a8e9e1853d..7c8231c538 100644 --- a/src/insets/InsetCitation.h +++ b/src/insets/InsetCitation.h @@ -82,12 +82,14 @@ public: static bool isCompatibleCommand(std::string const &); //@} /// + typedef std::vector> QualifiedList; + /// void redoLabel() { cache.recalculate = true; } /// CitationStyle getCitationStyle(BufferParams const & bp, std::string const & input, std::vector const & valid_styles) const; /// - std::map getQualifiedLists(docstring const p) const; + QualifiedList getQualifiedLists(docstring const p) const; /// static bool last_literal; diff --git a/src/tex2lyx/text.cpp b/src/tex2lyx/text.cpp index 2b34adc55e..b66ef7f44a 100644 --- a/src/tex2lyx/text.cpp +++ b/src/tex2lyx/text.cpp @@ -4461,13 +4461,13 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, } string keys, pretextlist, posttextlist; if (qualified) { - map pres, posts, preslit, postslit; + vector> pres, posts, preslit, postslit; vector lkeys; // text before the citation string lbefore, lbeforelit; // text after the citation string lafter, lafterlit; - string lkey; + string lkey; pair 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()); diff --git a/src/version.h b/src/version.h index 15e2e737fd..ca5e63df4a 100644 --- a/src/version.h +++ b/src/version.h @@ -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