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