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