]> git.lyx.org Git - lyx.git/blob - src/Counters.cpp
Avoid full metrics computation with Update:FitCursor
[lyx.git] / src / Counters.cpp
1 /**
2  * \file Counters.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Martin Vermeer
8  * \author André Pönitz
9  * \author Richard Kimberly Heck (roman numerals)
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "Counters.h"
17 #include "Layout.h"
18
19 #include "support/convert.h"
20 #include "support/counter_reps.h"
21 #include "support/debug.h"
22 #include "support/docstring.h"
23 #include "support/gettext.h"
24 #include "support/lassert.h"
25 #include "support/Lexer.h"
26 #include "support/lstrings.h"
27
28 #include <algorithm>
29 #include <sstream>
30
31 using namespace std;
32 using namespace lyx::support;
33
34 namespace lyx {
35
36
37 Counter::Counter()
38         : initial_value_(0), saved_value_(0)
39 {
40         reset();
41 }
42
43
44 Counter::Counter(docstring const & mc, docstring const & ls,
45                 docstring const & lsa, docstring const & prettyformat,
46                 docstring const & guiname)
47         : initial_value_(0), saved_value_(0), parent_(mc), labelstring_(ls),
48           labelstringappendix_(lsa), prettyformat_(prettyformat), guiname_(guiname)
49 {
50         reset();
51 }
52
53
54 bool Counter::read(Lexer & lex)
55 {
56         enum {
57                 CT_WITHIN = 1,
58                 CT_LABELSTRING,
59                 CT_LABELSTRING_APPENDIX,
60                 CT_PRETTYFORMAT,
61                 CT_INITIALVALUE,
62                 CT_GUINAME,
63                 CT_LATEXNAME,
64                 CT_REFFORMAT,
65                 CT_END
66         };
67
68         LexerKeyword counterTags[] = {
69                 { "end", CT_END },
70                 { "guiname", CT_GUINAME },
71                 { "initialvalue", CT_INITIALVALUE},
72                 { "labelstring", CT_LABELSTRING },
73                 { "labelstringappendix", CT_LABELSTRING_APPENDIX },
74                 { "latexname", CT_LATEXNAME },
75                 { "prettyformat", CT_PRETTYFORMAT },
76                 { "refformat", CT_REFFORMAT },
77                 { "within", CT_WITHIN }
78         };
79
80         lex.pushTable(counterTags);
81
82         bool getout = false;
83         while (!getout && lex.isOK()) {
84                 int le = lex.lex();
85                 switch (le) {
86                         case Lexer::LEX_UNDEF:
87                                 lex.printError("Unknown counter tag `$$Token'");
88                                 continue;
89                         default:
90                                 break;
91                 }
92                 switch (le) {
93                         case CT_WITHIN:
94                                 lex.next();
95                                 parent_ = lex.getDocString();
96                                 if (parent_ == "none")
97                                         parent_.erase();
98                                 break;
99                         case CT_INITIALVALUE:
100                                 lex.next();
101                                 initial_value_ = lex.getInteger();
102                                 // getInteger() returns -1 on error, and larger
103                                 // negative values do not make much sense.
104                                 // In the other case, we subtract one, since the
105                                 // counter will be incremented before its first use.
106                                 if (initial_value_ <= -1)
107                                         initial_value_ = 0;
108                                 else
109                                         initial_value_ -= 1;
110                                 break;
111                         case CT_PRETTYFORMAT:
112                                 lex.next();
113                                 prettyformat_ = lex.getDocString();
114                                 break;
115                         case CT_REFFORMAT: {
116                                 lex.next();
117                                 docstring const key = lex.getDocString();
118                                 lex.next();
119                                 docstring const value = lex.getDocString();
120                                 ref_formats_[key] = value;
121                                 // LYXERR0("refformat: " << key << " => " << value);
122                                 break;
123                         }
124                         case CT_LABELSTRING:
125                                 lex.next();
126                                 labelstring_ = lex.getDocString();
127                                 labelstringappendix_ = labelstring_;
128                                 break;
129                         case CT_LABELSTRING_APPENDIX:
130                                 lex.next();
131                                 labelstringappendix_ = lex.getDocString();
132                                 break;
133                         case CT_GUINAME:
134                                 lex.next();
135                                 guiname_ = lex.getDocString();
136                                 break;
137                         case CT_LATEXNAME:
138                                 lex.next();
139                                 latexname_ = lex.getDocString();
140                                 break;
141                         case CT_END:
142                                 getout = true;
143                                 break;
144                 }
145                 // fall back on GuiName if PrettyFormat is empty
146                 if (prettyformat_.empty()) {
147                         if (guiname_.empty())
148                                 prettyformat_ = from_ascii("##");
149                         else
150                                 prettyformat_ = "## (" + guiname_ + ")";
151                 }
152         }
153
154         // Here if have a full counter if getout == true
155         if (!getout)
156                 LYXERR0("No End tag found for counter!");
157         lex.popTable();
158         return getout;
159 }
160
161
162 void Counter::set(int v)
163 {
164         value_ = v;
165 }
166
167
168 void Counter::addto(int v)
169 {
170         value_ += v;
171 }
172
173
174 int Counter::value() const
175 {
176         return value_;
177 }
178
179
180 void Counter::saveValue()
181 {
182         saved_value_ = value_;
183 }
184
185
186 void Counter::restoreValue()
187 {
188         value_ = saved_value_;
189 }
190
191
192 void Counter::step()
193 {
194         ++value_;
195 }
196
197
198 void Counter::reset()
199 {
200         value_ = initial_value_;
201 }
202
203
204 docstring const & Counter::refFormat(docstring const & prefix) const
205 {
206         map<docstring, docstring>::const_iterator it = ref_formats_.find(prefix);
207         if (it == ref_formats_.end())
208                 return prettyformat_;
209         return it->second;
210 }
211
212
213 docstring const & Counter::parent() const
214 {
215         return parent_;
216 }
217
218
219 bool Counter::checkAndRemoveParent(docstring const & cnt)
220 {
221         if (parent_ != cnt)
222                 return false;
223         parent_ = docstring();
224         return true;
225 }
226
227
228 docstring const & Counter::labelString(bool in_appendix) const
229 {
230         return in_appendix ? labelstringappendix_ : labelstring_;
231 }
232
233
234 Counter::StringMap & Counter::flatLabelStrings(bool in_appendix) const
235 {
236         return in_appendix ? flatlabelstringappendix_ : flatlabelstring_;
237 }
238
239
240 Counters::Counters() : appendix_(false), subfloat_(false), longtable_(false)
241 {
242         layout_stack_.push_back(nullptr);
243         counter_stack_.push_back(from_ascii(""));
244 }
245
246
247 void Counters::newCounter(docstring const & newc,
248                           docstring const & parentc,
249                           docstring const & ls,
250                           docstring const & lsa,
251                           docstring const & prettyformat,
252                           docstring const & guiname)
253 {
254         if (!parentc.empty() && !hasCounter(parentc)) {
255                 lyxerr << "Parent counter does not exist: "
256                            << to_utf8(parentc)
257                        << endl;
258                 return;
259         }
260         counterList_[newc] = Counter(parentc, ls, lsa, prettyformat, guiname);
261 }
262
263
264 bool Counters::hasCounter(docstring const & c) const
265 {
266         return counterList_.find(c) != counterList_.end();
267 }
268
269
270 bool Counters::read(Lexer & lex, docstring const & name, bool makenew)
271 {
272         if (hasCounter(name)) {
273                 LYXERR(Debug::TCLASS, "Reading existing counter " << to_utf8(name));
274                 return counterList_[name].read(lex);
275         }
276
277         LYXERR(Debug::TCLASS, "Reading new counter " << to_utf8(name));
278         Counter cnt;
279         bool success = cnt.read(lex);
280         // if makenew is false, we will just discard what we read
281         if (success && makenew)
282                 counterList_[name] = cnt;
283         else if (!success)
284                 LYXERR0("Error reading counter `" << name << "'!");
285         return success;
286 }
287
288
289 void Counters::set(docstring const & ctr, int const val)
290 {
291         CounterList::iterator const it = counterList_.find(ctr);
292         if (it == counterList_.end()) {
293                 lyxerr << "set: Counter does not exist: "
294                        << to_utf8(ctr) << endl;
295                 return;
296         }
297         it->second.set(val);
298 }
299
300
301 void Counters::addto(docstring const & ctr, int const val)
302 {
303         CounterList::iterator const it = counterList_.find(ctr);
304         if (it == counterList_.end()) {
305                 lyxerr << "addto: Counter does not exist: "
306                        << to_utf8(ctr) << endl;
307                 return;
308         }
309         it->second.addto(val);
310 }
311
312
313 int Counters::value(docstring const & ctr) const
314 {
315         CounterList::const_iterator const cit = counterList_.find(ctr);
316         if (cit == counterList_.end()) {
317                 lyxerr << "value: Counter does not exist: "
318                        << to_utf8(ctr) << endl;
319                 return 0;
320         }
321         return cit->second.value();
322 }
323
324
325 void Counters::saveValue(docstring const & ctr) const
326 {
327         CounterList::const_iterator const cit = counterList_.find(ctr);
328         if (cit == counterList_.end()) {
329                 lyxerr << "value: Counter does not exist: "
330                        << to_utf8(ctr) << endl;
331                 return;
332         }
333         Counter const & cnt = cit->second;
334         Counter & ccnt = const_cast<Counter &>(cnt);
335         ccnt.saveValue();
336 }
337
338
339 void Counters::restoreValue(docstring const & ctr) const
340 {
341         CounterList::const_iterator const cit = counterList_.find(ctr);
342         if (cit == counterList_.end()) {
343                 lyxerr << "value: Counter does not exist: "
344                        << to_utf8(ctr) << endl;
345                 return;
346         }
347         Counter const & cnt = cit->second;
348         Counter & ccnt = const_cast<Counter &>(cnt);
349         ccnt.restoreValue();
350 }
351
352
353 void Counters::resetChildren(docstring const & count)
354 {
355         for (auto & ctr : counterList_) {
356                 if (ctr.second.parent() == count) {
357                         ctr.second.reset();
358                         resetChildren(ctr.first);
359                 }
360         }
361 }
362
363
364 void Counters::stepParent(docstring const & ctr, UpdateType utype)
365 {
366         CounterList::iterator it = counterList_.find(ctr);
367         if (it == counterList_.end()) {
368                 lyxerr << "step: Counter does not exist: "
369                        << to_utf8(ctr) << endl;
370                 return;
371         }
372         step(it->second.parent(), utype);
373 }
374
375
376 void Counters::step(docstring const & ctr, UpdateType /* deleted */)
377 {
378         CounterList::iterator it = counterList_.find(ctr);
379         if (it == counterList_.end()) {
380                 lyxerr << "step: Counter does not exist: "
381                        << to_utf8(ctr) << endl;
382                 return;
383         }
384
385         it->second.step();
386         LBUFERR(!counter_stack_.empty());
387         counter_stack_.pop_back();
388         counter_stack_.push_back(ctr);
389
390         resetChildren(ctr);
391 }
392
393
394 docstring const & Counters::guiName(docstring const & cntr) const
395 {
396         CounterList::const_iterator it = counterList_.find(cntr);
397         if (it == counterList_.end()) {
398                 lyxerr << "step: Counter does not exist: "
399                            << to_utf8(cntr) << endl;
400                 return empty_docstring();
401         }
402
403         docstring const & guiname = it->second.guiName();
404         if (guiname.empty())
405                 return cntr;
406         return guiname;
407 }
408
409
410 docstring const & Counters::latexName(docstring const & cntr) const
411 {
412         CounterList::const_iterator it = counterList_.find(cntr);
413         if (it == counterList_.end()) {
414                 lyxerr << "step: Counter does not exist: "
415                            << to_utf8(cntr) << endl;
416                 return empty_docstring();
417         }
418
419         docstring const & latexname = it->second.latexName();
420         if (latexname.empty())
421                 return cntr;
422         return latexname;
423 }
424
425
426 void Counters::reset()
427 {
428         appendix_ = false;
429         subfloat_ = false;
430         current_float_.erase();
431         for (auto & ctr : counterList_)
432                 ctr.second.reset();
433         counter_stack_.clear();
434         counter_stack_.push_back(from_ascii(""));
435         layout_stack_.clear();
436         layout_stack_.push_back(nullptr);
437 }
438
439
440 void Counters::reset(docstring const & match)
441 {
442         LASSERT(!match.empty(), return);
443
444         for (auto & ctr : counterList_) {
445                 if (ctr.first.find(match) != string::npos)
446                         ctr.second.reset();
447         }
448 }
449
450
451 bool Counters::copy(docstring const & cnt, docstring const & newcnt)
452 {
453         auto const it = counterList_.find(cnt);
454         if (it == counterList_.end())
455                 return false;
456         counterList_[newcnt] = it->second;
457         return true;
458 }
459
460
461 bool Counters::remove(docstring const & cnt)
462 {
463         bool retval = counterList_.erase(cnt);
464         if (!retval)
465                 return false;
466         for (auto & ctr : counterList_) {
467                 if (ctr.second.checkAndRemoveParent(cnt))
468                         LYXERR(Debug::TCLASS, "Removed parent counter `" +
469                                         to_utf8(cnt) + "' from counter: " + to_utf8(ctr.first));
470         }
471         return retval;
472 }
473
474
475 docstring Counters::labelItem(docstring const & ctr,
476                               docstring const & numbertype) const
477 {
478         CounterList::const_iterator const cit = counterList_.find(ctr);
479         if (cit == counterList_.end()) {
480                 lyxerr << "Counter "
481                        << to_utf8(ctr)
482                        << " does not exist." << endl;
483                 return docstring();
484         }
485
486         int val = cit->second.value();
487
488         if (numbertype == "hebrew")
489                 return docstring(1, hebrewCounter(val));
490
491         if (numbertype == "alph")
492                 return docstring(1, loweralphaCounter(val));
493
494         if (numbertype == "Alph")
495                 return docstring(1, alphaCounter(val));
496
497         if (numbertype == "roman")
498                 return lowerromanCounter(val);
499
500         if (numbertype == "Roman")
501                 return romanCounter(val);
502
503         if (numbertype == "fnsymbol")
504                 return fnsymbolCounter(val);
505
506         if (numbertype == "superarabic")
507                 return superarabicCounter(val);
508
509         return convert<docstring>(val);
510 }
511
512
513 docstring Counters::theCounter(docstring const & counter,
514                                string const & lang) const
515 {
516         CounterList::const_iterator it = counterList_.find(counter);
517         if (it == counterList_.end())
518                 return from_ascii("#");
519         Counter const & ctr = it->second;
520         Counter::StringMap & sm = ctr.flatLabelStrings(appendix());
521         Counter::StringMap::iterator smit = sm.find(lang);
522         if (smit != sm.end())
523                 return counterLabel(smit->second, lang);
524
525         vector<docstring> callers;
526         docstring const & fls = flattenLabelString(counter, appendix(),
527                                                    lang, callers);
528         sm[lang] = fls;
529         return counterLabel(fls, lang);
530 }
531
532
533 docstring Counters::flattenLabelString(docstring const & counter,
534                                        bool in_appendix,
535                                        string const & lang,
536                                        vector<docstring> & callers) const
537 {
538         if (find(callers.begin(), callers.end(), counter) != callers.end()) {
539                 // recursion detected
540                 lyxerr << "Warning: Recursion in label for counter `"
541                        << counter << "' detected"
542                        << endl;
543                 return from_ascii("??");
544         }
545
546         CounterList::const_iterator it = counterList_.find(counter);
547         if (it == counterList_.end())
548                 return from_ascii("#");
549         Counter const & c = it->second;
550
551         docstring ls = translateIfPossible(c.labelString(in_appendix), lang);
552
553         callers.push_back(counter);
554         if (ls.empty()) {
555                 if (!c.parent().empty())
556                         ls = flattenLabelString(c.parent(), in_appendix, lang, callers)
557                                 + from_ascii(".");
558                 callers.pop_back();
559                 return ls + from_ascii("\\arabic{") + counter + "}";
560         }
561
562         while (true) {
563                 //lyxerr << "ls=" << to_utf8(ls) << endl;
564                 size_t const i = ls.find(from_ascii("\\the"), 0);
565                 if (i == docstring::npos)
566                         break;
567                 size_t const j = i + 4;
568                 size_t k = j;
569                 while (k < ls.size() && lowercase(ls[k]) >= 'a'
570                        && lowercase(ls[k]) <= 'z')
571                         ++k;
572                 docstring const newc = ls.substr(j, k - j);
573                 docstring const repl = flattenLabelString(newc, in_appendix,
574                                                           lang, callers);
575                 ls.replace(i, k - j + 4, repl);
576         }
577         callers.pop_back();
578
579         return ls;
580 }
581
582
583 docstring Counters::counterLabel(docstring const & format,
584                                  string const & lang) const
585 {
586         docstring label = format;
587
588         // FIXME: Using regexps would be better, but we compile boost without
589         // wide regexps currently.
590         docstring const the = from_ascii("\\the");
591         while (true) {
592                 //lyxerr << "label=" << label << endl;
593                 size_t const i = label.find(the, 0);
594                 if (i == docstring::npos)
595                         break;
596                 size_t const j = i + 4;
597                 size_t k = j;
598                 while (k < label.size() && lowercase(label[k]) >= 'a'
599                        && lowercase(label[k]) <= 'z')
600                         ++k;
601                 docstring const newc(label, j, k - j);
602                 label.replace(i, k - i, theCounter(newc, lang));
603         }
604         while (true) {
605                 //lyxerr << "label=" << label << endl;
606
607                 size_t const i = label.find('\\', 0);
608                 if (i == docstring::npos)
609                         break;
610                 size_t const j = label.find('{', i + 1);
611                 if (j == docstring::npos)
612                         break;
613                 size_t const k = label.find('}', j + 1);
614                 if (k == docstring::npos)
615                         break;
616                 docstring const numbertype(label, i + 1, j - i - 1);
617                 docstring const counter(label, j + 1, k - j - 1);
618                 label.replace(i, k + 1 - i, labelItem(counter, numbertype));
619         }
620         //lyxerr << "DONE! label=" << label << endl;
621         return label;
622 }
623
624
625 docstring Counters::formattedCounter(docstring const & name,
626                         docstring const & prex, string const & lang) const
627 {
628         CounterList::const_iterator it = counterList_.find(name);
629         if (it == counterList_.end())
630                 return from_ascii("#");
631         Counter const & ctr = it->second;
632
633         docstring const value = theCounter(name, lang);
634         docstring const format =
635                 translateIfPossible(counterLabel(ctr.refFormat(prex), lang), lang);
636         if (format.empty())
637                 return value;
638         return subst(format, from_ascii("##"), value);
639 }
640
641
642 docstring Counters::prettyCounter(docstring const & name,
643                                string const & lang) const
644 {
645         CounterList::const_iterator it = counterList_.find(name);
646         if (it == counterList_.end())
647                 return from_ascii("#");
648         Counter const & ctr = it->second;
649
650         docstring const value = theCounter(name, lang);
651         docstring const & format =
652                 translateIfPossible(counterLabel(ctr.prettyFormat(), lang), lang);
653         if (format.empty())
654                 return value;
655         return subst(format, from_ascii("##"), value);
656 }
657
658
659 docstring Counters::currentCounter() const
660 {
661         LBUFERR(!counter_stack_.empty());
662         return counter_stack_.back();
663 }
664
665
666 void Counters::setActiveLayout(Layout const & lay)
667 {
668         LASSERT(!layout_stack_.empty(), return);
669         Layout const * const lastlay = layout_stack_.back();
670         // we want to check whether the layout has changed and, if so,
671         // whether we are coming out of or going into an environment.
672         if (!lastlay) {
673                 layout_stack_.pop_back();
674                 layout_stack_.push_back(&lay);
675                 if (lay.isEnvironment())
676                         beginEnvironment();
677         } else if (lastlay->name() != lay.name()) {
678                 layout_stack_.pop_back();
679                 layout_stack_.push_back(&lay);
680                 if (lastlay->isEnvironment()) {
681                         // we are coming out of an environment
682                         // LYXERR0("Out: " << lastlay->name());
683                         endEnvironment();
684                 }
685                 if (lay.isEnvironment()) {
686                         // we are going into a new environment
687                         // LYXERR0("In: " << lay.name());
688                         beginEnvironment();
689                 }
690         }
691 }
692
693
694 void Counters::beginEnvironment()
695 {
696         counter_stack_.push_back(counter_stack_.back());
697 }
698
699
700 void Counters::endEnvironment()
701 {
702         LASSERT(!counter_stack_.empty(), return);
703         counter_stack_.pop_back();
704 }
705
706
707 vector<docstring> Counters::listOfCounters() const {
708         vector<docstring> ret;
709         for(auto const & k : counterList_)
710                 ret.emplace_back(k.first);
711         return ret;
712 }
713
714
715 } // namespace lyx