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