]> git.lyx.org Git - lyx.git/blob - src/Lexer.cpp
de.po
[lyx.git] / src / Lexer.cpp
1 /**
2  * \file Lexer.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  * \author Lars Gullik Bjønnes
8  * \author Jean-Marc Lasgouttes
9  * \author John Levon
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "Lexer.h"
17 #include "Format.h"
18
19 #include "support/convert.h"
20 #include "support/debug.h"
21 #include "support/FileName.h"
22 #include "support/filetools.h"
23 #include "support/gzstream.h"
24 #include "support/lassert.h"
25 #include "support/lstrings.h"
26 #include "support/lyxalgo.h"
27 #include "support/types.h"
28
29 #include <functional>
30 #include <istream>
31 #include <stack>
32 #include <vector>
33
34 using namespace std;
35 using namespace lyx::support;
36
37 namespace lyx {
38
39 //////////////////////////////////////////////////////////////////////
40 //
41 // Lexer::Pimpl
42 //
43 //////////////////////////////////////////////////////////////////////
44
45
46 ///
47 class Lexer::Pimpl {
48 public:
49         ///
50         Pimpl(LexerKeyword * tab, int num);
51         ///
52         string const getString() const;
53         ///
54         docstring const getDocString() const;
55         ///
56         void printError(string const & message) const;
57         ///
58         void printTable(ostream & os);
59         ///
60         void pushTable(LexerKeyword * tab, int num);
61         ///
62         void popTable();
63         ///
64         bool setFile(FileName const & filename);
65         ///
66         void setStream(istream & i);
67         ///
68         void setCommentChar(char c);
69         ///
70         bool next(bool esc = false);
71         ///
72         int searchKeyword(char const * const tag) const;
73         ///
74         int lex();
75         ///
76         bool eatLine();
77         ///
78         bool nextToken();
79         /// test if there is a pushed token or the stream is ok
80         bool inputAvailable();
81         ///
82         void pushToken(string const &);
83         /// fb_ is only used to open files, the stream is accessed through is.
84         filebuf fb_;
85
86         /// gz_ is only used to open files, the stream is accessed through is.
87         gz::gzstreambuf gz_;
88
89         /// the stream that we use.
90         istream is;
91         ///
92         string name;
93         ///
94         LexerKeyword * table;
95         ///
96         int no_items;
97         ///
98         string buff;
99         ///
100         int status;
101         ///
102         int lineno;
103         ///
104         string pushTok;
105         /// used for error messages
106         string context;
107         ///
108         char commentChar;
109 private:
110         /// non-copyable
111         Pimpl(Pimpl const &);
112         void operator=(Pimpl const &);
113
114         ///
115         void verifyTable();
116         ///
117         class PushedTable {
118         public:
119                 ///
120                 PushedTable()
121                         : table_elem(nullptr), table_siz(0) {}
122                 ///
123                 PushedTable(LexerKeyword * ki, int siz)
124                         : table_elem(ki), table_siz(siz) {}
125                 ///
126                 LexerKeyword * table_elem;
127                 ///
128                 int table_siz;
129         };
130         ///
131         stack<PushedTable> pushed;
132 };
133
134
135 namespace {
136
137 // used by lower_bound, sort and sorted
138 bool compareTags(LexerKeyword const & a, LexerKeyword const & b)
139 {
140         // we use the ascii version, because in turkish, 'i'
141         // is not the lowercase version of 'I', and thus
142         // turkish locale breaks parsing of tags.
143         return compare_ascii_no_case(a.tag, b.tag) < 0;
144 }
145
146 } // namespace
147
148
149
150 Lexer::Pimpl::Pimpl(LexerKeyword * tab, int num)
151         : is(&fb_), table(tab), no_items(num),
152           status(0), lineno(0), commentChar('#')
153 {
154         verifyTable();
155 }
156
157
158 string const Lexer::Pimpl::getString() const
159 {
160         return buff;
161 }
162
163
164 docstring const Lexer::Pimpl::getDocString() const
165 {
166         return from_utf8(buff);
167 }
168
169
170 void Lexer::Pimpl::printError(string const & message) const
171 {
172         string const tmpmsg = subst(message, "$$Token", getString());
173         lyxerr << "LyX: " << tmpmsg << " [around line " << lineno
174                 << " of file " << to_utf8(makeDisplayPath(name))
175                 << " current token: '" << getString() << "'"
176                 << " context: '" << context << "']" << endl;
177 }
178
179
180 void Lexer::Pimpl::printTable(ostream & os)
181 {
182         os << "\nNumber of tags: " << no_items << endl;
183         for (int i= 0; i < no_items; ++i)
184                 os << "table[" << i
185                    << "]:  tag: `" << table[i].tag
186                    << "'  code:" << table[i].code << '\n';
187         os.flush();
188 }
189
190
191 void Lexer::Pimpl::verifyTable()
192 {
193         // Check if the table is sorted and if not, sort it.
194         if (table
195             && !lyx::sorted(table, table + no_items, &compareTags)) {
196                 lyxerr << "The table passed to Lexer is not sorted!\n"
197                        << "Tell the developers to fix it!" << endl;
198                 // We sort it anyway to avoid problems.
199                 lyxerr << "\nUnsorted:" << endl;
200                 printTable(lyxerr);
201
202                 sort(table, table + no_items, &compareTags);
203                 lyxerr << "\nSorted:" << endl;
204                 printTable(lyxerr);
205         }
206 }
207
208
209 void Lexer::Pimpl::pushTable(LexerKeyword * tab, int num)
210 {
211         PushedTable tmppu(table, no_items);
212         pushed.push(tmppu);
213
214         table = tab;
215         no_items = num;
216
217         verifyTable();
218 }
219
220
221 void Lexer::Pimpl::popTable()
222 {
223         if (pushed.empty()) {
224                 lyxerr << "Lexer error: nothing to pop!" << endl;
225                 return;
226         }
227
228         PushedTable tmp = pushed.top();
229         pushed.pop();
230         table = tmp.table_elem;
231         no_items = tmp.table_siz;
232 }
233
234
235 bool Lexer::Pimpl::setFile(FileName const & filename)
236 {
237         // Check the format of the file.
238         if (theFormats().isZippedFile(filename)) {
239                 LYXERR(Debug::LYXLEX, "lyxlex: compressed");
240                 // The check only outputs a debug message, because it triggers
241                 // a bug in compaq cxx 6.2, where is_open() returns 'true' for
242                 // a fresh new filebuf.  (JMarc)
243                 if (gz_.is_open() || istream::off_type(is.tellg()) > -1)
244                         LYXERR(Debug::LYXLEX, "Error in LyXLex::setFile: "
245                                 "file or stream already set.");
246                 gz_.open(filename.toFilesystemEncoding().c_str(), ios::in);
247                 is.rdbuf(&gz_);
248                 name = filename.absFileName();
249                 lineno = 0;
250                 if (!gz_.is_open() || !is.good())
251                         return false;
252         } else {
253                 LYXERR(Debug::LYXLEX, "lyxlex: UNcompressed");
254
255                 // The check only outputs a debug message, because it triggers
256                 // a bug in compaq cxx 6.2, where is_open() returns 'true' for
257                 // a fresh new filebuf.  (JMarc)
258                 if (fb_.is_open() || istream::off_type(is.tellg()) > 0) {
259                         LYXERR(Debug::LYXLEX, "Error in Lexer::setFile: "
260                                 "file or stream already set.");
261                 }
262                 fb_.open(filename.toSafeFilesystemEncoding().c_str(), ios::in);
263                 is.rdbuf(&fb_);
264                 name = filename.absFileName();
265                 lineno = 0;
266                 if (!fb_.is_open() || !is.good())
267                         return false;
268         }
269
270         // Skip byte order mark.
271         if (is.peek() == 0xef) {
272                 is.get();
273                 if (is.peek() == 0xbb) {
274                         is.get();
275                         LASSERT(is.get() == 0xbf, /**/);
276                 } else
277                         is.unget();
278         }
279
280         return true;
281 }
282
283
284 void Lexer::Pimpl::setStream(istream & i)
285 {
286         if (fb_.is_open() || istream::off_type(is.tellg()) > 0) {
287                 LYXERR(Debug::LYXLEX, "Error in Lexer::setStream: "
288                         "file or stream already set.");
289         }
290         is.rdbuf(i.rdbuf());
291         lineno = 0;
292 }
293
294
295 void Lexer::Pimpl::setCommentChar(char c)
296 {
297         commentChar = c;
298 }
299
300
301 bool Lexer::Pimpl::next(bool esc /* = false */)
302 {
303         if (!pushTok.empty()) {
304                 // There can have been a whole line pushed so
305                 // we extract the first word and leaves the rest
306                 // in pushTok. (Lgb)
307                 if (pushTok[0] == '\\' && pushTok.find(' ') != string::npos) {
308                         buff.clear();
309                         pushTok = split(pushTok, buff, ' ');
310                 } else {
311                         buff = pushTok;
312                         pushTok.clear();
313                 }
314                 status = LEX_TOKEN;
315                 return true;
316         }
317
318
319         char cc = 0;
320         status = 0;
321         while (is && !status) {
322                 is.get(cc);
323                 unsigned char c = cc;
324
325                 if (c == commentChar) {
326                         // Read rest of line (fast :-)
327 #if 1
328                         // That is not fast... (Lgb)
329                         string dummy;
330                         getline(is, dummy);
331
332                         LYXERR(Debug::LYXLEX, "Comment read: `" << c << dummy << '\'');
333 #else
334                         // unfortunately ignore is buggy (Lgb)
335                         is.ignore(100, '\n');
336 #endif
337                         ++lineno;
338                         continue;
339                 }
340
341                 if (c == '\"') {
342                         buff.clear();
343
344                         if (esc) {
345
346                                 do {
347                                         bool escaped = false;
348                                         is.get(cc);
349                                         c = cc;
350                                         if (c == '\r') continue;
351                                         if (c == '\\') {
352                                                 // escape the next char
353                                                 is.get(cc);
354                                                 c = cc;
355                                                 if (c == '\"' || c == '\\')
356                                                         escaped = true;
357                                                 else
358                                                         buff.push_back('\\');
359                                         }
360                                         buff.push_back(c);
361
362                                         if (!escaped && c == '\"')
363                                                 break;
364                                 } while (c != '\n' && is);
365
366                         } else {
367
368                                 do {
369                                         is.get(cc);
370                                         c = cc;
371                                         if (c != '\r')
372                                                 buff.push_back(c);
373                                 } while (c != '\"' && c != '\n' && is);
374
375                         }
376
377                         if (c != '\"') {
378                                 printError("Missing quote");
379                                 if (c == '\n')
380                                         ++lineno;
381                         }
382
383                         buff.resize(buff.size() - 1);
384                         status = LEX_DATA;
385                         break;
386                 }
387
388                 if (c == ',')
389                         continue;              /* Skip ','s */
390
391                 // using relational operators with chars other
392                 // than == and != is not safe. And if it is done
393                 // the type _have_ to be unsigned. It usually a
394                 // lot better to use the functions from cctype
395                 if (c > ' ' && is)  {
396                         buff.clear();
397
398                         do {
399                                 if (esc && c == '\\') {
400                                         // escape the next char
401                                         is.get(cc);
402                                         c = cc;
403                                         //escaped = true;
404                                 }
405                                 buff.push_back(c);
406                                 is.get(cc);
407                                 c = cc;
408                         } while (c > ' ' && c != ',' && is);
409                         status = LEX_TOKEN;
410                 }
411
412                 if (c == '\r' && is) {
413                         // The Windows support has lead to the
414                         // possibility of "\r\n" at the end of
415                         // a line.  This will stop LyX choking
416                         // when it expected to find a '\n'
417                         is.get(cc);
418                         c = cc;
419                 }
420
421                 if (c == '\n')
422                         ++lineno;
423
424         }
425         if (status)
426                 return true;
427
428         status = is.eof() ? LEX_FEOF: LEX_UNDEF;
429         buff.clear();
430         return false;
431 }
432
433
434 int Lexer::Pimpl::searchKeyword(char const * const tag) const
435 {
436         LexerKeyword search_tag = { tag, 0 };
437         LexerKeyword * res =
438                 lower_bound(table, table + no_items,
439                             search_tag, &compareTags);
440         // use the compare_ascii_no_case instead of compare_no_case,
441         // because in turkish, 'i' is not the lowercase version of 'I',
442         // and thus turkish locale breaks parsing of tags.
443         if (res != table + no_items
444             && !compare_ascii_no_case(res->tag, tag))
445                 return res->code;
446         return LEX_UNDEF;
447 }
448
449
450 int Lexer::Pimpl::lex()
451 {
452         //NOTE: possible bug.
453         if (next() && status == LEX_TOKEN)
454                 return searchKeyword(getString().c_str());
455         return status;
456 }
457
458
459 bool Lexer::Pimpl::eatLine()
460 {
461         buff.clear();
462
463         unsigned char c = '\0';
464         char cc = 0;
465         while (is && c != '\n') {
466                 is.get(cc);
467                 c = cc;
468                 //LYXERR(Debug::LYXLEX, "Lexer::EatLine read char: `" << c << '\'');
469                 if (c != '\r' && is)
470                         buff.push_back(c);
471         }
472
473         if (c == '\n') {
474                 ++lineno;
475                 buff.resize(buff.size() - 1);
476                 status = LEX_DATA;
477                 return true;
478         } else if (buff.length() > 0) { // last line
479                 status = LEX_DATA;
480                 return true;
481         } else {
482                 return false;
483         }
484 }
485
486
487 bool Lexer::Pimpl::nextToken()
488 {
489         if (!pushTok.empty()) {
490                 // There can have been a whole line pushed so
491                 // we extract the first word and leaves the rest
492                 // in pushTok. (Lgb)
493                 if (pushTok[0] == '\\' && pushTok.find(' ') != string::npos) {
494                         buff.clear();
495                         pushTok = split(pushTok, buff, ' ');
496                 } else {
497                         buff = pushTok;
498                         pushTok.clear();
499                 }
500                 status = LEX_TOKEN;
501                 return true;
502         }
503
504         status = 0;
505         while (is && !status) {
506                 unsigned char c = 0;
507                 char cc = 0;
508                 is.get(cc);
509                 c = cc;
510                 if ((c >= ' ' || c == '\t') && is) {
511                         buff.clear();
512
513                         if (c == '\\') { // first char == '\\'
514                                 do {
515                                         buff.push_back(c);
516                                         is.get(cc);
517                                         c = cc;
518                                 } while (c > ' ' && c != '\\' && is);
519                         } else {
520                                 do {
521                                         buff.push_back(c);
522                                         is.get(cc);
523                                         c = cc;
524                                 } while ((c >= ' ' || c == '\t') && c != '\\' && is);
525                         }
526
527                         if (c == '\\')
528                                 is.putback(c); // put it back
529                         status = LEX_TOKEN;
530                 }
531
532                 if (c == '\n')
533                         ++lineno;
534
535         }
536         if (status)
537                 return true;
538
539         status = is.eof() ? LEX_FEOF: LEX_UNDEF;
540         buff.clear();
541         return false;
542 }
543
544
545 bool Lexer::Pimpl::inputAvailable()
546 {
547         return is.good();
548 }
549
550
551 void Lexer::Pimpl::pushToken(string const & pt)
552 {
553         pushTok = pt;
554 }
555
556
557
558
559 //////////////////////////////////////////////////////////////////////
560 //
561 // Lexer
562 //
563 //////////////////////////////////////////////////////////////////////
564
565 Lexer::Lexer()
566         : pimpl_(new Pimpl(nullptr, 0)), lastReadOk_(false)
567 {}
568
569
570 void Lexer::init(LexerKeyword * tab, int num)
571 {
572          pimpl_ = new Pimpl(tab, num);
573 }
574
575
576 Lexer::~Lexer()
577 {
578         delete pimpl_;
579 }
580
581
582 bool Lexer::isOK() const
583 {
584         return pimpl_->inputAvailable();
585 }
586
587
588 void Lexer::setLineNumber(int l)
589 {
590         pimpl_->lineno = l;
591 }
592
593
594 int Lexer::lineNumber() const
595 {
596         return pimpl_->lineno;
597 }
598
599
600 istream & Lexer::getStream()
601 {
602         return pimpl_->is;
603 }
604
605
606 void Lexer::pushTable(LexerKeyword * tab, int num)
607 {
608         pimpl_->pushTable(tab, num);
609 }
610
611
612 void Lexer::popTable()
613 {
614         pimpl_->popTable();
615 }
616
617
618 void Lexer::printTable(ostream & os)
619 {
620         pimpl_->printTable(os);
621 }
622
623
624 void Lexer::printError(string const & message) const
625 {
626         pimpl_->printError(message);
627 }
628
629
630 bool Lexer::setFile(FileName const & filename)
631 {
632         return pimpl_->setFile(filename);
633 }
634
635
636 void Lexer::setStream(istream & i)
637 {
638         pimpl_->setStream(i);
639 }
640
641
642 void Lexer::setCommentChar(char c)
643 {
644         pimpl_->setCommentChar(c);
645 }
646
647
648 int Lexer::lex()
649 {
650         return pimpl_->lex();
651 }
652
653
654 int Lexer::getInteger() const
655 {
656         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
657         if (!lastReadOk_) {
658                 pimpl_->printError("integer token missing");
659                 return -1;
660         }
661
662         if (isStrInt(pimpl_->getString()))
663                 return convert<int>(pimpl_->getString());
664
665         lastReadOk_ = false;
666         pimpl_->printError("Bad integer `$$Token'");
667         return -1;
668 }
669
670
671 double Lexer::getFloat() const
672 {
673         // replace comma with dot in case the file was written with
674         // the wrong locale (should be rare, but is easy enough to
675         // avoid).
676         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
677         if (!lastReadOk_) {
678                 pimpl_->printError("float token missing");
679                 return -1;
680         }
681
682         string const str = subst(pimpl_->getString(), ",", ".");
683         if (isStrDbl(str))
684                 return convert<double>(str);
685
686         lastReadOk_ = false;
687         pimpl_->printError("Bad float `$$Token'");
688         return -1;
689 }
690
691
692 string const Lexer::getString(bool trim) const
693 {
694         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
695
696         if (lastReadOk_)
697                 return trim ? support::trim(pimpl_->getString(), "\t ") : pimpl_->getString();
698
699         return string();
700 }
701
702
703 docstring const Lexer::getDocString(bool trim) const
704 {
705         lastReadOk_ = pimpl_->status == LEX_DATA || pimpl_->status == LEX_TOKEN;
706
707         if (lastReadOk_)
708                 return trim ? support::trim(pimpl_->getDocString(), "\t ") : pimpl_->getDocString();
709
710         return docstring();
711 }
712
713
714 // I would prefer to give a tag number instead of an explicit token
715 // here, but it is not possible because Buffer::readDocument uses
716 // explicit tokens (JMarc)
717 docstring Lexer::getLongString(docstring const & endtoken)
718 {
719         docstring str;
720         docstring prefix;
721         bool firstline = true;
722
723         while (pimpl_->is) { //< eatLine only reads from is, not from pushTok
724                 if (!eatLine())
725                         // blank line in the file being read
726                         continue;
727                 docstring tmpstr = getDocString();
728                 docstring const token = trim(tmpstr, " \t");
729
730                 LYXERR(Debug::PARSER, "LongString: `" << tmpstr << '\'');
731
732                 // We do a case independent comparison, like searchKeyword does.
733                 if (compare_no_case(token, endtoken) == 0)
734                         break;
735
736                 if (firstline) {
737                         size_t i = tmpstr.find_first_not_of(from_ascii(" \t"));
738                         if (i != string::npos)
739                                 prefix = tmpstr.substr(0, i);
740                         firstline = false;
741                         LYXERR(Debug::PARSER, "Prefix = `" << prefix << "\'");
742                 }
743
744                 // further lines in long strings may have the same
745                 // whitespace prefix as the first line. Remove it.
746                 if (!prefix.empty() && prefixIs(tmpstr, prefix))
747                         tmpstr.erase(0, prefix.length());
748
749                 str += tmpstr + '\n';
750         }
751
752         if (!pimpl_->is)
753                 printError("Long string not ended by `" + to_utf8(endtoken) + '\'');
754
755         return str;
756 }
757
758
759 bool Lexer::getBool() const
760 {
761         string const s = pimpl_->getString();
762         if (s == "false" || s == "0") {
763                 lastReadOk_ = true;
764                 return false;
765         }
766         if (s == "true" || s == "1") {
767                 lastReadOk_ = true;
768                 return true;
769         }
770         pimpl_->printError("Bad boolean `$$Token'. "
771                                  "Use \"false\" or \"true\"");
772         lastReadOk_ = false;
773         return false;
774 }
775
776
777 bool Lexer::eatLine()
778 {
779         return pimpl_->eatLine();
780 }
781
782
783 bool Lexer::next(bool esc)
784 {
785         return pimpl_->next(esc);
786 }
787
788
789 bool Lexer::nextToken()
790 {
791         return pimpl_->nextToken();
792 }
793
794
795 void Lexer::pushToken(string const & pt)
796 {
797         pimpl_->pushToken(pt);
798 }
799
800
801 Lexer::operator void const *() const
802 {
803         // This behaviour is NOT the same as the streams which would
804         // use fail() here. However, our implementation of getString() et al.
805         // can cause the eof() and fail() bits to be set, even though we
806         // haven't tried to read 'em.
807         return lastReadOk_? this : nullptr;
808 }
809
810
811 bool Lexer::operator!() const
812 {
813         return !lastReadOk_;
814 }
815
816
817 Lexer & Lexer::operator>>(string & s)
818 {
819         if (isOK()) {
820                 next();
821                 s = getString();
822         } else {
823                 lastReadOk_ = false;
824         }
825         return *this;
826 }
827
828
829 Lexer & Lexer::operator>>(docstring & s)
830 {
831         if (isOK()) {
832                 next();
833                 s = getDocString();
834         } else {
835                 lastReadOk_ = false;
836         }
837         return *this;
838 }
839
840
841 Lexer & Lexer::operator>>(double & s)
842 {
843         if (isOK()) {
844                 next();
845                 s = getFloat();
846         } else {
847                 lastReadOk_ = false;
848         }
849         return *this;
850 }
851
852
853 Lexer & Lexer::operator>>(int & s)
854 {
855         if (isOK()) {
856                 next();
857                 s = getInteger();
858         } else {
859                 lastReadOk_ = false;
860         }
861         return *this;
862 }
863
864
865 Lexer & Lexer::operator>>(unsigned int & s)
866 {
867         if (isOK()) {
868                 next();
869                 s = getInteger();
870         } else {
871                 lastReadOk_ = false;
872         }
873         return *this;
874 }
875
876
877 Lexer & Lexer::operator>>(bool & s)
878 {
879         if (isOK()) {
880                 next();
881                 s = getBool();
882         } else {
883                 lastReadOk_ = false;
884         }
885         return *this;
886 }
887
888
889 Lexer & Lexer::operator>>(char & c)
890 {
891         string s;
892         operator>>(s);
893         if (!s.empty())
894                 c = s[0];
895         return *this;
896 }
897
898
899 // quotes a string, e.g. for use in preferences files or as an argument
900 // of the "log" dialog
901 string Lexer::quoteString(string const & arg)
902 {
903         string res;
904         res += '"';
905         res += subst(subst(arg, "\\", "\\\\"), "\"", "\\\"");
906         res += '"';
907         return res;
908 }
909
910
911 // same for docstring
912 docstring Lexer::quoteString(docstring const & arg)
913 {
914         docstring res;
915         res += '"';
916         res += subst(subst(arg, from_ascii("\\"), from_ascii("\\\\")),
917                      from_ascii("\""), from_ascii("\\\""));
918         res += '"';
919         return res;
920 }
921
922
923 Lexer & Lexer::operator>>(char const * required)
924 {
925         string token;
926         *this >> token;
927         if (token != required) {
928                 LYXERR0("Missing '" << required << "'-tag in " << pimpl_->context
929                         << ". Got " << token << " instead. Line: " << lineNumber());
930                 pushToken(token);
931         }
932         return *this;
933 }
934
935
936 bool Lexer::checkFor(char const * required)
937 {
938         string token;
939         *this >> token;
940         if (token == required)
941                 return true;
942         pushToken(token);
943         return false;
944 }
945
946
947 void Lexer::setContext(std::string const & str)
948 {
949         pimpl_->context = str;
950 }
951
952
953 } // namespace lyx