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