]> git.lyx.org Git - lyx.git/blob - src/support/lstrings.C
qt3 compiles again
[lyx.git] / src / support / lstrings.C
1 /**
2  * \file lstrings.C
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 Jean-Marc Lasgouttes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "support/lstrings.h"
15 #include "support/lyxlib.h"
16 #include "support/convert.h"
17
18 #include "debug.h"
19
20 #include <boost/tokenizer.hpp>
21 #include <boost/assert.hpp>
22
23 #ifndef I_AM_NOT_AFRAID_OF_HEADER_LIBRARIES
24 #if USE_BOOST_FORMAT
25 #include <boost/format.hpp>
26 #endif
27 #endif
28
29 #include <cctype>
30 #include <cstdlib>
31
32 #include <algorithm>
33 #include <sstream>
34
35 using lyx::docstring;
36
37 using std::transform;
38 using std::string;
39 using std::vector;
40
41 #ifndef CXX_GLOBAL_CSTD
42 using std::isdigit;
43 using std::tolower;
44 using std::toupper;
45 #endif
46
47
48 namespace lyx {
49 namespace support {
50
51 int compare_no_case(string const & s, string const & s2)
52 {
53         string::const_iterator p = s.begin();
54         string::const_iterator p2 = s2.begin();
55
56         while (p != s.end() && p2 != s2.end()) {
57                 int const lc1 = tolower(*p);
58                 int const lc2 = tolower(*p2);
59                 if (lc1 != lc2)
60                         return (lc1 < lc2) ? -1 : 1;
61                 ++p;
62                 ++p2;
63         }
64
65         if (s.size() == s2.size())
66                 return 0;
67         if (s.size() < s2.size())
68                 return -1;
69         return 1;
70 }
71
72
73 int compare_no_case(docstring const & s, docstring const & s2)
74 {
75         docstring::const_iterator p = s.begin();
76         docstring::const_iterator p2 = s2.begin();
77
78         while (p != s.end() && p2 != s2.end()) {
79                 int const lc1 = tolower(*p);
80                 int const lc2 = tolower(*p2);
81                 if (lc1 != lc2)
82                         return (lc1 < lc2) ? -1 : 1;
83                 ++p;
84                 ++p2;
85         }
86
87         if (s.size() == s2.size())
88                 return 0;
89         if (s.size() < s2.size())
90                 return -1;
91         return 1;
92 }
93
94
95 namespace {
96         int ascii_tolower(int c) {
97                 if (c >= 'A' && c <= 'Z')
98                         return c - 'A' + 'a';
99                 return c;
100         }
101 }
102
103
104 int compare_ascii_no_case(string const & s, string const & s2)
105 {
106         string::const_iterator p = s.begin();
107         string::const_iterator p2 = s2.begin();
108
109         while (p != s.end() && p2 != s2.end()) {
110                 int const lc1 = ascii_tolower(*p);
111                 int const lc2 = ascii_tolower(*p2);
112                 if (lc1 != lc2)
113                         return (lc1 < lc2) ? -1 : 1;
114                 ++p;
115                 ++p2;
116         }
117
118         if (s.size() == s2.size())
119                 return 0;
120         if (s.size() < s2.size())
121                 return -1;
122         return 1;
123 }
124
125
126 int compare_no_case(string const & s, string const & s2, unsigned int len)
127 {
128         string::const_iterator p = s.begin();
129         string::const_iterator p2 = s2.begin();
130         unsigned int i = 0;
131         while (i < len && p != s.end() && p2 != s2.end()) {
132                 int const lc1 = tolower(*p);
133                 int const lc2 = tolower(*p2);
134                 if (lc1 != lc2)
135                         return (lc1 < lc2) ? -1 : 1;
136                 ++i;
137                 ++p;
138                 ++p2;
139         }
140
141         if (s.size() >= len && s2.size() >= len)
142                 return 0;
143         if (s.size() < s2.size())
144                 return -1;
145         return 1;
146 }
147
148
149 bool isStrInt(string const & str)
150 {
151         if (str.empty()) return false;
152
153         // Remove leading and trailing white space chars.
154         string const tmpstr = trim(str);
155         if (tmpstr.empty()) return false;
156
157         string::const_iterator cit = tmpstr.begin();
158         if ((*cit) == '-') ++cit;
159         string::const_iterator end = tmpstr.end();
160         for (; cit != end; ++cit) {
161                 if (!isdigit((*cit))) return false;
162         }
163         return true;
164 }
165
166
167 bool isStrUnsignedInt(string const & str)
168 {
169         if (str.empty()) return false;
170
171         // Remove leading and trailing white space chars.
172         string const tmpstr = trim(str);
173         if (tmpstr.empty()) return false;
174
175         string::const_iterator cit = tmpstr.begin();
176         string::const_iterator end = tmpstr.end();
177         for (; cit != end; ++cit) {
178                 if (!isdigit((*cit))) return false;
179         }
180         return true;
181 }
182
183
184 bool isStrDbl(string const & str)
185 {
186         if (str.empty()) return false;
187
188         // Remove leading and trailing white space chars.
189         string const tmpstr = trim(str);
190         if (tmpstr.empty()) return false;
191         //      if (1 < tmpstr.count('.')) return false;
192
193         string::const_iterator cit = tmpstr.begin();
194         bool found_dot(false);
195         if ((*cit) == '-') ++cit;
196         string::const_iterator end = tmpstr.end();
197         for (; cit != end; ++cit) {
198                 if (!isdigit((*cit))
199                     && '.' != (*cit)) {
200                         return false;
201                 }
202                 if ('.' == (*cit)) {
203                         if (found_dot) {
204                                 return false;
205                         } else {
206                                 found_dot = true;
207                         }
208                 }
209         }
210         return true;
211 }
212
213
214 char lowercase(char c)
215 {
216         return char(tolower(c));
217 }
218
219
220 char uppercase(char c)
221 {
222         return char(toupper(c));
223 }
224
225 // FIXME for lowercase() and uppercase() function below:
226 // 1) std::tolower() and std::toupper() are templates that
227 // compile fine with char_type. With the test (c >= 256) we
228 // do not trust these function to do the right thing with
229 // unicode char.
230 // 2) these functions use the current locale, which is wrong
231 // if it is not latin1 based (latin1 is a subset of UCS4).
232
233 char_type lowercase(char_type c)
234 {
235         if (c >= 256)
236                 return c;
237
238         return tolower(c);
239 }
240
241
242 char_type uppercase(char_type c)
243 {
244         if (c >= 256)
245                 return c;
246
247         return toupper(c);
248 }
249
250
251 namespace {
252
253 // since we cannot use std::tolower and std::toupper directly in the
254 // calls to std::transform yet, we use these helper clases. (Lgb)
255
256 struct local_lowercase {
257         char operator()(char c) const {
258                 return tolower(c);
259         }
260 };
261
262 struct local_uppercase {
263         char operator()(char c) const {
264                 return toupper(c);
265         }
266 };
267
268 struct local_ascii_lowercase {
269         char operator()(char c) const {
270                 return ascii_tolower(c);
271         }
272 };
273
274 } // end of anon namespace
275
276 string const lowercase(string const & a)
277 {
278         string tmp(a);
279         transform(tmp.begin(), tmp.end(), tmp.begin(), local_lowercase());
280         return tmp;
281 }
282
283 string const uppercase(string const & a)
284 {
285         string tmp(a);
286         transform(tmp.begin(), tmp.end(), tmp.begin(), local_uppercase());
287         return tmp;
288 }
289
290
291 string const ascii_lowercase(string const & a)
292 {
293         string tmp(a);
294         transform(tmp.begin(), tmp.end(), tmp.begin(),
295                   local_ascii_lowercase());
296         return tmp;
297 }
298
299
300 bool prefixIs(string const & a, string const & pre)
301 {
302         string::size_type const prelen = pre.length();
303         string::size_type const alen = a.length();
304
305         if (prelen > alen || a.empty())
306                 return false;
307         else {
308 #if defined(STD_STRING_IS_GOOD)
309                 return a.compare(0, prelen, pre) == 0;
310 #else
311                 return ::strncmp(a.c_str(), pre.c_str(), prelen) == 0;
312 #endif
313         }
314 }
315
316
317 bool suffixIs(string const & a, char c)
318 {
319         if (a.empty()) return false;
320         return a[a.length() - 1] == c;
321 }
322
323
324 bool suffixIs(string const & a, string const & suf)
325 {
326         string::size_type const suflen = suf.length();
327         string::size_type const alen = a.length();
328
329         if (suflen > alen) {
330                 return false;
331         } else {
332 #if !defined(USE_INCLUDED_STRING) && !defined(STD_STRING_IS_GOOD)
333                 string tmp(a, alen - suflen);
334                 return ::strncmp(tmp.c_str(), suf.c_str(), suflen) == 0;
335 #else
336                 return a.compare(alen - suflen, suflen, suf) == 0;
337 #endif
338         }
339 }
340
341
342 bool containsOnly(string const & s, string const & cset)
343 {
344         return s.find_first_not_of(cset) == string::npos;
345 }
346
347
348 // ale970405+lasgoutt-970425
349 // rewritten to use new string (Lgb)
350 string const token(string const & a, char delim, int n)
351 {
352         if (a.empty()) return string();
353
354         string::size_type k = 0;
355         string::size_type i = 0;
356
357         // Find delimiter or end of string
358         for (; n--;)
359                 if ((i = a.find(delim, i)) == string::npos)
360                         break;
361                 else
362                         ++i; // step delim
363         // i is now the n'th delim (or string::npos)
364         if (i == string::npos) return string();
365         k = a.find(delim, i);
366         // k is now the n'th + 1 delim (or string::npos)
367
368         return a.substr(i, k - i);
369 }
370
371
372 docstring const token(docstring const & a, char_type delim, int n)
373 {
374         if (a.empty()) return docstring();
375
376         string::size_type k = 0;
377         string::size_type i = 0;
378
379         // Find delimiter or end of string
380         for (; n--;)
381                 if ((i = a.find(delim, i)) == docstring::npos)
382                         break;
383                 else
384                         ++i; // step delim
385         // i is now the n'th delim (or string::npos)
386         if (i == docstring::npos) return docstring();
387         k = a.find(delim, i);
388         // k is now the n'th + 1 delim (or string::npos)
389
390         return a.substr(i, k - i);
391 }
392
393
394 // this could probably be faster and/or cleaner, but it seems to work (JMarc)
395 // rewritten to use new string (Lgb)
396 int tokenPos(string const & a, char delim, string const & tok)
397 {
398         int i = 0;
399         string str(a);
400         string tmptok;
401
402         while (!str.empty()) {
403                 str = split(str, tmptok, delim);
404                 if (tok == tmptok)
405                         return i;
406                 ++i;
407         }
408         return -1;
409 }
410
411
412 namespace {
413
414 template<typename Ch> inline
415 std::basic_string<Ch> const subst_char(std::basic_string<Ch> const & a,
416                 Ch oldchar, Ch newchar)
417 {
418         typedef std::basic_string<Ch> String;
419         String tmp(a);
420         typename String::iterator lit = tmp.begin();
421         typename String::iterator end = tmp.end();
422         for (; lit != end; ++lit)
423                 if ((*lit) == oldchar)
424                         (*lit) = newchar;
425         return tmp;
426 }
427
428
429 template<typename String> inline
430 String const subst_string(String const & a,
431                 String const & oldstr, String const & newstr)
432 {
433         BOOST_ASSERT(!oldstr.empty());
434         String lstr = a;
435         typename String::size_type i = 0;
436         typename String::size_type const olen = oldstr.length();
437         while ((i = lstr.find(oldstr, i)) != string::npos) {
438                 lstr.replace(i, olen, newstr);
439                 i += newstr.length(); // We need to be sure that we dont
440                 // use the same i over and over again.
441         }
442         return lstr;
443 }
444
445 }
446
447
448 string const subst(string const & a, char oldchar, char newchar)
449 {
450         return subst_char(a, oldchar, newchar);
451 }
452
453
454 docstring const subst(docstring const & a,
455                 char_type oldchar, char_type newchar)
456 {
457         return subst_char(a, oldchar, newchar);
458 }
459
460
461 string const subst(string const & a,
462                 string const & oldstr, string const & newstr)
463 {
464         return subst_string(a, oldstr, newstr);
465 }
466
467
468 docstring const subst(docstring const & a,
469                 docstring const & oldstr, docstring const & newstr)
470 {
471         return subst_string(a, oldstr, newstr);
472 }
473
474
475 docstring const trim(docstring const & a, char const * p)
476 {
477         BOOST_ASSERT(p);
478
479         if (a.empty() || !*p)
480                 return a;
481
482         docstring s = lyx::from_ascii(p);
483         docstring::size_type r = a.find_last_not_of(s);
484         docstring::size_type l = a.find_first_not_of(s);
485
486         // Is this the minimal test? (lgb)
487         if (r == docstring::npos && l == docstring::npos)
488                 return docstring();
489
490         return a.substr(l, r - l + 1);
491 }
492
493
494 string const trim(string const & a, char const * p)
495 {
496         BOOST_ASSERT(p);
497
498         if (a.empty() || !*p)
499                 return a;
500
501         string::size_type r = a.find_last_not_of(p);
502         string::size_type l = a.find_first_not_of(p);
503
504         // Is this the minimal test? (lgb)
505         if (r == string::npos && l == string::npos)
506                 return string();
507
508         return a.substr(l, r - l + 1);
509 }
510
511
512 string const rtrim(string const & a, char const * p)
513 {
514         BOOST_ASSERT(p);
515
516         if (a.empty() || !*p)
517                 return a;
518
519         string::size_type r = a.find_last_not_of(p);
520
521         // Is this test really needed? (Lgb)
522         if (r == string::npos)
523                 return string();
524
525         return a.substr(0, r + 1);
526 }
527
528
529 string const ltrim(string const & a, char const * p)
530 {
531         BOOST_ASSERT(p);
532
533         if (a.empty() || !*p)
534                 return a;
535
536         string::size_type l = a.find_first_not_of(p);
537
538         if (l == string::npos)
539                 return string();
540
541         return a.substr(l, string::npos);
542 }
543
544
545 template<typename String, typename Char> inline
546 String const doSplit(String const & a, String & piece, Char delim)
547 {
548         String tmp;
549         typename String::size_type i = a.find(delim);
550         if (i == a.length() - 1) {
551                 piece = a.substr(0, i);
552         } else if (i != String::npos) {
553                 piece = a.substr(0, i);
554                 tmp = a.substr(i + 1);
555         } else if (i == 0) {
556                 piece.erase();
557                 tmp = a.substr(i + 1);
558         } else {
559                 piece = a;
560         }
561         return tmp;
562 }
563
564
565 string const split(string const & a, string & piece, char delim)
566 {
567         return doSplit(a, piece, delim);
568 }
569
570
571 docstring const split(docstring const & a, docstring & piece, char_type delim)
572 {
573         return doSplit(a, piece, delim);
574 }
575
576
577 string const split(string const & a, char delim)
578 {
579         string tmp;
580         string::size_type i = a.find(delim);
581         if (i != string::npos) // found delim
582                 tmp = a.substr(i + 1);
583         return tmp;
584 }
585
586
587 // ale970521
588 string const rsplit(string const & a, string & piece, char delim)
589 {
590         string tmp;
591         string::size_type i = a.rfind(delim);
592         if (i != string::npos) { // delimiter was found
593                 piece = a.substr(0, i);
594                 tmp = a.substr(i + 1);
595         } else { // delimiter was not found
596                 piece.erase();
597         }
598         return tmp;
599 }
600
601
602 // This function escapes 8-bit characters and other problematic
603 // characters that cause problems in latex labels.
604 string const escape(string const & lab)
605 {
606         char hexdigit[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
607                               '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
608         string enc;
609         for (string::size_type i = 0; i < lab.length(); ++i) {
610                 unsigned char c= lab[i];
611                 if (c >= 128 || c == '=' || c == '%') {
612                         enc += '=';
613                         enc += hexdigit[c>>4];
614                         enc += hexdigit[c & 15];
615                 } else {
616                         enc += c;
617                 }
618         }
619         return enc;
620 }
621
622
623 /// gives a vector of stringparts which have the delimiter delim
624 vector<string> const getVectorFromString(string const & str,
625                                          string const & delim)
626 {
627 // Lars would like this code to go, but for now his replacement (below)
628 // doesn't fullfil the same function. I have, therefore, reactivated the
629 // old code for now. Angus 11 Nov 2002.
630 #if 1
631         vector<string> vec;
632         if (str.empty())
633                 return vec;
634         string keys = rtrim(str);
635         for(;;) {
636                 string::size_type const idx = keys.find(delim);
637                 if (idx == string::npos) {
638                         vec.push_back(ltrim(keys));
639                         break;
640                 }
641                 string const key = trim(keys.substr(0, idx));
642                 if (!key.empty())
643                         vec.push_back(key);
644                 string::size_type const start = idx + delim.size();
645                 keys = keys.substr(start);
646         }
647         return vec;
648 #else
649         boost::char_separator<char> sep(delim.c_str());
650         boost::tokenizer<boost::char_separator<char> > tokens(str, sep);
651         return vector<string>(tokens.begin(), tokens.end());
652 #endif
653 }
654
655
656 // the same vice versa
657 string const getStringFromVector(vector<string> const & vec,
658                                  string const & delim)
659 {
660         string str;
661         int i = 0;
662         for (vector<string>::const_iterator it = vec.begin();
663              it != vec.end(); ++it) {
664                 string item = trim(*it);
665                 if (item.empty())
666                         continue;
667                 if (i++ > 0)
668                         str += delim;
669                 str += item;
670         }
671         return str;
672 }
673
674
675 int findToken(char const * const str[], string const & search_token)
676 {
677         int i = 0;
678
679         while (str[i][0] && str[i] != search_token)
680                 ++i;
681         if (!str[i][0])
682                 i = -1;
683         return i;
684 }
685
686
687 docstring const externalLineEnding(docstring const & str)
688 {
689 #if defined(__APPLE__)
690         // The MAC clipboard uses \r for lineendings, and we use \n
691         return subst(str, '\n', '\r');
692 #elif defined (_WIN32) || (defined (__CYGWIN__) && defined (X_DISPLAY_MISSING))
693         // Windows clipboard uses \r\n for lineendings, and we use \n
694         return subst(str, lyx::from_ascii("\n"), lyx::from_ascii("\r\n"));
695 #else
696         return str;
697 #endif
698 }
699
700
701 docstring const internalLineEnding(docstring const & str)
702 {
703         docstring const s = subst(str,
704                         lyx::from_ascii("\r\n"), lyx::from_ascii("\n"));
705         return subst(s, '\r', '\n');
706 }
707
708
709 #ifndef I_AM_NOT_AFRAID_OF_HEADER_LIBRARIES
710 #if USE_BOOST_FORMAT
711
712 template<>
713 docstring bformat(docstring const & fmt, int arg1)
714 {
715         return (boost::basic_format<char_type>(fmt) % arg1).str();
716 }
717
718
719 template<>
720 docstring bformat(docstring const & fmt, long arg1)
721 {
722         return (boost::basic_format<char_type>(fmt) % arg1).str();
723 }
724
725
726 template<>
727 docstring bformat(docstring const & fmt, unsigned int arg1)
728 {
729         return (boost::basic_format<char_type>(fmt) % arg1).str();
730 }
731
732
733 template<>
734 docstring bformat<docstring>(docstring const & fmt, docstring arg1)
735 {
736         return (boost::basic_format<char_type>(fmt) % arg1).str();
737 }
738
739
740 template<>
741 docstring bformat(docstring const & fmt, char * arg1)
742 {
743         return (boost::basic_format<char_type>(fmt) % arg1).str();
744 }
745
746
747 template<>
748 docstring bformat(docstring const & fmt, int arg1, int arg2)
749 {
750         return (boost::basic_format<char_type>(fmt) % arg1 % arg2).str();
751 }
752
753
754 template<>
755 docstring bformat(docstring const & fmt, docstring arg1, docstring arg2)
756 {
757         return (boost::basic_format<char_type>(fmt) % arg1 % arg2).str();
758 }
759
760
761 template<>
762 docstring bformat(docstring const & fmt, char const * arg1, docstring arg2)
763 {
764         return (boost::basic_format<char_type>(fmt) % arg1 % arg2).str();
765 }
766
767
768 template<>
769 docstring bformat(docstring const & fmt, docstring arg1, docstring arg2, docstring arg3)
770 {
771         return (boost::basic_format<char_type>(fmt) % arg1 % arg2 % arg3).str();
772 }
773
774
775 template<>
776 docstring bformat(docstring const & fmt,
777                docstring arg1, docstring arg2, docstring arg3, docstring arg4)
778 {
779         return (boost::basic_format<char_type>(fmt) % arg1 % arg2 % arg3 % arg4).str();
780 }
781
782 #else
783
784 template<>
785 docstring bformat(docstring const & fmt, int arg1)
786 {
787         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%1$d")));
788         docstring const str = subst(fmt, lyx::from_ascii("%1$d"), convert<docstring>(arg1));
789         return subst(str, lyx::from_ascii("%%"), lyx::from_ascii("%"));
790 }
791
792
793 template<>
794 docstring bformat(docstring const & fmt, long arg1)
795 {
796         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%1$d")));
797         docstring const str = subst(fmt, lyx::from_ascii("%1$d"), convert<docstring>(arg1));
798         return subst(str, lyx::from_ascii("%%"), lyx::from_ascii("%"));
799 }
800
801
802 template<>
803 docstring bformat(docstring const & fmt, unsigned int arg1)
804 {
805         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%1$d")));
806         docstring const str = subst(fmt, lyx::from_ascii("%1$d"), convert<docstring>(arg1));
807         return subst(str, lyx::from_ascii("%%"), lyx::from_ascii("%"));
808 }
809
810
811 template<>
812 docstring bformat(docstring const & fmt, docstring arg1)
813 {
814         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%1$s")));
815         docstring const str = subst(fmt, lyx::from_ascii("%1$s"), arg1);
816         return subst(str, lyx::from_ascii("%%"), lyx::from_ascii("%"));
817 }
818
819
820 template<>
821 docstring bformat(docstring const & fmt, char * arg1)
822 {
823         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%1$s")));
824         docstring const str = subst(fmt, lyx::from_ascii("%1$s"), lyx::from_ascii(arg1));
825         return subst(str, lyx::from_ascii("%%"), lyx::from_ascii("%"));
826 }
827
828
829 template<>
830 docstring bformat(docstring const & fmt, docstring arg1, docstring arg2)
831 {
832         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%1$s")));
833         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%2$s")));
834         docstring str = subst(fmt, lyx::from_ascii("%1$s"), arg1);
835         str = subst(str, lyx::from_ascii("%2$s"), arg2);
836         return subst(str, lyx::from_ascii("%%"), lyx::from_ascii("%"));
837 }
838
839
840 template<>
841 docstring bformat(docstring const & fmt, char const * arg1, docstring arg2)
842 {
843         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%1$s")));
844         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%2$s")));
845         docstring str = subst(fmt, lyx::from_ascii("%1$s"), lyx::from_ascii(arg1));
846         str = subst(fmt, lyx::from_ascii("%2$s"), arg2);
847         return subst(str, lyx::from_ascii("%%"), lyx::from_ascii("%"));
848 }
849
850
851 template<>
852 docstring bformat(docstring const & fmt, int arg1, int arg2)
853 {
854         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%1$d")));
855         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%2$d")));
856         docstring str = subst(fmt, lyx::from_ascii("%1$d"), convert<docstring>(arg1));
857         str = subst(str, lyx::from_ascii("%2$d"), convert<docstring>(arg2));
858         return subst(str, lyx::from_ascii("%%"), lyx::from_ascii("%"));
859 }
860
861
862 template<>
863 docstring bformat(docstring const & fmt, docstring arg1, docstring arg2, docstring arg3)
864 {
865         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%1$s")));
866         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%2$s")));
867         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%3$s")));
868         docstring str = subst(fmt, lyx::from_ascii("%1$s"), arg1);
869         str = subst(str, lyx::from_ascii("%2$s"), arg2);
870         str = subst(str, lyx::from_ascii("%3$s"), arg3);
871         return subst(str, lyx::from_ascii("%%"), lyx::from_ascii("%"));
872 }
873
874
875 template<>
876 docstring bformat(docstring const & fmt,
877                docstring arg1, docstring arg2, docstring arg3, docstring arg4)
878 {
879         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%1$s")));
880         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%2$s")));
881         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%3$s")));
882         BOOST_ASSERT(contains(fmt, lyx::from_ascii("%4$s")));
883         docstring str = subst(fmt, lyx::from_ascii("%1$s"), arg1);
884         str = subst(str, lyx::from_ascii("%2$s"), arg2);
885         str = subst(str, lyx::from_ascii("%3$s"), arg3);
886         str = subst(str, lyx::from_ascii("%4$s"), arg4);
887         return subst(str, lyx::from_ascii("%%"), lyx::from_ascii("%"));
888 }
889
890 #endif
891 #endif
892
893 } // namespace support
894 } // namespace lyx