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 
Counter()35 Counter::Counter()
36 	: initial_value_(0)
37 {
38 	reset();
39 }
40 
41 
Counter(docstring const & mc,docstring const & ls,docstring const & lsa)42 Counter::Counter(docstring const & mc, docstring const & ls,
43                 docstring const & lsa)
44 	: initial_value_(0), master_(mc), labelstring_(ls), labelstringappendix_(lsa)
45 {
46 	reset();
47 }
48 
49 
read(Lexer & lex)50 bool Counter::read(Lexer & lex)
51 {
52 	enum {
53 		CT_WITHIN = 1,
54 		CT_LABELSTRING,
55 		CT_LABELSTRING_APPENDIX,
56 		CT_PRETTYFORMAT,
57 		CT_INITIALVALUE,
58 		CT_END
59 	};
60 
61 	LexerKeyword counterTags[] = {
62 		{ "end", CT_END },
63 	  { "initialvalue", CT_INITIALVALUE},
64 		{ "labelstring", CT_LABELSTRING },
65 		{ "labelstringappendix", CT_LABELSTRING_APPENDIX },
66 		{ "prettyformat", CT_PRETTYFORMAT },
67 		{ "within", CT_WITHIN }
68 	};
69 
70 	lex.pushTable(counterTags);
71 
72 	bool getout = false;
73 	while (!getout && lex.isOK()) {
74 		int le = lex.lex();
75 		switch (le) {
76 			case Lexer::LEX_UNDEF:
77 				lex.printError("Unknown counter tag `$$Token'");
78 				continue;
79 			default:
80 				break;
81 		}
82 		switch (le) {
83 			case CT_WITHIN:
84 				lex.next();
85 				master_ = lex.getDocString();
86 				if (master_ == "none")
87 					master_.erase();
88 				break;
89 			case CT_INITIALVALUE:
90 				lex.next();
91 				initial_value_ = lex.getInteger();
92 				// getInteger() returns -1 on error, and larger
93 				// negative values do not make much sense.
94 				// In the other case, we subtract one, since the
95 				// counter will be incremented before its first use.
96 				if (initial_value_ <= -1)
97 					initial_value_ = 0;
98 				else
99 					initial_value_ -= 1;
100 				break;
101 			case CT_PRETTYFORMAT:
102 				lex.next();
103 				prettyformat_ = lex.getDocString();
104 				break;
105 			case CT_LABELSTRING:
106 				lex.next();
107 				labelstring_ = lex.getDocString();
108 				labelstringappendix_ = labelstring_;
109 				break;
110 			case CT_LABELSTRING_APPENDIX:
111 				lex.next();
112 				labelstringappendix_ = lex.getDocString();
113 				break;
114 			case CT_END:
115 				getout = true;
116 				break;
117 		}
118 	}
119 
120 	// Here if have a full counter if getout == true
121 	if (!getout)
122 		LYXERR0("No End tag found for counter!");
123 	lex.popTable();
124 	return getout;
125 }
126 
127 
set(int v)128 void Counter::set(int v)
129 {
130 	value_ = v;
131 }
132 
133 
addto(int v)134 void Counter::addto(int v)
135 {
136 	value_ += v;
137 }
138 
139 
value() const140 int Counter::value() const
141 {
142 	return value_;
143 }
144 
145 
step()146 void Counter::step()
147 {
148 	++value_;
149 }
150 
151 
reset()152 void Counter::reset()
153 {
154 	value_ = initial_value_;
155 }
156 
157 
master() const158 docstring const & Counter::master() const
159 {
160 	return master_;
161 }
162 
163 
checkAndRemoveMaster(docstring const & cnt)164 bool Counter::checkAndRemoveMaster(docstring const & cnt)
165 {
166 	if (master_ != cnt)
167 		return false;
168 	master_ = docstring();
169 	return true;
170 }
171 
172 
labelString(bool in_appendix) const173 docstring const & Counter::labelString(bool in_appendix) const
174 {
175 	return in_appendix ? labelstringappendix_ : labelstring_;
176 }
177 
178 
flatLabelStrings(bool in_appendix) const179 Counter::StringMap & Counter::flatLabelStrings(bool in_appendix) const
180 {
181 	return in_appendix ? flatlabelstringappendix_ : flatlabelstring_;
182 }
183 
184 
Counters()185 Counters::Counters() : appendix_(false), subfloat_(false), longtable_(false)
186 {
187 	layout_stack_.push_back(0);
188 	counter_stack_.push_back(from_ascii(""));
189 }
190 
191 
newCounter(docstring const & newc,docstring const & masterc,docstring const & ls,docstring const & lsa)192 void Counters::newCounter(docstring const & newc,
193 			  docstring const & masterc,
194 			  docstring const & ls,
195 			  docstring const & lsa)
196 {
197 	if (!masterc.empty() && !hasCounter(masterc)) {
198 		lyxerr << "Master counter does not exist: "
199 		       << to_utf8(masterc)
200 		       << endl;
201 		return;
202 	}
203 	counterList_[newc] = Counter(masterc, ls, lsa);
204 }
205 
206 
hasCounter(docstring const & c) const207 bool Counters::hasCounter(docstring const & c) const
208 {
209 	return counterList_.find(c) != counterList_.end();
210 }
211 
212 
read(Lexer & lex,docstring const & name,bool makenew)213 bool Counters::read(Lexer & lex, docstring const & name, bool makenew)
214 {
215 	if (hasCounter(name)) {
216 		LYXERR(Debug::TCLASS, "Reading existing counter " << to_utf8(name));
217 		return counterList_[name].read(lex);
218 	}
219 
220 	LYXERR(Debug::TCLASS, "Reading new counter " << to_utf8(name));
221 	Counter cnt;
222 	bool success = cnt.read(lex);
223 	// if makenew is false, we will just discard what we read
224 	if (success && makenew)
225 		counterList_[name] = cnt;
226 	else if (!success)
227 		LYXERR0("Error reading counter `" << name << "'!");
228 	return success;
229 }
230 
231 
set(docstring const & ctr,int const val)232 void Counters::set(docstring const & ctr, int const val)
233 {
234 	CounterList::iterator const it = counterList_.find(ctr);
235 	if (it == counterList_.end()) {
236 		lyxerr << "set: Counter does not exist: "
237 		       << to_utf8(ctr) << endl;
238 		return;
239 	}
240 	it->second.set(val);
241 }
242 
243 
addto(docstring const & ctr,int const val)244 void Counters::addto(docstring const & ctr, int const val)
245 {
246 	CounterList::iterator const it = counterList_.find(ctr);
247 	if (it == counterList_.end()) {
248 		lyxerr << "addto: Counter does not exist: "
249 		       << to_utf8(ctr) << endl;
250 		return;
251 	}
252 	it->second.addto(val);
253 }
254 
255 
value(docstring const & ctr) const256 int Counters::value(docstring const & ctr) const
257 {
258 	CounterList::const_iterator const cit = counterList_.find(ctr);
259 	if (cit == counterList_.end()) {
260 		lyxerr << "value: Counter does not exist: "
261 		       << to_utf8(ctr) << endl;
262 		return 0;
263 	}
264 	return cit->second.value();
265 }
266 
267 
resetSlaves(docstring const & ctr)268 void Counters::resetSlaves(docstring const & ctr)
269 {
270 	CounterList::iterator it = counterList_.begin();
271 	CounterList::iterator const end = counterList_.end();
272 	for (; it != end; ++it) {
273 		if (it->second.master() == ctr) {
274 			it->second.reset();
275 			resetSlaves(it->first);
276 		}
277 	}
278 }
279 
280 
stepMaster(docstring const & ctr,UpdateType utype)281 void Counters::stepMaster(docstring const & ctr, UpdateType utype)
282 {
283 	CounterList::iterator it = counterList_.find(ctr);
284 	if (it == counterList_.end()) {
285 		lyxerr << "step: Counter does not exist: "
286 		       << to_utf8(ctr) << endl;
287 		return;
288 	}
289 	step(it->second.master(), utype);
290 }
291 
292 
step(docstring const & ctr,UpdateType utype)293 void Counters::step(docstring const & ctr, UpdateType utype)
294 {
295 	CounterList::iterator it = counterList_.find(ctr);
296 	if (it == counterList_.end()) {
297 		lyxerr << "step: Counter does not exist: "
298 		       << to_utf8(ctr) << endl;
299 		return;
300 	}
301 
302 	it->second.step();
303 	if (utype == OutputUpdate) {
304 		LBUFERR(!counter_stack_.empty());
305 		counter_stack_.pop_back();
306 		counter_stack_.push_back(ctr);
307 	}
308 
309 	resetSlaves(ctr);
310 }
311 
312 
reset()313 void Counters::reset()
314 {
315 	appendix_ = false;
316 	subfloat_ = false;
317 	current_float_.erase();
318 	CounterList::iterator it = counterList_.begin();
319 	CounterList::iterator const end = counterList_.end();
320 	for (; it != end; ++it)
321 		it->second.reset();
322 	counter_stack_.clear();
323 	counter_stack_.push_back(from_ascii(""));
324 	layout_stack_.clear();
325 	layout_stack_.push_back(0);
326 }
327 
328 
reset(docstring const & match)329 void Counters::reset(docstring const & match)
330 {
331 	LASSERT(!match.empty(), return);
332 
333 	CounterList::iterator it = counterList_.begin();
334 	CounterList::iterator end = counterList_.end();
335 	for (; it != end; ++it) {
336 		if (it->first.find(match) != string::npos)
337 			it->second.reset();
338 	}
339 }
340 
341 
remove(docstring const & cnt)342 bool Counters::remove(docstring const & cnt)
343 {
344 	bool retval = counterList_.erase(cnt);
345 	if (!retval)
346 		return false;
347 	CounterList::iterator it = counterList_.begin();
348 	CounterList::iterator end = counterList_.end();
349 	for (; it != end; ++it) {
350 		if (it->second.checkAndRemoveMaster(cnt))
351 			LYXERR(Debug::TCLASS, "Removed master counter `" +
352 					to_utf8(cnt) + "' from counter: " + to_utf8(it->first));
353 	}
354 	return retval;
355 }
356 
357 
copy(Counters & from,Counters & to,docstring const & match)358 void Counters::copy(Counters & from, Counters & to, docstring const & match)
359 {
360 	CounterList::iterator it = counterList_.begin();
361 	CounterList::iterator end = counterList_.end();
362 	for (; it != end; ++it) {
363 		if (it->first.find(match) != string::npos || match == "") {
364 			to.set(it->first, from.value(it->first));
365 		}
366 	}
367 }
368 
369 
370 namespace {
371 
loweralphaCounter(int const n)372 char loweralphaCounter(int const n)
373 {
374 	if (n < 1 || n > 26)
375 		return '?';
376 	return 'a' + n - 1;
377 }
378 
379 
alphaCounter(int const n)380 char alphaCounter(int const n)
381 {
382 	if (n < 1 || n > 26)
383 		return '?';
384 	return 'A' + n - 1;
385 }
386 
387 
hebrewCounter(int const n)388 char hebrewCounter(int const n)
389 {
390 	static const char hebrew[22] = {
391 		'\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8',
392 		'\xe9', '\xeb', '\xec', '\xee', '\xf0', '\xf1', '\xf2', '\xf4', '\xf6',
393 		'\xf7', '\xf8', '\xf9', '\xfa'
394 	};
395 
396 	if (n < 1 || n > 22)
397 		return '?';
398 	return hebrew[n - 1];
399 }
400 
401 
402 // On the special cases, see http://mathworld.wolfram.com/RomanNumerals.html
403 // and for a list of roman numerals up to and including 3999, see
404 // http://www.research.att.com/~njas/sequences/a006968.txt. (Thanks to Joost
405 // for this info.)
romanCounter(int const n)406 docstring const romanCounter(int const n)
407 {
408 	static char const * const ones[9] = {
409 		"I",   "II",  "III", "IV", "V",
410 		"VI",  "VII", "VIII", "IX"
411 	};
412 
413 	static char const * const tens[9] = {
414 		"X", "XX", "XXX", "XL", "L",
415 		"LX", "LXX", "LXXX", "XC"
416 	};
417 
418 	static char const * const hunds[9] = {
419 		"C", "CC", "CCC", "CD", "D",
420 		"DC", "DCC", "DCCC", "CM"
421 	};
422 
423 	if (n >= 1000 || n < 1)
424 		return from_ascii("??");
425 
426 	int val = n;
427 	string roman;
428 	switch (n) {
429 	//special cases
430 	case 900:
431 		roman = "CM";
432 		break;
433 	case 400:
434 		roman = "CD";
435 		break;
436 	default:
437 		if (val >= 100) {
438 			int hundreds = val / 100;
439 			roman = hunds[hundreds - 1];
440 			val = val % 100;
441 		}
442 		if (val >= 10) {
443 			switch (val) {
444 			//special case
445 			case 90:
446 				roman = roman + "XC";
447 				val = 0; //skip next
448 				break;
449 			default:
450 				int tensnum = val / 10;
451 				roman = roman + tens[tensnum - 1];
452 				val = val % 10;
453 			} // end switch
454 		} // end tens
455 		if (val > 0)
456 			roman = roman + ones[val -1];
457 	}
458 	return from_ascii(roman);
459 }
460 
461 
lowerromanCounter(int const n)462 docstring const lowerromanCounter(int const n)
463 {
464 	return lowercase(romanCounter(n));
465 }
466 
467 
fnsymbolCounter(int const n)468 docstring const fnsymbolCounter(int const n)
469 {
470 	switch(n) {
471 	case 1: return docstring(1, 0x002a); //*
472 	case 2: return docstring(1, 0x2020); // dagger
473 	case 3: return docstring(1, 0x2021); // double dagger
474 	case 4: return docstring(1, 0x00A7); // section sign
475 	case 5: return docstring(1, 0x00B6); // pilcrow sign
476 	case 6: return docstring(1, 0x2016); // vertical bar
477 	case 7: return docstring(2, 0x002a); // two *
478 	case 8: return docstring(2, 0x2020); // two daggers
479 	case 9: return docstring(2, 0x2021); // two double daggers
480 	default:
481 		return from_ascii("?");
482 	};
483 }
484 
485 } // namespace
486 
487 
labelItem(docstring const & ctr,docstring const & numbertype) const488 docstring Counters::labelItem(docstring const & ctr,
489 			      docstring const & numbertype) const
490 {
491 	CounterList::const_iterator const cit = counterList_.find(ctr);
492 	if (cit == counterList_.end()) {
493 		lyxerr << "Counter "
494 		       << to_utf8(ctr)
495 		       << " does not exist." << endl;
496 		return docstring();
497 	}
498 
499 	int val = cit->second.value();
500 
501 	if (numbertype == "hebrew")
502 		return docstring(1, hebrewCounter(val));
503 
504 	if (numbertype == "alph")
505 		return docstring(1, loweralphaCounter(val));
506 
507 	if (numbertype == "Alph")
508 		return docstring(1, alphaCounter(val));
509 
510 	if (numbertype == "roman")
511 		return lowerromanCounter(val);
512 
513 	if (numbertype == "Roman")
514 		return romanCounter(val);
515 
516 	if (numbertype == "fnsymbol")
517 		return fnsymbolCounter(val);
518 
519 	return convert<docstring>(val);
520 }
521 
522 
theCounter(docstring const & counter,string const & lang) const523 docstring Counters::theCounter(docstring const & counter,
524 			       string const & lang) const
525 {
526 	CounterList::const_iterator it = counterList_.find(counter);
527 	if (it == counterList_.end())
528 		return from_ascii("#");
529 	Counter const & ctr = it->second;
530 	Counter::StringMap & sm = ctr.flatLabelStrings(appendix());
531 	Counter::StringMap::iterator smit = sm.find(lang);
532 	if (smit != sm.end())
533 		return counterLabel(smit->second, lang);
534 
535 	vector<docstring> callers;
536 	docstring const & fls = flattenLabelString(counter, appendix(),
537 						   lang, callers);
538 	sm[lang] = fls;
539 	return counterLabel(fls, lang);
540 }
541 
542 
flattenLabelString(docstring const & counter,bool in_appendix,string const & lang,vector<docstring> & callers) const543 docstring Counters::flattenLabelString(docstring const & counter,
544 				       bool in_appendix,
545 				       string const & lang,
546 				       vector<docstring> & callers) const
547 {
548 	if (find(callers.begin(), callers.end(), counter) != callers.end()) {
549 		// recursion detected
550 		lyxerr << "Warning: Recursion in label for counter `"
551 		       << counter << "' detected"
552 		       << endl;
553 		return from_ascii("??");
554 	}
555 
556 	CounterList::const_iterator it = counterList_.find(counter);
557 	if (it == counterList_.end())
558 		return from_ascii("#");
559 	Counter const & c = it->second;
560 
561 	docstring ls = translateIfPossible(c.labelString(in_appendix), lang);
562 
563 	callers.push_back(counter);
564 	if (ls.empty()) {
565 		if (!c.master().empty())
566 			ls = flattenLabelString(c.master(), in_appendix, lang, callers)
567 				+ from_ascii(".");
568 		callers.pop_back();
569 		return ls + from_ascii("\\arabic{") + counter + "}";
570 	}
571 
572 	while (true) {
573 		//lyxerr << "ls=" << to_utf8(ls) << endl;
574 		size_t const i = ls.find(from_ascii("\\the"), 0);
575 		if (i == docstring::npos)
576 			break;
577 		size_t const j = i + 4;
578 		size_t k = j;
579 		while (k < ls.size() && lowercase(ls[k]) >= 'a'
580 		       && lowercase(ls[k]) <= 'z')
581 			++k;
582 		docstring const newc = ls.substr(j, k - j);
583 		docstring const repl = flattenLabelString(newc, in_appendix,
584 							  lang, callers);
585 		ls.replace(i, k - j + 4, repl);
586 	}
587 	callers.pop_back();
588 
589 	return ls;
590 }
591 
592 
counterLabel(docstring const & format,string const & lang) const593 docstring Counters::counterLabel(docstring const & format,
594 				 string const & lang) const
595 {
596 	docstring label = format;
597 
598 	// FIXME: Using regexps would be better, but we compile boost without
599 	// wide regexps currently.
600 	docstring const the = from_ascii("\\the");
601 	while (true) {
602 		//lyxerr << "label=" << label << endl;
603 		size_t const i = label.find(the, 0);
604 		if (i == docstring::npos)
605 			break;
606 		size_t const j = i + 4;
607 		size_t k = j;
608 		while (k < label.size() && lowercase(label[k]) >= 'a'
609 		       && lowercase(label[k]) <= 'z')
610 			++k;
611 		docstring const newc(label, j, k - j);
612 		label.replace(i, k - i, theCounter(newc, lang));
613 	}
614 	while (true) {
615 		//lyxerr << "label=" << label << endl;
616 
617 		size_t const i = label.find('\\', 0);
618 		if (i == docstring::npos)
619 			break;
620 		size_t const j = label.find('{', i + 1);
621 		if (j == docstring::npos)
622 			break;
623 		size_t const k = label.find('}', j + 1);
624 		if (k == docstring::npos)
625 			break;
626 		docstring const numbertype(label, i + 1, j - i - 1);
627 		docstring const counter(label, j + 1, k - j - 1);
628 		label.replace(i, k + 1 - i, labelItem(counter, numbertype));
629 	}
630 	//lyxerr << "DONE! label=" << label << endl;
631 	return label;
632 }
633 
634 
prettyCounter(docstring const & name,string const & lang) const635 docstring Counters::prettyCounter(docstring const & name,
636 			       string const & lang) const
637 {
638 	CounterList::const_iterator it = counterList_.find(name);
639 	if (it == counterList_.end())
640 		return from_ascii("#");
641 	Counter const & ctr = it->second;
642 
643 	docstring const value = theCounter(name, lang);
644 	docstring const & format =
645 	    translateIfPossible(ctr.prettyFormat(), lang);
646 	if (format.empty())
647 		return value;
648 	return subst(format, from_ascii("##"), value);
649 }
650 
651 
currentCounter() const652 docstring Counters::currentCounter() const
653 {
654 	LBUFERR(!counter_stack_.empty());
655 	return counter_stack_.back();
656 }
657 
658 
setActiveLayout(Layout const & lay)659 void Counters::setActiveLayout(Layout const & lay)
660 {
661 	LASSERT(!layout_stack_.empty(), return);
662 	Layout const * const lastlay = layout_stack_.back();
663 	// we want to check whether the layout has changed and, if so,
664 	// whether we are coming out of or going into an environment.
665 	if (!lastlay) {
666 		layout_stack_.pop_back();
667 		layout_stack_.push_back(&lay);
668 		if (lay.isEnvironment())
669 			beginEnvironment();
670 	} else if (lastlay->name() != lay.name()) {
671 		layout_stack_.pop_back();
672 		layout_stack_.push_back(&lay);
673 		if (lastlay->isEnvironment()) {
674 			// we are coming out of an environment
675 			// LYXERR0("Out: " << lastlay->name());
676 			endEnvironment();
677 		}
678 		if (lay.isEnvironment()) {
679 			// we are going into a new environment
680 			// LYXERR0("In: " << lay.name());
681 			beginEnvironment();
682 		}
683 	}
684 }
685 
686 
beginEnvironment()687 void Counters::beginEnvironment()
688 {
689 	counter_stack_.push_back(counter_stack_.back());
690 }
691 
692 
endEnvironment()693 void Counters::endEnvironment()
694 {
695 	LASSERT(!counter_stack_.empty(), return);
696 	counter_stack_.pop_back();
697 }
698 
699 
700 } // namespace lyx
701