]> git.lyx.org Git - features.git/blob - src/insets/InsetBibitem.cpp
Fix encoding issues in bibitems (bug #6534)
[features.git] / src / insets / InsetBibitem.cpp
1 /**
2  * \file InsetBibitem.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12 #include <algorithm>
13
14 #include "InsetBibitem.h"
15
16 #include "BiblioInfo.h"
17 #include "Buffer.h"
18 #include "buffer_funcs.h"
19 #include "BufferParams.h"
20 #include "BufferView.h"
21 #include "Counters.h"
22 #include "DispatchResult.h"
23 #include "Encoding.h"
24 #include "FuncRequest.h"
25 #include "InsetIterator.h"
26 #include "InsetList.h"
27 #include "Language.h" 
28 #include "Lexer.h"
29 #include "output_xhtml.h"
30 #include "OutputParams.h"
31 #include "Paragraph.h"
32 #include "ParagraphList.h"
33 #include "ParIterator.h"
34 #include "TextClass.h"
35
36 #include "frontends/alert.h"
37
38 #include "support/convert.h"
39 #include "support/debug.h"
40 #include "support/docstream.h"
41 #include "support/gettext.h"
42 #include "support/lstrings.h"
43
44 using namespace std;
45 using namespace lyx::support;
46
47 namespace lyx {
48
49
50 int InsetBibitem::key_counter = 0;
51 docstring const key_prefix = from_ascii("key-");
52
53
54 InsetBibitem::InsetBibitem(Buffer * buf, InsetCommandParams const & p)
55         : InsetCommand(buf, p, "bibitem")
56 {
57         buffer_->invalidateBibinfoCache();
58         if (getParam("key").empty())
59                 setParam("key", key_prefix + convert<docstring>(++key_counter));
60 }
61
62
63 InsetBibitem::~InsetBibitem()
64 {
65         if (isBufferValid())
66                 buffer_->invalidateBibinfoCache();
67 }
68
69
70 void InsetBibitem::initView()
71 {
72         updateCommand(getParam("key"));
73 }
74
75
76 void InsetBibitem::updateCommand(docstring const & new_key, bool)
77 {
78         docstring const old_key = getParam("key");
79         docstring key = new_key;
80
81         vector<docstring> bibkeys = buffer().masterBibInfo().getKeys();
82
83         int i = 1;
84
85         if (find(bibkeys.begin(), bibkeys.end(), key) != bibkeys.end()) {
86                 // generate unique label
87                 key = new_key + '-' + convert<docstring>(i);
88                 while (find(bibkeys.begin(), bibkeys.end(), key) != bibkeys.end()) {
89                         ++i;
90                         key = new_key + '-' + convert<docstring>(i);
91                 }
92                 frontend::Alert::warning(_("Keys must be unique!"),
93                         bformat(_("The key %1$s already exists,\n"
94                         "it will be changed to %2$s."), new_key, key));
95         }
96         setParam("key", key);
97
98         buffer().updateLabels();
99 }
100
101
102 ParamInfo const & InsetBibitem::findInfo(string const & /* cmdName */)
103 {
104         static ParamInfo param_info_;
105         if (param_info_.empty()) {
106                 param_info_.add("label", ParamInfo::LATEX_OPTIONAL);
107                 param_info_.add("key", ParamInfo::LATEX_REQUIRED);
108         }
109         return param_info_;
110 }
111
112
113 void InsetBibitem::doDispatch(Cursor & cur, FuncRequest & cmd)
114 {
115         switch (cmd.action) {
116
117         case LFUN_INSET_MODIFY: {
118                 InsetCommandParams p(BIBITEM_CODE);
119                 InsetCommand::string2params("bibitem", to_utf8(cmd.argument()), p);
120                 if (p.getCmdName().empty()) {
121                         cur.noUpdate();
122                         break;
123                 }
124                 docstring const & old_key = params()["key"];
125                 setParam("label", p["label"]);
126                 if (p["key"] != old_key) {
127                         updateCommand(p["key"]);
128                         cur.bv().buffer().changeRefsIfUnique(old_key,
129                                 params()["key"], CITE_CODE);
130                 }
131                 buffer_->invalidateBibinfoCache();
132                 break;
133         }
134
135         default:
136                 InsetCommand::doDispatch(cur, cmd);
137                 break;
138         }
139 }
140
141
142 void InsetBibitem::read(Lexer & lex)
143 {
144         InsetCommand::read(lex);
145
146         if (prefixIs(getParam("key"), key_prefix)) {
147                 int const key = convert<int>(getParam("key").substr(key_prefix.length()));
148                 key_counter = max(key_counter, key);
149         }
150 }
151
152
153 docstring InsetBibitem::bibLabel() const
154 {
155         docstring const & label = getParam("label");
156         return label.empty() ? autolabel_ : label;
157 }
158
159
160 docstring InsetBibitem::screenLabel() const
161 {
162         return getParam("key") + " [" + bibLabel() + ']';
163 }
164
165
166 int InsetBibitem::plaintext(odocstream & os, OutputParams const &) const
167 {
168         odocstringstream oss;
169         oss << '[' << bibLabel() << "] ";
170
171         docstring const str = oss.str();
172         os << str;
173
174         return str.size();
175 }
176
177
178 int InsetBibitem::latex(odocstream & os, OutputParams const & runparams) const
179 {
180         docstring cmd = '\\' + from_ascii(defaultCommand());
181         docstring uncodable;
182         if (!getParam("label").empty()) {
183                 cmd += '[';
184                 docstring orig = getParam("label");
185                 for (size_t n = 0; n < orig.size(); ++n) {
186                         try {
187                                 cmd += runparams.encoding->latexChar(orig[n]);
188                         } catch (EncodingException & /* e */) {
189                                 LYXERR0("Uncodable character in bibitem!");
190                                 if (runparams.dryrun) {
191                                         cmd += "<" + _("LyX Warning: ")
192                                             + _("uncodable character") + " '";
193                                         cmd += docstring(1, orig[n]);
194                                         cmd += "'>";
195                                 } else
196                                         uncodable += orig[n];
197                         }
198                 }
199                 cmd += ']';
200         }
201         cmd += '{' + escape(getParam("key")) + '}';
202
203         os << cmd;
204
205         if (!uncodable.empty()) {
206                 // issue a warning about omitted characters
207                 // FIXME: should be passed to the error dialog
208                 frontend::Alert::warning(_("Uncodable characters in bibliography item"),
209                         bformat(_("The following characters in one of the bibliography items are\n"
210                                   "not representable in the current encoding and have been omitted:\n%1$s."),
211                         uncodable));
212         }
213         
214         return 0;
215 }
216
217
218 // ale070405
219 docstring bibitemWidest(Buffer const & buffer, OutputParams const & runparams)
220 {
221         int w = 0;
222
223         InsetBibitem const * bitem = 0;
224
225         // FIXME: this font is used unitialized for now but should  be set to
226         // a proportional font. Here is what Georg Baum has to say about it:
227         /*
228         bibitemWidest() is supposed to find the bibitem with the widest label in the
229         output, because that is needed as an argument of the bibliography
230         environment to determine the correct indentation. To be 100% correct we
231         would need the metrics of the font that is used in the output, but usually
232         we don't have access to these.
233         In practice, any proportional font is probably good enough, since we don't
234         need to know the final with, we only need to know the which label is the
235         widest.
236         Unless there is an easy way to get the metrics of the output font I suggest
237         to use a hardcoded font like "Times" or so.
238
239         It is very important that the result of this function is the same both with
240         and without GUI. After thinking about this it is clear that no Font
241         metrics should be used here, since these come from the gui. If we can't
242         easily get the LaTeX font metrics we should make our own poor mans font
243         metrics replacement, e.g. by hardcoding the metrics of the standard TeX
244         font.
245         */
246
247         docstring lbl;
248
249         ParagraphList::const_iterator it = buffer.paragraphs().begin();
250         ParagraphList::const_iterator end = buffer.paragraphs().end();
251
252         for (; it != end; ++it) {
253                 if (it->insetList().empty())
254                         continue;
255                 Inset * inset = it->insetList().begin()->inset;
256                 if (inset->lyxCode() != BIBITEM_CODE)
257                         continue;
258
259                 bitem = static_cast<InsetBibitem const *>(inset);
260                 docstring const label = bitem->bibLabel();
261
262                 // FIXME: we can't be sure using the following that the GUI
263                 // version and the command-line version will give the same
264                 // result.
265                 //
266                 //int const wx = use_gui?
267                 //      theFontMetrics(font).width(label): label.size();
268                 //
269                 // So for now we just use the label size in order to be sure
270                 // that GUI and no-GUI gives the same bibitem (even if that is
271                 // potentially the wrong one.
272                 int const wx = label.size();
273
274                 if (wx > w) {
275                         w = wx;
276                         lbl = label;
277                 }
278         }
279
280         if (!lbl.empty()) {
281                 docstring latex_lbl;
282                 for (size_t n = 0; n < lbl.size(); ++n) {
283                         try {
284                                 latex_lbl += runparams.encoding->latexChar(lbl[n]);
285                         } catch (EncodingException & /* e */) {
286                                 if (runparams.dryrun) {
287                                         latex_lbl += "<" + _("LyX Warning: ")
288                                                   + _("uncodable character") + " '";
289                                         latex_lbl += docstring(1, lbl[n]);
290                                         latex_lbl += "'>";
291                                 }
292                         }
293                 }
294                 return latex_lbl;
295         }
296
297         return from_ascii("99");
298 }
299
300
301 void InsetBibitem::fillWithBibKeys(BiblioInfo & keys, InsetIterator const & it) const
302 {
303         docstring const key = getParam("key");
304         BibTeXInfo keyvalmap(false);
305         keyvalmap.label(bibLabel());
306         DocIterator doc_it(it); 
307         doc_it.forwardPos();
308         keyvalmap[from_ascii("ref")] = doc_it.paragraph().asString();
309         keys[key] = keyvalmap;
310 }
311
312
313 // Update the counters of this inset and of its contents
314 void InsetBibitem::updateLabels(ParIterator const & it, UpdateType utype)
315 {
316         BufferParams const & bp = buffer().masterBuffer()->params();
317         Counters & counters = bp.documentClass().counters();
318         docstring const bibitem = from_ascii("bibitem");
319         if (counters.hasCounter(bibitem) && getParam("label").empty()) {
320                 counters.step(bibitem, utype);
321                 string const & lang = it.paragraph().getParLanguage(bp)->code();
322                 autolabel_ = counters.theCounter(bibitem, lang);
323         } else {
324                 autolabel_ = from_ascii("??");
325         }
326 }
327
328
329 docstring InsetBibitem::xhtml(XHTMLStream & xs, OutputParams const &) const
330 {
331         // FIXME XHTML
332         // XHTML 1.1 doesn't have the "name" attribute for <a>, so we have to use
333         // the "id" atttribute to get the document to validate. Probably, we will
334         // need to use "name" anyway, eventually, because some browsers do not
335         // handle jumping to ids. If we don't do that, though, we can just put the
336         // id into the span tag.
337         string const attrs = "id='" + to_utf8(getParam("key")) + "'";
338         xs << html::CompTag("a", attrs);
339         xs << html::StartTag("span", "class='bibitemlabel'");
340         xs << bibLabel();
341         xs << html::EndTag("span");
342         return docstring();
343 }
344
345
346 } // namespace lyx