]> git.lyx.org Git - lyx.git/blob - src/Counters.cpp
Fix bug #12772
[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         return convert<docstring>(val);
480 }
481
482
483 docstring Counters::theCounter(docstring const & counter,
484                                string const & lang) const
485 {
486         CounterList::const_iterator it = counterList_.find(counter);
487         if (it == counterList_.end())
488                 return from_ascii("#");
489         Counter const & ctr = it->second;
490         Counter::StringMap & sm = ctr.flatLabelStrings(appendix());
491         Counter::StringMap::iterator smit = sm.find(lang);
492         if (smit != sm.end())
493                 return counterLabel(smit->second, lang);
494
495         vector<docstring> callers;
496         docstring const & fls = flattenLabelString(counter, appendix(),
497                                                    lang, callers);
498         sm[lang] = fls;
499         return counterLabel(fls, lang);
500 }
501
502
503 docstring Counters::flattenLabelString(docstring const & counter,
504                                        bool in_appendix,
505                                        string const & lang,
506                                        vector<docstring> & callers) const
507 {
508         if (find(callers.begin(), callers.end(), counter) != callers.end()) {
509                 // recursion detected
510                 lyxerr << "Warning: Recursion in label for counter `"
511                        << counter << "' detected"
512                        << endl;
513                 return from_ascii("??");
514         }
515
516         CounterList::const_iterator it = counterList_.find(counter);
517         if (it == counterList_.end())
518                 return from_ascii("#");
519         Counter const & c = it->second;
520
521         docstring ls = translateIfPossible(c.labelString(in_appendix), lang);
522
523         callers.push_back(counter);
524         if (ls.empty()) {
525                 if (!c.parent().empty())
526                         ls = flattenLabelString(c.parent(), in_appendix, lang, callers)
527                                 + from_ascii(".");
528                 callers.pop_back();
529                 return ls + from_ascii("\\arabic{") + counter + "}";
530         }
531
532         while (true) {
533                 //lyxerr << "ls=" << to_utf8(ls) << endl;
534                 size_t const i = ls.find(from_ascii("\\the"), 0);
535                 if (i == docstring::npos)
536                         break;
537                 size_t const j = i + 4;
538                 size_t k = j;
539                 while (k < ls.size() && lowercase(ls[k]) >= 'a'
540                        && lowercase(ls[k]) <= 'z')
541                         ++k;
542                 docstring const newc = ls.substr(j, k - j);
543                 docstring const repl = flattenLabelString(newc, in_appendix,
544                                                           lang, callers);
545                 ls.replace(i, k - j + 4, repl);
546         }
547         callers.pop_back();
548
549         return ls;
550 }
551
552
553 docstring Counters::counterLabel(docstring const & format,
554                                  string const & lang) const
555 {
556         docstring label = format;
557
558         // FIXME: Using regexps would be better, but we compile boost without
559         // wide regexps currently.
560         docstring const the = from_ascii("\\the");
561         while (true) {
562                 //lyxerr << "label=" << label << endl;
563                 size_t const i = label.find(the, 0);
564                 if (i == docstring::npos)
565                         break;
566                 size_t const j = i + 4;
567                 size_t k = j;
568                 while (k < label.size() && lowercase(label[k]) >= 'a'
569                        && lowercase(label[k]) <= 'z')
570                         ++k;
571                 docstring const newc(label, j, k - j);
572                 label.replace(i, k - i, theCounter(newc, lang));
573         }
574         while (true) {
575                 //lyxerr << "label=" << label << endl;
576
577                 size_t const i = label.find('\\', 0);
578                 if (i == docstring::npos)
579                         break;
580                 size_t const j = label.find('{', i + 1);
581                 if (j == docstring::npos)
582                         break;
583                 size_t const k = label.find('}', j + 1);
584                 if (k == docstring::npos)
585                         break;
586                 docstring const numbertype(label, i + 1, j - i - 1);
587                 docstring const counter(label, j + 1, k - j - 1);
588                 label.replace(i, k + 1 - i, labelItem(counter, numbertype));
589         }
590         //lyxerr << "DONE! label=" << label << endl;
591         return label;
592 }
593
594
595 docstring Counters::prettyCounter(docstring const & name,
596                                string const & lang) const
597 {
598         CounterList::const_iterator it = counterList_.find(name);
599         if (it == counterList_.end())
600                 return from_ascii("#");
601         Counter const & ctr = it->second;
602
603         docstring const value = theCounter(name, lang);
604         docstring const & format =
605             translateIfPossible(ctr.prettyFormat(), lang);
606         if (format.empty())
607                 return value;
608         return subst(format, from_ascii("##"), value);
609 }
610
611
612 docstring Counters::currentCounter() const
613 {
614         LBUFERR(!counter_stack_.empty());
615         return counter_stack_.back();
616 }
617
618
619 void Counters::setActiveLayout(Layout const & lay)
620 {
621         LASSERT(!layout_stack_.empty(), return);
622         Layout const * const lastlay = layout_stack_.back();
623         // we want to check whether the layout has changed and, if so,
624         // whether we are coming out of or going into an environment.
625         if (!lastlay) {
626                 layout_stack_.pop_back();
627                 layout_stack_.push_back(&lay);
628                 if (lay.isEnvironment())
629                         beginEnvironment();
630         } else if (lastlay->name() != lay.name()) {
631                 layout_stack_.pop_back();
632                 layout_stack_.push_back(&lay);
633                 if (lastlay->isEnvironment()) {
634                         // we are coming out of an environment
635                         // LYXERR0("Out: " << lastlay->name());
636                         endEnvironment();
637                 }
638                 if (lay.isEnvironment()) {
639                         // we are going into a new environment
640                         // LYXERR0("In: " << lay.name());
641                         beginEnvironment();
642                 }
643         }
644 }
645
646
647 void Counters::beginEnvironment()
648 {
649         counter_stack_.push_back(counter_stack_.back());
650 }
651
652
653 void Counters::endEnvironment()
654 {
655         LASSERT(!counter_stack_.empty(), return);
656         counter_stack_.pop_back();
657 }
658
659
660 vector<docstring> Counters::listOfCounters() const {
661         vector<docstring> ret;
662         for(auto const & k : counterList_)
663                 ret.emplace_back(k.first);
664         return ret;
665 }
666
667
668 } // namespace lyx