]> git.lyx.org Git - lyx.git/blob - src/Counters.cpp
Generate the magic label always. We'll need it other times, when we do
[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 "Lexer.h"
18
19 #include "support/convert.h"
20 #include "support/debug.h"
21 #include "support/gettext.h"
22 #include "support/lassert.h"
23 #include "support/lstrings.h"
24
25 #include <algorithm>
26 #include <sstream>
27
28 using namespace std;
29 using namespace lyx::support;
30
31 namespace lyx {
32
33
34 Counter::Counter()
35 {
36         reset();
37 }
38
39
40 Counter::Counter(docstring const & mc, docstring const & ls, 
41                  docstring const & lsa)
42         : master_(mc), labelstring_(ls), labelstringappendix_(lsa)
43 {
44         reset();
45 }
46
47
48 bool Counter::read(Lexer & lex)
49 {
50         enum {
51                 CT_WITHIN = 1,
52                 CT_LABELSTRING,
53                 CT_LABELSTRING_APPENDIX,
54                 CT_END
55         };
56
57         LexerKeyword counterTags[] = {
58                 { "end", CT_END },
59                 { "labelstring", CT_LABELSTRING },
60                 { "labelstringappendix", CT_LABELSTRING_APPENDIX },
61                 { "within", CT_WITHIN }
62         };
63
64         lex.pushTable(counterTags);
65
66         bool getout = false;
67         while (!getout && lex.isOK()) {
68                 int le = lex.lex();
69                 switch (le) {
70                         case Lexer::LEX_UNDEF:
71                                 lex.printError("Unknown counter tag `$$Token'");
72                                 continue;
73                         default: 
74                                 break;
75                 }
76                 switch (le) {
77                         case CT_WITHIN:
78                                 lex.next();
79                                 master_ = lex.getDocString();
80                                 if (master_ == "none")
81                                         master_.erase();
82                                 break;
83                         case CT_LABELSTRING:
84                                 lex.next();
85                                 labelstring_ = lex.getDocString();
86                                 labelstringappendix_ = labelstring_;
87                                 break;
88                         case CT_LABELSTRING_APPENDIX:
89                                 lex.next();
90                                 labelstringappendix_ = lex.getDocString();
91                                 break;
92                         case CT_END:
93                                 getout = true;
94                                 break;
95                 }
96         }
97
98         // Here if have a full counter if getout == true
99         if (!getout)
100                 LYXERR0("No End tag found for counter!");
101         lex.popTable();
102         return getout;
103 }
104
105 void Counter::set(int v)
106 {
107         value_ = v;
108 }
109
110
111 void Counter::addto(int v)
112 {
113         value_ += v;
114 }
115
116
117 int Counter::value() const
118 {
119         return value_;
120 }
121
122
123 void Counter::step()
124 {
125         ++value_;
126 }
127
128
129 void Counter::reset()
130 {
131         value_ = 0;
132 }
133
134
135 docstring const & Counter::master() const
136 {
137         return master_;
138 }
139
140
141 docstring const & Counter::labelString(bool in_appendix) const
142 {
143         return in_appendix ? labelstringappendix_ : labelstring_;
144 }
145
146
147 Counter::StringMap & Counter::flatLabelStrings(bool in_appendix) const
148 {
149         return in_appendix ? flatlabelstringappendix_ : flatlabelstring_;
150 }
151
152
153 void Counters::newCounter(docstring const & newc,
154                           docstring const & masterc, 
155                           docstring const & ls,
156                           docstring const & lsa)
157 {
158         if (!masterc.empty() && !hasCounter(masterc)) {
159                 lyxerr << "Master counter does not exist: "
160                        << to_utf8(masterc)
161                        << endl;
162                 return;
163         }
164         counterList_[newc] = Counter(masterc, ls, lsa);
165 }
166
167
168 bool Counters::hasCounter(docstring const & c) const
169 {
170         return counterList_.find(c) != counterList_.end();
171 }
172
173
174 bool Counters::read(Lexer & lex, docstring const & name, bool makenew)
175 {
176         if (hasCounter(name)) {
177                 LYXERR(Debug::TCLASS, "Reading existing counter " << to_utf8(name));
178                 return counterList_[name].read(lex);
179         }
180
181         LYXERR(Debug::TCLASS, "Reading new counter " << to_utf8(name));
182         Counter cnt;
183         bool success = cnt.read(lex);
184         // if makenew is false, we will just discard what we read
185         if (success && makenew)
186                 counterList_[name] = cnt;
187         else if (!success)
188                 LYXERR0("Error reading counter `" << name << "'!");
189         return success;
190 }
191
192
193 void Counters::set(docstring const & ctr, int const val)
194 {
195         CounterList::iterator const it = counterList_.find(ctr);
196         if (it == counterList_.end()) {
197                 lyxerr << "set: Counter does not exist: "
198                        << to_utf8(ctr) << endl;
199                 return;
200         }
201         it->second.set(val);
202 }
203
204
205 void Counters::addto(docstring const & ctr, int const val)
206 {
207         CounterList::iterator const it = counterList_.find(ctr);
208         if (it == counterList_.end()) {
209                 lyxerr << "addto: Counter does not exist: "
210                        << to_utf8(ctr) << endl;
211                 return;
212         }
213         it->second.addto(val);
214 }
215
216
217 int Counters::value(docstring const & ctr) const
218 {
219         CounterList::const_iterator const cit = counterList_.find(ctr);
220         if (cit == counterList_.end()) {
221                 lyxerr << "value: Counter does not exist: "
222                        << to_utf8(ctr) << endl;
223                 return 0;
224         }
225         return cit->second.value();
226 }
227
228
229 void Counters::step(docstring const & ctr)
230 {
231         CounterList::iterator it = counterList_.find(ctr);
232         if (it == counterList_.end()) {
233                 lyxerr << "step: Counter does not exist: "
234                        << to_utf8(ctr) << endl;
235                 return;
236         }
237
238         it->second.step();
239         it = counterList_.begin();
240         CounterList::iterator const end = counterList_.end();
241         for (; it != end; ++it) {
242                 if (it->second.master() == ctr) {
243                         it->second.reset();
244                 }
245         }
246 }
247
248
249 void Counters::reset()
250 {
251         appendix_ = false;
252         subfloat_ = false;
253         current_float_.erase();
254         CounterList::iterator it = counterList_.begin();
255         CounterList::iterator const end = counterList_.end();
256         for (; it != end; ++it)
257                 it->second.reset();
258 }
259
260
261 void Counters::reset(docstring const & match)
262 {
263         LASSERT(!match.empty(), /**/);
264
265         CounterList::iterator it = counterList_.begin();
266         CounterList::iterator end = counterList_.end();
267         for (; it != end; ++it) {
268                 if (it->first.find(match) != string::npos)
269                         it->second.reset();
270         }
271 }
272
273
274 void Counters::copy(Counters & from, Counters & to, docstring const & match)
275 {
276         CounterList::iterator it = counterList_.begin();
277         CounterList::iterator end = counterList_.end();
278         for (; it != end; ++it) {
279                 if (it->first.find(match) != string::npos || match == "") {
280                         to.set(it->first, from.value(it->first));
281                 }
282         }
283 }
284
285
286 namespace {
287
288 char loweralphaCounter(int const n)
289 {
290         if (n < 1 || n > 26)
291                 return '?';
292         return 'a' + n - 1;
293 }
294
295
296 char alphaCounter(int const n)
297 {
298         if (n < 1 || n > 26)
299                 return '?';
300         return 'A' + n - 1;
301 }
302
303
304 char hebrewCounter(int const n)
305 {
306         static const char hebrew[22] = {
307                 '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8',
308                 '\xe9', '\xeb', '\xec', '\xee', '\xf0', '\xf1', '\xf2', '\xf4', '\xf6',
309                 '\xf7', '\xf8', '\xf9', '\xfa'
310         };
311
312         if (n < 1 || n > 22)
313                 return '?';
314         return hebrew[n - 1];
315 }
316
317
318
319 // On the special cases, see http://mathworld.wolfram.com/RomanNumerals.html
320 // and for a list of roman numerals up to and including 3999, see 
321 // http://www.research.att.com/~njas/sequences/a006968.txt. (Thanks to Joost
322 // for this info.)
323 docstring const romanCounter(int const n)
324 {
325         static char const * const ones[9] = {
326                 "I",   "II",  "III", "IV", "V",
327                 "VI",  "VII", "VIII", "IX"
328         };
329         
330         static char const * const tens[9] = {
331                 "X", "XX", "XXX", "XL", "L",
332                 "LX", "LXX", "LXXX", "XC"
333         };
334         
335         static char const * const hunds[9] = {
336                 "C", "CC", "CCC", "CD", "D",
337                 "DC", "DCC", "DCCC", "CM"
338         };
339         
340         if (n > 1000 || n < 1) 
341                 return from_ascii("??");
342         
343         int val = n;
344         string roman;
345         switch (n) {
346         //special cases
347         case 900: 
348                 roman = "CM";
349                 break;
350         case 400:
351                 roman = "CD";
352                 break;
353         default:
354                 if (val >= 100) {
355                         int hundreds = val / 100;
356                         roman = hunds[hundreds - 1];
357                         val = val % 100;
358                 }
359                 if (val >= 10) {
360                         switch (val) {
361                         //special case
362                         case 90:
363                                 roman = roman + "XC";
364                                 val = 0; //skip next
365                                 break;
366                         default:
367                                 int tensnum = val / 10;
368                                 roman = roman + tens[tensnum - 1];
369                                 val = val % 10;
370                         } // end switch
371                 } // end tens
372                 if (val > 0)
373                         roman = roman + ones[val -1];
374         }
375         return from_ascii(roman);
376 }
377
378
379 docstring const lowerromanCounter(int const n)
380 {
381         return lowercase(romanCounter(n));
382 }
383
384 } // namespace anon
385
386
387 docstring Counters::labelItem(docstring const & ctr,
388                               docstring const & numbertype) const
389 {
390         CounterList::const_iterator const cit = counterList_.find(ctr);
391         if (cit == counterList_.end()) {
392                 lyxerr << "Counter "
393                        << to_utf8(ctr)
394                        << " does not exist." << endl;
395                 return docstring();
396         }
397
398         int val = cit->second.value();
399
400         if (numbertype == "hebrew")
401                 return docstring(1, hebrewCounter(val));
402
403         if (numbertype == "alph")
404                 return docstring(1, loweralphaCounter(val));
405
406         if (numbertype == "Alph")
407                 return docstring(1, alphaCounter(val));
408
409         if (numbertype == "roman")
410                 return lowerromanCounter(val);
411
412         if (numbertype == "Roman")
413                 return romanCounter(val);
414
415         return convert<docstring>(val);
416 }
417
418
419 docstring Counters::theCounter(docstring const & counter,
420                                string const & lang) const
421 {
422         CounterList::const_iterator it = counterList_.find(counter); 
423         if (it == counterList_.end())
424                 return from_ascii("??");
425         Counter const & ctr = it->second;
426         Counter::StringMap & sm = ctr.flatLabelStrings(appendix());
427         Counter::StringMap::iterator smit = sm.find(lang);
428         if (smit != sm.end())
429                 return counterLabel(smit->second, lang);
430
431         vector<docstring> callers;
432         docstring const & fls = flattenLabelString(counter, appendix(),
433                                                    lang, callers);
434         sm[lang] = fls;
435         return counterLabel(fls, lang);
436 }
437
438
439 docstring Counters::flattenLabelString(docstring const & counter, 
440                                        bool in_appendix,
441                                        string const & lang,
442                                        vector<docstring> & callers) const
443 {
444         docstring label;
445
446         if (find(callers.begin(), callers.end(), counter) != callers.end()) {
447                 // recursion detected
448                 lyxerr << "Warning: Recursion in label for counter `"
449                        << counter << "' detected"
450                        << endl;
451                 return from_ascii("??");
452         }
453                 
454         CounterList::const_iterator it = counterList_.find(counter); 
455         if (it == counterList_.end())
456                 return from_ascii("??");
457         Counter const & c = it->second;
458
459         docstring ls = translateIfPossible(c.labelString(in_appendix), lang);
460
461         callers.push_back(counter);
462         if (ls.empty()) {
463                 if (!c.master().empty())
464                         ls = flattenLabelString(c.master(), in_appendix, lang, callers) 
465                                 + from_ascii(".");
466                 callers.pop_back();
467                 return ls + from_ascii("\\arabic{") + counter + "}";
468         }
469
470         while (true) {
471                 //lyxerr << "ls=" << to_utf8(ls) << endl;
472                 size_t const i = ls.find(from_ascii("\\the"), 0);
473                 if (i == docstring::npos)
474                         break;
475                 size_t const j = i + 4;
476                 size_t k = j;
477                 while (k < ls.size() && lowercase(ls[k]) >= 'a' 
478                        && lowercase(ls[k]) <= 'z')
479                         ++k;
480                 docstring const newc = ls.substr(j, k - j);
481                 docstring const repl = flattenLabelString(newc, in_appendix,
482                                                           lang, callers);
483                 ls.replace(i, k - j + 4, repl);
484         }
485         callers.pop_back();
486
487         return ls;
488 }
489
490
491 docstring Counters::counterLabel(docstring const & format,
492                                  string const & lang) const
493 {
494         docstring label = format;
495
496         // FIXME: Using regexps would be better, but we compile boost without
497         // wide regexps currently.
498         docstring const the = from_ascii("\\the");
499         while (true) {
500                 //lyxerr << "label=" << label << endl;
501                 size_t const i = label.find(the, 0);
502                 if (i == docstring::npos)
503                         break;
504                 size_t const j = i + 4;
505                 size_t k = j;
506                 while (k < label.size() && lowercase(label[k]) >= 'a' 
507                        && lowercase(label[k]) <= 'z')
508                         ++k;
509                 docstring const newc(label, j, k - j);
510                 label.replace(i, k - i, theCounter(newc, lang));
511         }
512         while (true) {
513                 //lyxerr << "label=" << label << endl;
514
515                 size_t const i = label.find('\\', 0);
516                 if (i == docstring::npos)
517                         break;
518                 size_t const j = label.find('{', i + 1);
519                 if (j == docstring::npos)
520                         break;
521                 size_t const k = label.find('}', j + 1);
522                 if (k == docstring::npos)
523                         break;
524                 docstring const numbertype(label, i + 1, j - i - 1);
525                 docstring const counter(label, j + 1, k - j - 1);
526                 label.replace(i, k + 1 - i, labelItem(counter, numbertype));
527         }
528         //lyxerr << "DONE! label=" << label << endl;
529         return label;
530 }
531
532
533 } // namespace lyx