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