]> git.lyx.org Git - lyx.git/blob - src/insets/InsetRef.cpp
Merge branch 'master' into biblatex2
[lyx.git] / src / insets / InsetRef.cpp
1 /**
2  * \file InsetRef.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author José Matos
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10 #include <config.h>
11
12 #include "InsetRef.h"
13
14 #include "Buffer.h"
15 #include "BufferParams.h"
16 #include "Cursor.h"
17 #include "DispatchResult.h"
18 #include "InsetLabel.h"
19 #include "Language.h"
20 #include "LaTeXFeatures.h"
21 #include "LyX.h"
22 #include "OutputParams.h"
23 #include "output_xhtml.h"
24 #include "ParIterator.h"
25 #include "sgml.h"
26 #include "texstream.h"
27 #include "TocBackend.h"
28
29 #include "support/debug.h"
30 #include "support/docstream.h"
31 #include "support/gettext.h"
32 #include "support/lstrings.h"
33 #include "support/textutils.h"
34
35 using namespace lyx::support;
36 using namespace std;
37
38 namespace lyx {
39
40
41 InsetRef::InsetRef(Buffer * buf, InsetCommandParams const & p)
42         : InsetCommand(buf, p)
43 {}
44
45
46 InsetRef::InsetRef(InsetRef const & ir)
47         : InsetCommand(ir)
48 {}
49
50
51 bool InsetRef::isCompatibleCommand(string const & s) {
52         //FIXME This is likely not the best way to handle this.
53         //But this stuff is hardcoded elsewhere already.
54         return s == "ref" 
55                 || s == "pageref"
56                 || s == "vref" 
57                 || s == "vpageref"
58                 || s == "formatted"
59                 || s == "eqref"
60                 || s == "nameref"
61                 || s == "labelonly";
62 }
63
64
65 ParamInfo const & InsetRef::findInfo(string const & /* cmdName */)
66 {
67         static ParamInfo param_info_;
68         if (param_info_.empty()) {
69                 param_info_.add("name", ParamInfo::LATEX_OPTIONAL);
70                 param_info_.add("reference", ParamInfo::LATEX_REQUIRED,
71                                 ParamInfo::HANDLING_ESCAPE);
72                 param_info_.add("plural", ParamInfo::LYX_INTERNAL);
73                 param_info_.add("caps", ParamInfo::LYX_INTERNAL);
74                 param_info_.add("noprefix", ParamInfo::LYX_INTERNAL);
75         }
76         return param_info_;
77 }
78
79
80 namespace {
81
82 void capitalize(docstring & s) {
83         char_type t = uppercase(s[0]);
84         s[0] = t;
85 }
86
87 }
88
89
90 // the ref argument is the label name we are referencing.
91 // we expect ref to be in the form: pfx:suffix.
92 //
93 // if it isn't, then we can't produce a formatted reference, 
94 // so we return "\ref" and put ref into label.
95 //
96 // for refstyle, we return "\pfxcmd", and put suffix into 
97 // label and pfx into prefix. this is because refstyle expects
98 // the command: \pfxcmd{suffix}.
99 // 
100 // for prettyref, we return "\prettyref" and put ref into label
101 // and pfx into prefix. this is because prettyref uses the whole
102 // label, thus: \prettyref{pfx:suffix}.
103 //
104 docstring InsetRef::getFormattedCmd(docstring const & ref, 
105         docstring & label, docstring & prefix, docstring const & caps) const
106 {
107         static docstring const defcmd = from_ascii("\\ref");
108         static docstring const prtcmd = from_ascii("\\prettyref");
109         
110         label = split(ref, prefix, ':');
111
112         // we have to have xxx:xxxxx...
113         if (label.empty()) {
114                 LYXERR0("Label `" << ref << "' contains no `:' separator.");
115                 label = ref;
116                 prefix = from_ascii("");
117                 return defcmd;
118         }
119
120         if (prefix.empty()) {
121                 // we have ":xxxx"
122                 label = ref;
123                 return defcmd;
124         }
125         
126         if (!buffer().params().use_refstyle) {
127                 // \prettyref uses the whole label
128                 label = ref;
129                 return prtcmd;
130         }
131
132         // make sure the prefix is legal for a latex command
133         int const len = prefix.size();
134         for (int i = 0; i < len; i++) {
135                 char_type const c = prefix[i];
136                 if (!isAlphaASCII(c)) {
137                         LYXERR0("Prefix `" << prefix << "' is invalid for LaTeX.");
138                         // restore the label
139                         label = ref;
140                         return defcmd;
141                 }
142         }
143         if (caps == "true") {
144                 capitalize(prefix);
145         }
146         return from_ascii("\\") + prefix + from_ascii("ref");
147 }
148
149
150 docstring InsetRef::getEscapedLabel(OutputParams const & rp) const
151 {
152         InsetCommandParams const & p = params();
153         ParamInfo const & pi = p.info();
154         ParamInfo::ParamData const & pd = pi["reference"];
155         return p.prepareCommand(rp, getParam("reference"), pd.handling());              
156 }
157
158
159 void InsetRef::latex(otexstream & os, OutputParams const & rp) const
160 {
161         string const & cmd = getCmdName();
162         docstring const & data = getEscapedLabel(rp);
163
164         if (rp.inulemcmd > 0)
165                 os << "\\mbox{";
166
167         if (cmd == "eqref" && buffer().params().use_refstyle) {
168                 // we advertise this as printing "(n)", so we'll do that, at least
169                 // for refstyle, since refstlye's own \eqref prints, by default,
170                 // "equation n". if one wants \eqref, one can get it by using a
171                 // formatted label in this case.
172                 os << '(' << from_ascii("\\ref{") << data << from_ascii("})");
173         } 
174         else if (cmd == "formatted") {
175                 docstring label;
176                 docstring prefix;
177                 docstring const fcmd = 
178                         getFormattedCmd(data, label, prefix, getParam("caps"));
179                 os << fcmd;
180                 if (buffer().params().use_refstyle && getParam("plural") == "true")
181                     os << "[s]";
182                 os << '{' << label << '}';
183         }
184         else if (cmd == "labelonly") {
185                 docstring const & ref = getParam("reference");
186                 if (getParam("noprefix") != "true")
187                         os << ref;
188                 else {
189                         docstring prefix;
190                         docstring suffix = split(ref, prefix, ':');
191                         if (suffix.empty()) {
192                     LYXERR0("Label `" << ref << "' contains no `:' separator.");
193                                 os << ref;
194                         } else {
195                                 os << suffix;
196                         }
197                 }
198         }
199         else {
200                 // We don't want to output p_["name"], since that is only used 
201                 // in docbook. So we construct new params, without it, and use that.
202                 InsetCommandParams p(REF_CODE, cmd);
203                 docstring const ref = getParam("reference");
204                 p["reference"] = ref;
205                 os << p.getCommand(rp);
206         }
207
208         if (rp.inulemcmd > 0)
209                 os << "}";
210 }
211
212
213 int InsetRef::plaintext(odocstringstream & os,
214         OutputParams const &, size_t) const
215 {
216         docstring const str = getParam("reference");
217         os << '[' << str << ']';
218         return 2 + str.size();
219 }
220
221
222 int InsetRef::docbook(odocstream & os, OutputParams const & runparams) const
223 {
224         docstring const & name = getParam("name");
225         if (name.empty()) {
226                 if (runparams.flavor == OutputParams::XML) {
227                         os << "<xref linkend=\""
228                            << sgml::cleanID(buffer(), runparams, getParam("reference"))
229                            << "\" />";
230                 } else {
231                         os << "<xref linkend=\""
232                            << sgml::cleanID(buffer(), runparams, getParam("reference"))
233                            << "\">";
234                 }
235         } else {
236                 os << "<link linkend=\""
237                    << sgml::cleanID(buffer(), runparams, getParam("reference"))
238                    << "\">"
239                    << getParam("name")
240                    << "</link>";
241         }
242
243         return 0;
244 }
245
246
247 docstring InsetRef::xhtml(XHTMLStream & xs, OutputParams const & op) const
248 {
249         docstring const & ref = getParam("reference");
250         InsetLabel const * il = buffer().insetLabel(ref);
251         string const & cmd = params().getCmdName();
252         docstring display_string;
253
254         if (il && !il->counterValue().empty()) {
255                 // Try to construct a label from the InsetLabel we reference.
256                 docstring const & value = il->counterValue();
257                 if (cmd == "ref")
258                         display_string = value;
259                 else if (cmd == "vref")
260                         // normally, would be "ref on page #", but we have no pages
261                         display_string = value;
262                 else if (cmd == "pageref" || cmd == "vpageref")
263                         // normally would be "on page #", but we have no pages.
264                         display_string = translateIfPossible(from_ascii("elsewhere"),
265                                 op.local_font->language()->lang());
266                 else if (cmd == "eqref")
267                         display_string = '(' + value + ')';
268                 else if (cmd == "formatted") {
269                         display_string = il->prettyCounter();
270                         if (buffer().params().use_refstyle && getParam("caps") == "true")
271                                 capitalize(display_string);
272                         // it is hard to see what to do about plurals...
273                 }
274                 else if (cmd == "nameref")
275                         // FIXME We don't really have the ability to handle these
276                         // properly in XHTML output yet (bug #8599).
277                         // It might not be that hard to do. We have the InsetLabel,
278                         // and we can presumably find its paragraph using the TOC.
279                         // But the label might be referencing a section, yet not be
280                         // in that section. So this is not trivial.
281                         display_string = il->prettyCounter();
282         } else 
283                         display_string = ref;
284
285         // FIXME What we'd really like to do is to be able to output some
286         // appropriate sort of text here. But to do that, we need to associate
287         // some sort of counter with the label, and we don't have that yet.
288         docstring const attr = "href=\"#" + html::cleanAttr(ref) + '"';
289         xs << html::StartTag("a", to_utf8(attr));
290         xs << display_string;
291         xs << html::EndTag("a");
292         return docstring();
293 }
294
295
296 void InsetRef::toString(odocstream & os) const
297 {
298         odocstringstream ods;
299         plaintext(ods, OutputParams(0));
300         os << ods.str();
301 }
302
303
304 void InsetRef::forOutliner(docstring & os, size_t const, bool const) const
305 {
306         // There's no need for details in the TOC, and a long label
307         // will just get in the way.
308         os += '#';
309 }
310
311
312 void InsetRef::updateBuffer(ParIterator const & it, UpdateType)
313 {
314         docstring const & ref = getParam("reference");
315         // register this inset into the buffer reference cache.
316         buffer().addReference(ref, this, it);
317
318         docstring label;
319         string const & cmd = getCmdName();
320         for (int i = 0; !types[i].latex_name.empty(); ++i) {
321                 if (cmd == types[i].latex_name) {
322                         label = _(types[i].short_gui_name);
323                         break;
324                 }
325         }
326
327         if (cmd != "labelonly")
328                 label += ref;
329         else {
330                 if (getParam("noprefix") != "true")
331                         label += ref;
332                 else {
333                         docstring prefix;
334                         docstring suffix = split(ref, prefix, ':');
335                         if (suffix.empty()) {
336                                 label += ref;
337                         } else {
338                                 label += suffix;
339                         }
340                 }
341         }
342         
343         if (!buffer().params().isLatex() && !getParam("name").empty()) {
344                 label += "||";
345                 label += getParam("name");
346         }
347         
348         unsigned int const maxLabelChars = 24;
349         if (label.size() > maxLabelChars) {
350                 tooltip_ = label;
351                 support::truncateWithEllipsis(label, maxLabelChars);
352         } else
353                 tooltip_ = from_ascii("");
354         screen_label_ = label;
355 }
356
357
358 void InsetRef::addToToc(DocIterator const & cpit, bool output_active,
359                                                 UpdateType) const
360 {
361         docstring const & label = getParam("reference");
362         if (buffer().insetLabel(label))
363                 // This InsetRef has already been taken care of in InsetLabel::addToToc().
364                 return;
365
366         // It seems that this reference does not point to any valid label.
367         screen_label_ = _("BROKEN: ") + screen_label_;
368         shared_ptr<Toc> toc = buffer().tocBackend().toc("label");
369         toc->push_back(TocItem(cpit, 0, screen_label_, output_active));
370 }
371
372
373 void InsetRef::validate(LaTeXFeatures & features) const
374 {
375         string const cmd = getCmdName();
376         if (cmd == "vref" || cmd == "vpageref")
377                 features.require("varioref");
378         else if (cmd == "formatted") {
379                 docstring const data = getEscapedLabel(features.runparams());
380                 docstring label;
381                 docstring prefix;
382                 docstring const fcmd = 
383                         getFormattedCmd(data, label, prefix, getParam("caps"));
384                 if (buffer().params().use_refstyle) {
385                         features.require("refstyle");
386                         if (prefix == "cha")
387                                 features.addPreambleSnippet(from_ascii("\\let\\charef=\\chapref"));
388                         else if (!prefix.empty()) {
389                                 docstring lcmd = "\\AtBeginDocument{\\providecommand" +
390                                                 fcmd + "[1]{\\ref{" + prefix + ":#1}}}";
391                                 features.addPreambleSnippet(lcmd);
392                         }
393                 } else {
394                         features.require("prettyref");
395                         // prettyref uses "cha" for chapters, so we provide a kind of
396                         // translation.
397                         if (prefix == "chap")
398                                 features.addPreambleSnippet(from_ascii("\\let\\pr@chap=\\pr@cha"));
399                 }
400         } else if (cmd == "eqref" && !buffer().params().use_refstyle)
401                 // with refstyle, we simply output "(\ref{label})"
402                 features.require("amsmath");
403         else if (cmd == "nameref")
404                 features.require("nameref");
405 }
406
407
408 InsetRef::type_info const InsetRef::types[] = {
409         { "ref",       N_("Standard"),              N_("Ref: ")},
410         { "eqref",     N_("Equation"),              N_("EqRef: ")},
411         { "pageref",   N_("Page Number"),           N_("Page: ")},
412         { "vpageref",  N_("Textual Page Number"),   N_("TextPage: ")},
413         { "vref",      N_("Standard+Textual Page"), N_("Ref+Text: ")},
414         { "formatted", N_("Formatted"),             N_("Format: ")},
415         { "nameref",   N_("Reference to Name"),     N_("NameRef: ")},
416         { "labelonly", N_("Label Only"),            N_("Label: ")},
417         { "", "", "" }
418 };
419
420
421 int InsetRef::getType(string const & name)
422 {
423         for (int i = 0; !types[i].latex_name.empty(); ++i)
424                 if (name == types[i].latex_name)
425                         return i;
426         return 0;
427 }
428
429
430 string const & InsetRef::getName(int type)
431 {
432         return types[type].latex_name;
433 }
434
435
436 docstring InsetRef::getTOCString() const 
437 {
438         return tooltip_.empty() ? screen_label_ : tooltip_;
439 }
440
441 } // namespace lyx