]> git.lyx.org Git - lyx.git/blob - src/Counters.cpp
Remember navigate back bookmark through sessions
[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 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/debug.h"
22 #include "support/gettext.h"
23 #include "support/lassert.h"
24 #include "support/lstrings.h"
25
26 #include <algorithm>
27 #include <sstream>
28
29 using namespace std;
30 using namespace lyx::support;
31
32 namespace lyx {
33
34
35 Counter::Counter()
36 {
37         reset();
38 }
39
40
41 Counter::Counter(docstring const & mc, docstring const & ls,  
42                 docstring const & lsa)
43         : master_(mc), labelstring_(ls), labelstringappendix_(lsa)
44 {
45         reset();
46 }
47
48
49 bool Counter::read(Lexer & lex)
50 {
51         enum {
52                 CT_WITHIN = 1,
53                 CT_LABELSTRING,
54                 CT_LABELSTRING_APPENDIX,
55                 CT_PRETTYFORMAT,
56                 CT_END
57         };
58
59         LexerKeyword counterTags[] = {
60                 { "end", CT_END },
61                 { "labelstring", CT_LABELSTRING },
62                 { "labelstringappendix", CT_LABELSTRING_APPENDIX },
63                 { "prettyformat", CT_PRETTYFORMAT },
64                 { "within", CT_WITHIN }
65         };
66
67         lex.pushTable(counterTags);
68
69         bool getout = false;
70         while (!getout && lex.isOK()) {
71                 int le = lex.lex();
72                 switch (le) {
73                         case Lexer::LEX_UNDEF:
74                                 lex.printError("Unknown counter tag `$$Token'");
75                                 continue;
76                         default: 
77                                 break;
78                 }
79                 switch (le) {
80                         case CT_WITHIN:
81                                 lex.next();
82                                 master_ = lex.getDocString();
83                                 if (master_ == "none")
84                                         master_.erase();
85                                 break;
86                         case CT_PRETTYFORMAT:
87                                 lex.next();
88                                 prettyformat_ = lex.getDocString();
89                                 break;
90                         case CT_LABELSTRING:
91                                 lex.next();
92                                 labelstring_ = lex.getDocString();
93                                 labelstringappendix_ = labelstring_;
94                                 break;
95                         case CT_LABELSTRING_APPENDIX:
96                                 lex.next();
97                                 labelstringappendix_ = lex.getDocString();
98                                 break;
99                         case CT_END:
100                                 getout = true;
101                                 break;
102                 }
103         }
104
105         // Here if have a full counter if getout == true
106         if (!getout)
107                 LYXERR0("No End tag found for counter!");
108         lex.popTable();
109         return getout;
110 }
111
112 void Counter::set(int v)
113 {
114         value_ = v;
115 }
116
117
118 void Counter::addto(int v)
119 {
120         value_ += v;
121 }
122
123
124 int Counter::value() const
125 {
126         return value_;
127 }
128
129
130 void Counter::step()
131 {
132         ++value_;
133 }
134
135
136 void Counter::reset()
137 {
138         value_ = 0;
139 }
140
141
142 docstring const & Counter::master() const
143 {
144         return master_;
145 }
146
147
148 docstring const & Counter::labelString(bool in_appendix) const
149 {
150         return in_appendix ? labelstringappendix_ : labelstring_;
151 }
152
153
154 Counter::StringMap & Counter::flatLabelStrings(bool in_appendix) const
155 {
156         return in_appendix ? flatlabelstringappendix_ : flatlabelstring_;
157 }
158
159
160 void Counters::newCounter(docstring const & newc,
161                           docstring const & masterc, 
162                           docstring const & ls,
163                           docstring const & lsa)
164 {
165         if (!masterc.empty() && !hasCounter(masterc)) {
166                 lyxerr << "Master counter does not exist: "
167                        << to_utf8(masterc)
168                        << endl;
169                 return;
170         }
171         counterList_[newc] = Counter(masterc, ls, lsa);
172 }
173
174
175 bool Counters::hasCounter(docstring const & c) const
176 {
177         return counterList_.find(c) != counterList_.end();
178 }
179
180
181 bool Counters::read(Lexer & lex, docstring const & name, bool makenew)
182 {
183         if (hasCounter(name)) {
184                 LYXERR(Debug::TCLASS, "Reading existing counter " << to_utf8(name));
185                 return counterList_[name].read(lex);
186         }
187
188         LYXERR(Debug::TCLASS, "Reading new counter " << to_utf8(name));
189         Counter cnt;
190         bool success = cnt.read(lex);
191         // if makenew is false, we will just discard what we read
192         if (success && makenew)
193                 counterList_[name] = cnt;
194         else if (!success)
195                 LYXERR0("Error reading counter `" << name << "'!");
196         return success;
197 }
198
199
200 void Counters::set(docstring const & ctr, int const val)
201 {
202         CounterList::iterator const it = counterList_.find(ctr);
203         if (it == counterList_.end()) {
204                 lyxerr << "set: Counter does not exist: "
205                        << to_utf8(ctr) << endl;
206                 return;
207         }
208         it->second.set(val);
209 }
210
211
212 void Counters::addto(docstring const & ctr, int const val)
213 {
214         CounterList::iterator const it = counterList_.find(ctr);
215         if (it == counterList_.end()) {
216                 lyxerr << "addto: Counter does not exist: "
217                        << to_utf8(ctr) << endl;
218                 return;
219         }
220         it->second.addto(val);
221 }
222
223
224 int Counters::value(docstring const & ctr) const
225 {
226         CounterList::const_iterator const cit = counterList_.find(ctr);
227         if (cit == counterList_.end()) {
228                 lyxerr << "value: Counter does not exist: "
229                        << to_utf8(ctr) << endl;
230                 return 0;
231         }
232         return cit->second.value();
233 }
234
235
236 void Counters::step(docstring const & ctr, UpdateType utype)
237 {
238         CounterList::iterator it = counterList_.find(ctr);
239         if (it == counterList_.end()) {
240                 lyxerr << "step: Counter does not exist: "
241                        << to_utf8(ctr) << endl;
242                 return;
243         }
244
245         it->second.step();
246         if (utype == OutputUpdate) {
247                 LASSERT(!counter_stack_.empty(), /* */);
248                 counter_stack_.pop_back();
249                 counter_stack_.push_back(ctr);
250         }
251         it = counterList_.begin();
252         CounterList::iterator const end = counterList_.end();
253         for (; it != end; ++it) {
254                 if (it->second.master() == ctr) {
255                         it->second.reset();
256                 }
257         }
258 }
259
260
261 void Counters::reset()
262 {
263         appendix_ = false;
264         subfloat_ = false;
265         current_float_.erase();
266         CounterList::iterator it = counterList_.begin();
267         CounterList::iterator const end = counterList_.end();
268         for (; it != end; ++it)
269                 it->second.reset();
270         counter_stack_.clear();
271         counter_stack_.push_back(from_ascii(""));
272         layout_stack_.push_back(0);
273 }
274
275
276 void Counters::reset(docstring const & match)
277 {
278         LASSERT(!match.empty(), /**/);
279
280         CounterList::iterator it = counterList_.begin();
281         CounterList::iterator end = counterList_.end();
282         for (; it != end; ++it) {
283                 if (it->first.find(match) != string::npos)
284                         it->second.reset();
285         }
286 }
287
288
289 void Counters::copy(Counters & from, Counters & to, docstring const & match)
290 {
291         CounterList::iterator it = counterList_.begin();
292         CounterList::iterator end = counterList_.end();
293         for (; it != end; ++it) {
294                 if (it->first.find(match) != string::npos || match == "") {
295                         to.set(it->first, from.value(it->first));
296                 }
297         }
298 }
299
300
301 namespace {
302
303 char loweralphaCounter(int const n)
304 {
305         if (n < 1 || n > 26)
306                 return '?';
307         return 'a' + n - 1;
308 }
309
310
311 char alphaCounter(int const n)
312 {
313         if (n < 1 || n > 26)
314                 return '?';
315         return 'A' + n - 1;
316 }
317
318
319 char hebrewCounter(int const n)
320 {
321         static const char hebrew[22] = {
322                 '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8',
323                 '\xe9', '\xeb', '\xec', '\xee', '\xf0', '\xf1', '\xf2', '\xf4', '\xf6',
324                 '\xf7', '\xf8', '\xf9', '\xfa'
325         };
326
327         if (n < 1 || n > 22)
328                 return '?';
329         return hebrew[n - 1];
330 }
331
332
333
334 // On the special cases, see http://mathworld.wolfram.com/RomanNumerals.html
335 // and for a list of roman numerals up to and including 3999, see 
336 // http://www.research.att.com/~njas/sequences/a006968.txt. (Thanks to Joost
337 // for this info.)
338 docstring const romanCounter(int const n)
339 {
340         static char const * const ones[9] = {
341                 "I",   "II",  "III", "IV", "V",
342                 "VI",  "VII", "VIII", "IX"
343         };
344         
345         static char const * const tens[9] = {
346                 "X", "XX", "XXX", "XL", "L",
347                 "LX", "LXX", "LXXX", "XC"
348         };
349         
350         static char const * const hunds[9] = {
351                 "C", "CC", "CCC", "CD", "D",
352                 "DC", "DCC", "DCCC", "CM"
353         };
354         
355         if (n > 1000 || n < 1) 
356                 return from_ascii("??");
357         
358         int val = n;
359         string roman;
360         switch (n) {
361         //special cases
362         case 900: 
363                 roman = "CM";
364                 break;
365         case 400:
366                 roman = "CD";
367                 break;
368         default:
369                 if (val >= 100) {
370                         int hundreds = val / 100;
371                         roman = hunds[hundreds - 1];
372                         val = val % 100;
373                 }
374                 if (val >= 10) {
375                         switch (val) {
376                         //special case
377                         case 90:
378                                 roman = roman + "XC";
379                                 val = 0; //skip next
380                                 break;
381                         default:
382                                 int tensnum = val / 10;
383                                 roman = roman + tens[tensnum - 1];
384                                 val = val % 10;
385                         } // end switch
386                 } // end tens
387                 if (val > 0)
388                         roman = roman + ones[val -1];
389         }
390         return from_ascii(roman);
391 }
392
393
394 docstring const lowerromanCounter(int const n)
395 {
396         return lowercase(romanCounter(n));
397 }
398
399 } // namespace anon
400
401
402 docstring Counters::labelItem(docstring const & ctr,
403                               docstring const & numbertype) const
404 {
405         CounterList::const_iterator const cit = counterList_.find(ctr);
406         if (cit == counterList_.end()) {
407                 lyxerr << "Counter "
408                        << to_utf8(ctr)
409                        << " does not exist." << endl;
410                 return docstring();
411         }
412
413         int val = cit->second.value();
414
415         if (numbertype == "hebrew")
416                 return docstring(1, hebrewCounter(val));
417
418         if (numbertype == "alph")
419                 return docstring(1, loweralphaCounter(val));
420
421         if (numbertype == "Alph")
422                 return docstring(1, alphaCounter(val));
423
424         if (numbertype == "roman")
425                 return lowerromanCounter(val);
426
427         if (numbertype == "Roman")
428                 return romanCounter(val);
429
430         return convert<docstring>(val);
431 }
432
433
434 docstring Counters::theCounter(docstring const & counter,
435                                string const & lang) const
436 {
437         CounterList::const_iterator it = counterList_.find(counter); 
438         if (it == counterList_.end())
439                 return from_ascii("??");
440         Counter const & ctr = it->second;
441         Counter::StringMap & sm = ctr.flatLabelStrings(appendix());
442         Counter::StringMap::iterator smit = sm.find(lang);
443         if (smit != sm.end())
444                 return counterLabel(smit->second, lang);
445
446         vector<docstring> callers;
447         docstring const & fls = flattenLabelString(counter, appendix(),
448                                                    lang, callers);
449         sm[lang] = fls;
450         return counterLabel(fls, lang);
451 }
452
453
454 docstring Counters::flattenLabelString(docstring const & counter, 
455                                        bool in_appendix,
456                                        string const & lang,
457                                        vector<docstring> & callers) const
458 {
459         docstring label;
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 = ctr.prettyFormat();
558         if (format.empty())
559                 return value;
560         return subst(format, from_ascii("##"), value);
561 }
562
563
564 docstring Counters::currentCounter() const
565
566         LASSERT(!counter_stack_.empty(), /* */);
567         return counter_stack_.back(); 
568 }
569
570
571 void Counters::setActiveLayout(Layout const & lay)
572 {
573         LASSERT(!layout_stack_.empty(), return);
574         Layout const * const lastlay = layout_stack_.back();
575         // we want to check whether the layout has changed and, if so,
576         // whether we are coming out of or going into an environment.
577         if (!lastlay) { 
578                 layout_stack_.pop_back();
579                 layout_stack_.push_back(&lay);
580                 if (lay.isEnvironment())
581                         beginEnvironment();
582         } else if (lastlay->name() != lay.name()) {
583                 layout_stack_.pop_back();
584                 layout_stack_.push_back(&lay);
585                 if (lastlay->isEnvironment()) {
586                         // we are coming out of an environment
587                         // LYXERR0("Out: " << lastlay->name());
588                         endEnvironment();
589                 }
590                 if (lay.isEnvironment()) {
591                         // we are going into a new environment
592                         // LYXERR0("In: " << lay.name());
593                         beginEnvironment();
594                 }
595         } 
596 }
597
598
599 void Counters::beginEnvironment()
600 {
601         docstring cnt = counter_stack_.back();
602         counter_stack_.push_back(cnt);
603         deque<docstring>::const_iterator it = counter_stack_.begin();
604         deque<docstring>::const_iterator en = counter_stack_.end();
605 //      docstring d;
606 //      for (; it != en; ++it)
607 //              d += " --> " + *it;
608 //      LYXERR0(counter_stack_.size() << ": " << d);
609 }
610
611
612 void Counters::endEnvironment()
613 {
614         LASSERT(!counter_stack_.empty(), return);
615         counter_stack_.pop_back();
616         deque<docstring>::const_iterator it = counter_stack_.begin();
617         deque<docstring>::const_iterator en = counter_stack_.end();
618 //      docstring d;
619 //      for (; it != en; ++it)
620 //              d += " --> " + *it;
621 //      LYXERR0(counter_stack_.size() << ": " << d);
622 }
623
624
625 } // namespace lyx