]> git.lyx.org Git - lyx.git/blob - src/buffer.C
96d5cc2519c294fb1f202da05e481a8f8ae9421b
[lyx.git] / src / buffer.C
1 /**
2  * \file buffer.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  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "buffer.h"
14
15 #include "author.h"
16 #include "buffer_funcs.h"
17 #include "bufferlist.h"
18 #include "bufferparams.h"
19 #include "counters.h"
20 #include "Bullet.h"
21 #include "Chktex.h"
22 #include "debug.h"
23 #include "errorlist.h"
24 #include "exporter.h"
25 #include "format.h"
26 #include "funcrequest.h"
27 #include "gettext.h"
28 #include "iterators.h"
29 #include "language.h"
30 #include "LaTeX.h"
31 #include "LaTeXFeatures.h"
32 #include "LyXAction.h"
33 #include "lyxlex.h"
34 #include "lyxtext.h"
35 #include "lyxrc.h"
36 #include "lyxvc.h"
37 #include "messages.h"
38 #include "output.h"
39 #include "output_docbook.h"
40 #include "output_latex.h"
41 #include "output_linuxdoc.h"
42 #include "paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "PosIterator.h"
46 #include "sgml.h"
47 #include "texrow.h"
48 #include "undo.h"
49 #include "version.h"
50
51 #include "insets/insetbibitem.h"
52 #include "insets/insetbibtex.h"
53 #include "insets/insetinclude.h"
54 #include "insets/insettext.h"
55
56 #include "frontends/Alert.h"
57
58 #include "graphics/Previews.h"
59
60 #include "support/FileInfo.h"
61 #include "support/filetools.h"
62 #include "support/gzstream.h"
63 #include "support/lyxlib.h"
64 #include "support/os.h"
65 #include "support/path.h"
66 #include "support/textutils.h"
67 #include "support/tostr.h"
68
69 #include <boost/bind.hpp>
70
71 #include "support/std_sstream.h"
72
73 #include <iomanip>
74 #include <stack>
75
76 #include <utime.h>
77
78 #ifdef HAVE_LOCALE
79 #endif
80
81 using lyx::pos_type;
82
83 using lyx::support::AddName;
84 using lyx::support::atoi;
85 using lyx::support::bformat;
86 using lyx::support::ChangeExtension;
87 using lyx::support::cmd_ret;
88 using lyx::support::CreateBufferTmpDir;
89 using lyx::support::destroyDir;
90 using lyx::support::FileInfo;
91 using lyx::support::FileInfo;
92 using lyx::support::getExtFromContents;
93 using lyx::support::IsDirWriteable;
94 using lyx::support::IsFileWriteable;
95 using lyx::support::LibFileSearch;
96 using lyx::support::ltrim;
97 using lyx::support::MakeAbsPath;
98 using lyx::support::MakeDisplayPath;
99 using lyx::support::MakeLatexName;
100 using lyx::support::OnlyFilename;
101 using lyx::support::OnlyPath;
102 using lyx::support::Path;
103 using lyx::support::QuoteName;
104 using lyx::support::removeAutosaveFile;
105 using lyx::support::rename;
106 using lyx::support::RunCommand;
107 using lyx::support::split;
108 using lyx::support::strToInt;
109 using lyx::support::subst;
110 using lyx::support::tempName;
111 using lyx::support::trim;
112
113 namespace os = lyx::support::os;
114
115 using std::endl;
116 using std::for_each;
117 using std::make_pair;
118
119 using std::ifstream;
120 using std::ios;
121 using std::ostream;
122 using std::ostringstream;
123 using std::ofstream;
124 using std::pair;
125 using std::stack;
126 using std::vector;
127 using std::string;
128
129
130 // all these externs should eventually be removed.
131 extern BufferList bufferlist;
132
133 namespace {
134
135 const int LYX_FORMAT = 225;
136
137 } // namespace anon
138
139
140 typedef std::map<string, bool> DepClean;
141
142 struct Buffer::Impl
143 {
144         Impl(Buffer & parent, string const & file, bool readonly);
145
146         limited_stack<Undo> undostack;
147         limited_stack<Undo> redostack;
148         BufferParams params;
149         LyXVC lyxvc;
150         string temppath;
151         bool nicefile;
152         TexRow texrow;
153
154         /// need to regenerate .tex ?
155         DepClean dep_clean;
156
157         /// is save needed
158         mutable bool lyx_clean;
159
160         /// is autosave needed
161         mutable bool bak_clean;
162
163         /// is this a unnamed file (New...)
164         bool unnamed;
165
166         /// buffer is r/o
167         bool read_only;
168
169         /// name of the file the buffer is associated with.
170         string filename;
171
172         /// The path to the document file.
173         string filepath;
174
175         boost::scoped_ptr<Messages> messages;
176
177         /** set to true only when the file is fully loaded.
178          *  Used to prevent the premature generation of previews
179          *  and by the citation inset.
180          */
181         bool file_fully_loaded;
182
183         /// our Text
184         LyXText text;
185 };
186
187
188 Buffer::Impl::Impl(Buffer & parent, string const & file, bool readonly_)
189         : nicefile(true),
190           lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
191           filename(file), filepath(OnlyPath(file)), file_fully_loaded(false),
192                 text(0, 0)
193 {
194         lyxvc.buffer(&parent);
195         if (readonly_ || lyxrc.use_tempdir)
196                 temppath = CreateBufferTmpDir();
197 }
198
199
200 Buffer::Buffer(string const & file, bool ronly)
201         : pimpl_(new Impl(*this, file, ronly))
202 {
203         lyxerr[Debug::INFO] << "Buffer::Buffer()" << endl;
204 }
205
206
207 Buffer::~Buffer()
208 {
209         lyxerr[Debug::INFO] << "Buffer::~Buffer()" << endl;
210         // here the buffer should take care that it is
211         // saved properly, before it goes into the void.
212
213         closing();
214
215         if (!temppath().empty() && destroyDir(temppath()) != 0) {
216                 Alert::warning(_("Could not remove temporary directory"),
217                         bformat(_("Could not remove the temporary directory %1$s"), temppath()));
218         }
219
220         // Remove any previewed LaTeX snippets associated with this buffer.
221         lyx::graphics::Previews::get().removeLoader(*this);
222 }
223
224
225 LyXText & Buffer::text() const
226 {
227         return const_cast<LyXText &>(pimpl_->text);
228 }
229
230
231 limited_stack<Undo> & Buffer::undostack()
232 {
233         return pimpl_->undostack;
234 }
235
236
237 limited_stack<Undo> const & Buffer::undostack() const
238 {
239         return pimpl_->undostack;
240 }
241
242
243 limited_stack<Undo> & Buffer::redostack()
244 {
245         return pimpl_->redostack;
246 }
247
248
249 limited_stack<Undo> const & Buffer::redostack() const
250 {
251         return pimpl_->redostack;
252 }
253
254
255 BufferParams & Buffer::params()
256 {
257         return pimpl_->params;
258 }
259
260
261 BufferParams const & Buffer::params() const
262 {
263         return pimpl_->params;
264 }
265
266
267 ParagraphList & Buffer::paragraphs()
268 {
269         return pimpl_->text.paragraphs();
270 }
271
272
273 ParagraphList const & Buffer::paragraphs() const
274 {
275         return pimpl_->text.paragraphs();
276 }
277
278
279 LyXVC & Buffer::lyxvc()
280 {
281         return pimpl_->lyxvc;
282 }
283
284
285 LyXVC const & Buffer::lyxvc() const
286 {
287         return pimpl_->lyxvc;
288 }
289
290
291 string const & Buffer::temppath() const
292 {
293         return pimpl_->temppath;
294 }
295
296
297 bool & Buffer::niceFile()
298 {
299         return pimpl_->nicefile;
300 }
301
302
303 bool Buffer::niceFile() const
304 {
305         return pimpl_->nicefile;
306 }
307
308
309 TexRow & Buffer::texrow()
310 {
311         return pimpl_->texrow;
312 }
313
314
315 TexRow const & Buffer::texrow() const
316 {
317         return pimpl_->texrow;
318 }
319
320
321 string const Buffer::getLatexName(bool no_path) const
322 {
323         string const name = ChangeExtension(MakeLatexName(fileName()), ".tex");
324         return no_path ? OnlyFilename(name) : name;
325 }
326
327
328 pair<Buffer::LogType, string> const Buffer::getLogName() const
329 {
330         string const filename = getLatexName(false);
331
332         if (filename.empty())
333                 return make_pair(Buffer::latexlog, string());
334
335         string path = OnlyPath(filename);
336
337         if (lyxrc.use_tempdir || !IsDirWriteable(path))
338                 path = temppath();
339
340         string const fname = AddName(path,
341                                      OnlyFilename(ChangeExtension(filename,
342                                                                   ".log")));
343         string const bname =
344                 AddName(path, OnlyFilename(
345                         ChangeExtension(filename,
346                                         formats.extension("literate") + ".out")));
347
348         // If no Latex log or Build log is newer, show Build log
349
350         FileInfo const f_fi(fname);
351         FileInfo const b_fi(bname);
352
353         if (b_fi.exist() &&
354             (!f_fi.exist() || f_fi.getModificationTime() < b_fi.getModificationTime())) {
355                 lyxerr[Debug::FILES] << "Log name calculated as: " << bname << endl;
356                 return make_pair(Buffer::buildlog, bname);
357         }
358         lyxerr[Debug::FILES] << "Log name calculated as: " << fname << endl;
359         return make_pair(Buffer::latexlog, fname);
360 }
361
362
363 void Buffer::setReadonly(bool flag)
364 {
365         if (pimpl_->read_only != flag) {
366                 pimpl_->read_only = flag;
367                 readonly(flag);
368         }
369 }
370
371
372 void Buffer::setFileName(string const & newfile)
373 {
374         pimpl_->filename = MakeAbsPath(newfile);
375         pimpl_->filepath = OnlyPath(pimpl_->filename);
376         setReadonly(IsFileWriteable(pimpl_->filename) == 0);
377         updateTitles();
378 }
379
380
381 // We'll remove this later. (Lgb)
382 namespace {
383
384 void unknownClass(string const & unknown)
385 {
386         Alert::warning(_("Unknown document class"),
387                 bformat(_("Using the default document class, because the "
388                         "class %1$s is unknown."), unknown));
389 }
390
391 } // anon
392
393 int Buffer::readHeader(LyXLex & lex)
394 {
395         int unknown_tokens = 0;
396
397         while (lex.isOK()) {
398                 lex.nextToken();
399                 string const token = lex.getString();
400
401                 if (token.empty())
402                         continue;
403
404                 if (token == "\\end_header")
405                         break;
406
407                 lyxerr[Debug::PARSER] << "Handling header token: `"
408                                       << token << '\'' << endl;
409
410
411                 string unknown = params().readToken(lex, token);
412                 if (!unknown.empty()) {
413                         if (unknown[0] != '\\') {
414                                 unknownClass(unknown);
415                         } else {
416                                 ++unknown_tokens;
417                                 string const s = bformat(_("Unknown token: "
418                                                            "%1$s %2$s\n"),
419                                                          token,
420                                                          lex.getString());
421                                 error(ErrorItem(_("Header error"), s,
422                                                 -1, 0, 0));
423                         }
424                 }
425         }
426         return unknown_tokens;
427 }
428
429
430 // candidate for move to BufferView
431 // (at least some parts in the beginning of the func)
432 //
433 // Uwe C. Schroeder
434 // changed to be public and have one parameter
435 // if par = 0 normal behavior
436 // else insert behavior
437 // Returns false if "\end_document" is not read (Asger)
438 bool Buffer::readBody(LyXLex & lex, ParagraphList::iterator pit)
439 {
440         Paragraph::depth_type depth = 0;
441         bool the_end_read = false;
442
443         if (paragraphs().empty()) {
444                 readHeader(lex);
445                 if (!params().getLyXTextClass().load()) {
446                         string theclass = params().getLyXTextClass().name();
447                         Alert::error(_("Can't load document class"), bformat(
448                                         "Using the default document class, because the "
449                                         " class %1$s could not be loaded.", theclass));
450                         params().textclass = 0;
451                 }
452         } else {
453                 // We don't want to adopt the parameters from the
454                 // document we insert, so read them into a temporary buffer
455                 // and then discard it
456
457                 Buffer tmpbuf("", false);
458                 tmpbuf.readHeader(lex);
459         }
460
461         while (lex.isOK()) {
462                 lex.nextToken();
463                 string const token = lex.getString();
464
465                 if (token.empty())
466                         continue;
467
468                 lyxerr[Debug::PARSER] << "Handling token: `"
469                                       << token << '\'' << endl;
470
471                 if (token == "\\end_document") {
472                         the_end_read = true;
473                         continue;
474                 }
475
476                 readParagraph(lex, token, paragraphs(), pit, depth);
477         }
478
479         return the_end_read;
480 }
481
482
483 int Buffer::readParagraph(LyXLex & lex, string const & token,
484                           ParagraphList & pars, ParagraphList::iterator & pit,
485                           lyx::depth_type & depth)
486 {
487         static Change current_change;
488         int unknown = 0;
489
490         if (token == "\\begin_layout") {
491                 lex.pushToken(token);
492
493                 Paragraph par;
494                 par.params().depth(depth);
495                 if (params().tracking_changes)
496                         par.trackChanges();
497                 LyXFont f(LyXFont::ALL_INHERIT, params().language);
498                 par.setFont(0, f);
499
500                 // insert after
501                 if (pit != pars.end())
502                         ++pit;
503
504                 pit = pars.insert(pit, par);
505
506                 // FIXME: goddamn InsetTabular makes us pass a Buffer
507                 // not BufferParams
508                 ::readParagraph(*this, *pit, lex);
509
510         } else if (token == "\\begin_deeper") {
511                 ++depth;
512         } else if (token == "\\end_deeper") {
513                 if (!depth) {
514                         lex.printError("\\end_deeper: " "depth is already null");
515                 } else {
516                         --depth;
517                 }
518         } else {
519                 ++unknown;
520         }
521         return unknown;
522 }
523
524
525 // needed to insert the selection
526 void Buffer::insertStringAsLines(ParagraphList::iterator & par, pos_type & pos,
527                                  LyXFont const & fn, string const & str)
528 {
529         LyXLayout_ptr const & layout = par->layout();
530
531         LyXFont font = fn;
532
533         par->checkInsertChar(font);
534         // insert the string, don't insert doublespace
535         bool space_inserted = true;
536         bool autobreakrows = !par->inInset() ||
537                 static_cast<InsetText *>(par->inInset())->getAutoBreakRows();
538         for(string::const_iterator cit = str.begin();
539             cit != str.end(); ++cit) {
540                 if (*cit == '\n') {
541                         if (autobreakrows && (!par->empty() || par->allowEmpty())) {
542                                 breakParagraph(params(), paragraphs(), par, pos,
543                                                layout->isEnvironment());
544                                 ++par;
545                                 pos = 0;
546                                 space_inserted = true;
547                         } else {
548                                 continue;
549                         }
550                         // do not insert consecutive spaces if !free_spacing
551                 } else if ((*cit == ' ' || *cit == '\t') &&
552                            space_inserted && !par->isFreeSpacing()) {
553                         continue;
554                 } else if (*cit == '\t') {
555                         if (!par->isFreeSpacing()) {
556                                 // tabs are like spaces here
557                                 par->insertChar(pos, ' ', font);
558                                 ++pos;
559                                 space_inserted = true;
560                         } else {
561                                 const pos_type nb = 8 - pos % 8;
562                                 for (pos_type a = 0; a < nb ; ++a) {
563                                         par->insertChar(pos, ' ', font);
564                                         ++pos;
565                                 }
566                                 space_inserted = true;
567                         }
568                 } else if (!IsPrintable(*cit)) {
569                         // Ignore unprintables
570                         continue;
571                 } else {
572                         // just insert the character
573                         par->insertChar(pos, *cit, font);
574                         ++pos;
575                         space_inserted = (*cit == ' ');
576                 }
577
578         }
579 }
580
581
582 bool Buffer::readFile(string const & filename)
583 {
584         // Check if the file is compressed.
585         string const format = getExtFromContents(filename);
586         if (format == "gzip" || format == "zip" || format == "compress") {
587                 params().compressed = true;
588         }
589
590         bool ret = readFile(filename, paragraphs().begin());
591
592         // After we have read a file, we must ensure that the buffer
593         // language is set and used in the gui.
594         // If you know of a better place to put this, please tell me. (Lgb)
595         updateDocLang(params().language);
596
597         return ret;
598 }
599
600
601 bool Buffer::readFile(string const & filename, ParagraphList::iterator pit)
602 {
603         LyXLex lex(0, 0);
604         lex.setFile(filename);
605
606         return readFile(lex, filename, pit);
607 }
608
609
610 bool Buffer::fully_loaded() const
611 {
612         return pimpl_->file_fully_loaded;
613 }
614
615
616 void Buffer::fully_loaded(bool value)
617 {
618         pimpl_->file_fully_loaded = value;
619 }
620
621
622 bool Buffer::readFile(LyXLex & lex, string const & filename,
623                       ParagraphList::iterator pit)
624 {
625         BOOST_ASSERT(!filename.empty());
626
627         if (!lex.isOK()) {
628                 Alert::error(_("Document could not be read"),
629                              bformat(_("%1$s could not be read."), filename));
630                 return false;
631         }
632
633         lex.next();
634         string const token(lex.getString());
635
636         if (!lex.isOK()) {
637                 Alert::error(_("Document could not be read"),
638                              bformat(_("%1$s could not be read."), filename));
639                 return false;
640         }
641
642         // the first token _must_ be...
643         if (token != "\\lyxformat") {
644                 lyxerr << "Token: " << token << endl;
645
646                 Alert::error(_("Document format failure"),
647                              bformat(_("%1$s is not a LyX document."),
648                                        filename));
649                 return false;
650         }
651
652         lex.eatLine();
653         string tmp_format = lex.getString();
654         //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
655         // if present remove ".," from string.
656         string::size_type dot = tmp_format.find_first_of(".,");
657         //lyxerr << "           dot found at " << dot << endl;
658         if (dot != string::npos)
659                         tmp_format.erase(dot, 1);
660         int file_format = strToInt(tmp_format);
661         //lyxerr << "format: " << file_format << endl;
662
663         if (file_format > LYX_FORMAT) {
664                 Alert::warning(_("Document format failure"),
665                                bformat(_("%1$s was created with a newer"
666                                          " version of LyX. This is likely to"
667                                          " cause problems."),
668                                          filename));
669         } else if (file_format < LYX_FORMAT) {
670                 string const tmpfile = tempName();
671                 string command = LibFileSearch("lyx2lyx", "lyx2lyx");
672                 if (command.empty()) {
673                         Alert::error(_("Conversion script not found"),
674                                      bformat(_("%1$s is from an earlier"
675                                                " version of LyX, but the"
676                                                " conversion script lyx2lyx"
677                                                " could not be found."),
678                                                filename));
679                         return false;
680                 }
681                 command += " -t"
682                         + tostr(LYX_FORMAT)
683                         + " -o " + tmpfile + ' '
684                         + QuoteName(filename);
685                 lyxerr[Debug::INFO] << "Running '"
686                                     << command << '\''
687                                     << endl;
688                 cmd_ret const ret = RunCommand(command);
689                 if (ret.first != 0) {
690                         Alert::error(_("Conversion script failed"),
691                                      bformat(_("%1$s is from an earlier version"
692                                               " of LyX, but the lyx2lyx script"
693                                               " failed to convert it."),
694                                               filename));
695                         return false;
696                 } else {
697                         bool ret = readFile(tmpfile, pit);
698                         // Do stuff with tmpfile name and buffer name here.
699                         return ret;
700                 }
701
702         }
703
704         bool the_end = readBody(lex, pit);
705         params().setPaperStuff();
706
707         if (!the_end) {
708                 Alert::error(_("Document format failure"),
709                              bformat(_("%1$s ended unexpectedly, which means"
710                                        " that it is probably corrupted."),
711                                        filename));
712         }
713         pimpl_->file_fully_loaded = true;
714         return true;
715 }
716
717
718 // Should probably be moved to somewhere else: BufferView? LyXView?
719 bool Buffer::save() const
720 {
721         // We don't need autosaves in the immediate future. (Asger)
722         resetAutosaveTimers();
723
724         // make a backup
725         string s;
726         if (lyxrc.make_backup) {
727                 s = fileName() + '~';
728                 if (!lyxrc.backupdir_path.empty())
729                         s = AddName(lyxrc.backupdir_path,
730                                     subst(os::slashify_path(s),'/','!'));
731
732                 // Rename is the wrong way of making a backup,
733                 // this is the correct way.
734                 /* truss cp fil fil2:
735                    lstat("LyXVC3.lyx", 0xEFFFF898)                 Err#2 ENOENT
736                    stat("LyXVC.lyx", 0xEFFFF688)                   = 0
737                    open("LyXVC.lyx", O_RDONLY)                     = 3
738                    open("LyXVC3.lyx", O_WRONLY|O_CREAT|O_TRUNC, 0600) = 4
739                    fstat(4, 0xEFFFF508)                            = 0
740                    fstat(3, 0xEFFFF508)                            = 0
741                    read(3, " # T h i s   f i l e   w".., 8192)     = 5579
742                    write(4, " # T h i s   f i l e   w".., 5579)    = 5579
743                    read(3, 0xEFFFD4A0, 8192)                       = 0
744                    close(4)                                        = 0
745                    close(3)                                        = 0
746                    chmod("LyXVC3.lyx", 0100644)                    = 0
747                    lseek(0, 0, SEEK_CUR)                           = 46440
748                    _exit(0)
749                 */
750
751                 // Should probably have some more error checking here.
752                 // Doing it this way, also makes the inodes stay the same.
753                 // This is still not a very good solution, in particular we
754                 // might loose the owner of the backup.
755                 FileInfo finfo(fileName());
756                 if (finfo.exist()) {
757                         mode_t fmode = finfo.getMode();
758                         struct utimbuf times = {
759                                 finfo.getAccessTime(),
760                                 finfo.getModificationTime() };
761
762                         ifstream ifs(fileName().c_str());
763                         ofstream ofs(s.c_str(), ios::out|ios::trunc);
764                         if (ifs && ofs) {
765                                 ofs << ifs.rdbuf();
766                                 ifs.close();
767                                 ofs.close();
768                                 ::chmod(s.c_str(), fmode);
769
770                                 if (::utime(s.c_str(), &times)) {
771                                         lyxerr << "utime error." << endl;
772                                 }
773                         } else {
774                                 lyxerr << "LyX was not able to make "
775                                         "backup copy. Beware." << endl;
776                         }
777                 }
778         }
779
780         if (writeFile(fileName())) {
781                 markClean();
782                 removeAutosaveFile(fileName());
783         } else {
784                 // Saving failed, so backup is not backup
785                 if (lyxrc.make_backup) {
786                         rename(s, fileName());
787                 }
788                 return false;
789         }
790         return true;
791 }
792
793
794 bool Buffer::writeFile(string const & fname) const
795 {
796         if (pimpl_->read_only && (fname == fileName())) {
797                 return false;
798         }
799
800         FileInfo finfo(fname);
801         if (finfo.exist() && !finfo.writable()) {
802                 return false;
803         }
804
805         bool retval;
806
807         if (params().compressed) {
808                 gz::ogzstream ofs(fname.c_str());
809
810                 if (!ofs)
811                         return false;
812
813                 retval = do_writeFile(ofs);
814
815         } else {
816                 ofstream ofs(fname.c_str());
817                 if (!ofs)
818                         return false;
819
820                 retval = do_writeFile(ofs);
821         }
822
823         return retval;
824 }
825
826
827 bool Buffer::do_writeFile(ostream & ofs) const
828 {
829
830 #ifdef HAVE_LOCALE
831         // Use the standard "C" locale for file output.
832         ofs.imbue(std::locale::classic());
833 #endif
834
835         // The top of the file should not be written by params().
836
837         // write out a comment in the top of the file
838         ofs << "#LyX " << lyx_version
839             << " created this file. For more info see http://www.lyx.org/\n"
840             << "\\lyxformat " << LYX_FORMAT << "\n";
841
842         // now write out the buffer paramters.
843         params().writeFile(ofs);
844
845         ofs << "\\end_header\n";
846
847         Paragraph::depth_type depth = 0;
848
849         // this will write out all the paragraphs
850         // using recursive descent.
851         ParagraphList::const_iterator pit = paragraphs().begin();
852         ParagraphList::const_iterator pend = paragraphs().end();
853         for (; pit != pend; ++pit)
854                 pit->write(*this, ofs, params(), depth);
855
856         // Write marker that shows file is complete
857         ofs << "\n\\end_document" << endl;
858
859         // Shouldn't really be needed....
860         //ofs.close();
861
862         // how to check if close went ok?
863         // Following is an attempt... (BE 20001011)
864
865         // good() returns false if any error occured, including some
866         //        formatting error.
867         // bad()  returns true if something bad happened in the buffer,
868         //        which should include file system full errors.
869
870         bool status = true;
871         if (!ofs.good()) {
872                 status = false;
873 #if 0
874                 if (ofs.bad()) {
875                         lyxerr << "Buffer::writeFile: BAD ERROR!" << endl;
876                 } else {
877                         lyxerr << "Buffer::writeFile: NOT SO BAD ERROR!"
878                                << endl;
879                 }
880 #endif
881         }
882
883         return status;
884 }
885
886
887 void Buffer::makeLaTeXFile(string const & fname,
888                            string const & original_path,
889                            OutputParams const & runparams,
890                            bool output_preamble, bool output_body)
891 {
892         lyxerr[Debug::LATEX] << "makeLaTeXFile..." << endl;
893
894         ofstream ofs;
895         if (!openFileWrite(ofs, fname))
896                 return;
897
898         makeLaTeXFile(ofs, original_path,
899                       runparams, output_preamble, output_body);
900
901         ofs.close();
902         if (ofs.fail()) {
903                 lyxerr << "File was not closed properly." << endl;
904         }
905 }
906
907
908 void Buffer::makeLaTeXFile(ostream & os,
909                            string const & original_path,
910                            OutputParams const & runparams_in,
911                            bool output_preamble, bool output_body)
912 {
913         OutputParams runparams = runparams_in;
914         niceFile() = runparams.nice; // this will be used by Insetincludes.
915
916         // validate the buffer.
917         lyxerr[Debug::LATEX] << "  Validating buffer..." << endl;
918         LaTeXFeatures features(*this, params());
919         validate(features);
920         lyxerr[Debug::LATEX] << "  Buffer validation done." << endl;
921
922         texrow().reset();
923         // The starting paragraph of the coming rows is the
924         // first paragraph of the document. (Asger)
925         texrow().start(paragraphs().begin()->id(), 0);
926
927         if (output_preamble && runparams.nice) {
928                 os << "%% LyX " << lyx_version << " created this file.  "
929                         "For more info, see http://www.lyx.org/.\n"
930                         "%% Do not edit unless you really know what "
931                         "you are doing.\n";
932                 texrow().newline();
933                 texrow().newline();
934         }
935         lyxerr[Debug::INFO] << "lyx header finished" << endl;
936         // There are a few differences between nice LaTeX and usual files:
937         // usual is \batchmode and has a
938         // special input@path to allow the including of figures
939         // with either \input or \includegraphics (what figinsets do).
940         // input@path is set when the actual parameter
941         // original_path is set. This is done for usual tex-file, but not
942         // for nice-latex-file. (Matthias 250696)
943         if (output_preamble) {
944                 if (!runparams.nice) {
945                         // code for usual, NOT nice-latex-file
946                         os << "\\batchmode\n"; // changed
947                         // from \nonstopmode
948                         texrow().newline();
949                 }
950                 if (!original_path.empty()) {
951                         string inputpath = os::external_path(original_path);
952                         subst(inputpath, "~", "\\string~");
953                         os << "\\makeatletter\n"
954                             << "\\def\\input@path{{"
955                             << inputpath << "/}}\n"
956                             << "\\makeatother\n";
957                         texrow().newline();
958                         texrow().newline();
959                         texrow().newline();
960                 }
961
962                 // Write the preamble
963                 runparams.use_babel = params().writeLaTeX(os, features, texrow());
964
965                 if (!output_body)
966                         return;
967
968                 // make the body.
969                 os << "\\begin{document}\n";
970                 texrow().newline();
971         } // output_preamble
972         lyxerr[Debug::INFO] << "preamble finished, now the body." << endl;
973
974         if (!lyxrc.language_auto_begin) {
975                 os << subst(lyxrc.language_command_begin, "$$lang",
976                              params().language->babel())
977                     << endl;
978                 texrow().newline();
979         }
980
981         latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
982
983         // add this just in case after all the paragraphs
984         os << endl;
985         texrow().newline();
986
987         if (!lyxrc.language_auto_end) {
988                 os << subst(lyxrc.language_command_end, "$$lang",
989                              params().language->babel())
990                     << endl;
991                 texrow().newline();
992         }
993
994         if (output_preamble) {
995                 os << "\\end{document}\n";
996                 texrow().newline();
997
998                 lyxerr[Debug::LATEX] << "makeLaTeXFile...done" << endl;
999         } else {
1000                 lyxerr[Debug::LATEX] << "LaTeXFile for inclusion made."
1001                                      << endl;
1002         }
1003
1004         // Just to be sure. (Asger)
1005         texrow().newline();
1006
1007         lyxerr[Debug::INFO] << "Finished making LaTeX file." << endl;
1008         lyxerr[Debug::INFO] << "Row count was " << texrow().rows() - 1
1009                             << '.' << endl;
1010
1011         // we want this to be true outside previews (for insetexternal)
1012         niceFile() = true;
1013 }
1014
1015
1016 bool Buffer::isLatex() const
1017 {
1018         return params().getLyXTextClass().outputType() == LATEX;
1019 }
1020
1021
1022 bool Buffer::isLinuxDoc() const
1023 {
1024         return params().getLyXTextClass().outputType() == LINUXDOC;
1025 }
1026
1027
1028 bool Buffer::isLiterate() const
1029 {
1030         return params().getLyXTextClass().outputType() == LITERATE;
1031 }
1032
1033
1034 bool Buffer::isDocBook() const
1035 {
1036         return params().getLyXTextClass().outputType() == DOCBOOK;
1037 }
1038
1039
1040 bool Buffer::isSGML() const
1041 {
1042         LyXTextClass const & tclass = params().getLyXTextClass();
1043
1044         return tclass.outputType() == LINUXDOC ||
1045                tclass.outputType() == DOCBOOK;
1046 }
1047
1048
1049 void Buffer::makeLinuxDocFile(string const & fname,
1050                               OutputParams const & runparams,
1051                               bool body_only )
1052 {
1053         ofstream ofs;
1054         if (!openFileWrite(ofs, fname))
1055                 return;
1056
1057         niceFile() = runparams.nice; // this will be used by included files.
1058
1059         LaTeXFeatures features(*this, params());
1060
1061         validate(features);
1062
1063         texrow().reset();
1064
1065         LyXTextClass const & tclass = params().getLyXTextClass();
1066
1067         string top_element = tclass.latexname();
1068
1069         if (!body_only) {
1070                 ofs << tclass.class_header();
1071
1072                 string preamble = params().preamble;
1073                 string const name = runparams.nice ? ChangeExtension(pimpl_->filename, ".sgml")
1074                          : fname;
1075                 preamble += features.getIncludedFiles(name);
1076                 preamble += features.getLyXSGMLEntities();
1077
1078                 if (!preamble.empty()) {
1079                         ofs << " [ " << preamble << " ]";
1080                 }
1081                 ofs << ">\n\n";
1082
1083                 if (params().options.empty())
1084                         sgml::openTag(ofs, 0, false, top_element);
1085                 else {
1086                         string top = top_element;
1087                         top += ' ';
1088                         top += params().options;
1089                         sgml::openTag(ofs, 0, false, top);
1090                 }
1091         }
1092
1093         ofs << "<!-- LyX "  << lyx_version
1094             << " created this file. For more info see http://www.lyx.org/"
1095             << " -->\n";
1096
1097         linuxdocParagraphs(*this, paragraphs(), ofs, runparams);
1098
1099         if (!body_only) {
1100                 ofs << "\n\n";
1101                 sgml::closeTag(ofs, 0, false, top_element);
1102         }
1103
1104         ofs.close();
1105         // How to check for successful close
1106
1107         // we want this to be true outside previews (for insetexternal)
1108         niceFile() = true;
1109 }
1110
1111
1112 void Buffer::makeDocBookFile(string const & fname,
1113                              OutputParams const & runparams,
1114                              bool only_body)
1115 {
1116         ofstream ofs;
1117         if (!openFileWrite(ofs, fname))
1118                 return;
1119
1120         niceFile() = runparams.nice; // this will be used by Insetincludes.
1121
1122         LaTeXFeatures features(*this, params());
1123         validate(features);
1124
1125         texrow().reset();
1126
1127         LyXTextClass const & tclass = params().getLyXTextClass();
1128         string top_element = tclass.latexname();
1129
1130         if (!only_body) {
1131                 ofs << subst(tclass.class_header(), "#", top_element);
1132
1133                 string preamble = params().preamble;
1134                 string const name = runparams.nice ? ChangeExtension(pimpl_->filename, ".sgml")
1135                          : fname;
1136                 preamble += features.getIncludedFiles(name);
1137                 preamble += features.getLyXSGMLEntities();
1138
1139                 if (!preamble.empty()) {
1140                         ofs << "\n [ " << preamble << " ]";
1141                 }
1142                 ofs << ">\n\n";
1143         }
1144
1145         string top = top_element;
1146         top += " lang=\"";
1147         top += params().language->code();
1148         top += '"';
1149
1150         if (!params().options.empty()) {
1151                 top += ' ';
1152                 top += params().options;
1153         }
1154         sgml::openTag(ofs, 0, false, top);
1155
1156         ofs << "<!-- SGML/XML file was created by LyX " << lyx_version
1157             << "\n  See http://www.lyx.org/ for more information -->\n";
1158
1159         params().getLyXTextClass().counters().reset();
1160         docbookParagraphs(*this, paragraphs(), ofs, runparams);
1161
1162         ofs << "\n\n";
1163         sgml::closeTag(ofs, 0, false, top_element);
1164
1165         ofs.close();
1166         // How to check for successful close
1167
1168         // we want this to be true outside previews (for insetexternal)
1169         niceFile() = true;
1170 }
1171
1172
1173 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1174 // Other flags: -wall -v0 -x
1175 int Buffer::runChktex()
1176 {
1177         busy(true);
1178
1179         // get LaTeX-Filename
1180         string const name = getLatexName();
1181         string path = filePath();
1182
1183         string const org_path = path;
1184         if (lyxrc.use_tempdir || !IsDirWriteable(path)) {
1185                 path = temppath();
1186         }
1187
1188         Path p(path); // path to LaTeX file
1189         message(_("Running chktex..."));
1190
1191         // Generate the LaTeX file if neccessary
1192         OutputParams runparams;
1193         runparams.flavor = OutputParams::LATEX;
1194         runparams.nice = false;
1195         makeLaTeXFile(name, org_path, runparams);
1196
1197         TeXErrors terr;
1198         Chktex chktex(lyxrc.chktex_command, name, filePath());
1199         int res = chktex.run(terr); // run chktex
1200
1201         if (res == -1) {
1202                 Alert::error(_("chktex failure"),
1203                              _("Could not run chktex successfully."));
1204         } else if (res > 0) {
1205                 // Insert all errors as errors boxes
1206                 bufferErrors(*this, terr);
1207         }
1208
1209         busy(false);
1210
1211         return res;
1212 }
1213
1214
1215 void Buffer::validate(LaTeXFeatures & features) const
1216 {
1217         LyXTextClass const & tclass = params().getLyXTextClass();
1218
1219         if (params().tracking_changes) {
1220                 features.require("dvipost");
1221                 features.require("color");
1222         }
1223
1224         // AMS Style is at document level
1225         if (params().use_amsmath == BufferParams::AMS_ON
1226             || tclass.provides(LyXTextClass::amsmath))
1227                 features.require("amsmath");
1228
1229         for_each(paragraphs().begin(), paragraphs().end(),
1230                  boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1231
1232         // the bullet shapes are buffer level not paragraph level
1233         // so they are tested here
1234         for (int i = 0; i < 4; ++i) {
1235                 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1236                         int const font = params().user_defined_bullet(i).getFont();
1237                         if (font == 0) {
1238                                 int const c = params()
1239                                         .user_defined_bullet(i)
1240                                         .getCharacter();
1241                                 if (c == 16
1242                                    || c == 17
1243                                    || c == 25
1244                                    || c == 26
1245                                    || c == 31) {
1246                                         features.require("latexsym");
1247                                 }
1248                         } else if (font == 1) {
1249                                 features.require("amssymb");
1250                         } else if ((font >= 2 && font <= 5)) {
1251                                 features.require("pifont");
1252                         }
1253                 }
1254         }
1255
1256         if (lyxerr.debugging(Debug::LATEX)) {
1257                 features.showStruct();
1258         }
1259 }
1260
1261
1262 void Buffer::getLabelList(std::vector<string> & list) const
1263 {
1264         /// if this is a child document and the parent is already loaded
1265         /// Use the parent's list instead  [ale990407]
1266         if (!params().parentname.empty()
1267             && bufferlist.exists(params().parentname)) {
1268                 Buffer const * tmp = bufferlist.getBuffer(params().parentname);
1269                 if (tmp) {
1270                         tmp->getLabelList(list);
1271                         return;
1272                 }
1273         }
1274
1275         for (inset_iterator it = inset_const_iterator_begin();
1276              it != inset_const_iterator_end(); ++it) {
1277                 it->getLabelList(*this, list);
1278         }
1279 }
1280
1281
1282 // This is also a buffer property (ale)
1283 void Buffer::fillWithBibKeys(std::vector<std::pair<string, string> > & keys) const
1284 {
1285         /// if this is a child document and the parent is already loaded
1286         /// use the parent's list instead  [ale990412]
1287         if (!params().parentname.empty() &&
1288             bufferlist.exists(params().parentname)) {
1289                 Buffer const * tmp = bufferlist.getBuffer(params().parentname);
1290                 if (tmp) {
1291                         tmp->fillWithBibKeys(keys);
1292                         return;
1293                 }
1294         }
1295
1296         for (inset_iterator it = inset_const_iterator_begin();
1297                 it != inset_const_iterator_end(); ++it) {
1298                 if (it->lyxCode() == InsetOld::BIBTEX_CODE) {
1299                         InsetBibtex const & inset =
1300                                 dynamic_cast<InsetBibtex const &>(*it);
1301                         inset.fillWithBibKeys(*this, keys);
1302                 } else if (it->lyxCode() == InsetOld::INCLUDE_CODE) {
1303                         InsetInclude const & inset =
1304                                 dynamic_cast<InsetInclude const &>(*it);
1305                         inset.fillWithBibKeys(*this, keys);
1306                 } else if (it->lyxCode() == InsetOld::BIBITEM_CODE) {
1307                         InsetBibitem const & inset =
1308                                 dynamic_cast<InsetBibitem const &>(*it);
1309                         string const key = inset.getContents();
1310                         string const opt = inset.getOptions();
1311                         string const ref; // = pit->asString(this, false);
1312                         string const info = opt + "TheBibliographyRef" + ref;
1313                         keys.push_back(pair<string, string>(key, info));
1314                 }
1315         }
1316 }
1317
1318
1319 bool Buffer::isDepClean(string const & name) const
1320 {
1321         DepClean::const_iterator it = pimpl_->dep_clean.find(name);
1322         if (it == pimpl_->dep_clean.end())
1323                 return true;
1324         return it->second;
1325 }
1326
1327
1328 void Buffer::markDepClean(string const & name)
1329 {
1330         pimpl_->dep_clean[name] = true;
1331 }
1332
1333
1334 bool Buffer::dispatch(string const & command, bool * result)
1335 {
1336         return dispatch(lyxaction.lookupFunc(command), result);
1337 }
1338
1339
1340 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1341 {
1342         bool dispatched = true;
1343
1344         switch (func.action) {
1345                 case LFUN_EXPORT: {
1346                         bool const tmp = Exporter::Export(this, func.argument, false);
1347                         if (result)
1348                                 *result = tmp;
1349                         break;
1350                 }
1351
1352                 default:
1353                         dispatched = false;
1354         }
1355         return dispatched;
1356 }
1357
1358
1359 void Buffer::changeLanguage(Language const * from, Language const * to)
1360 {
1361         lyxerr << "Changing Language!" << endl;
1362
1363         // Take care of l10n/i18n
1364         updateDocLang(to);
1365
1366         ParIterator end = par_iterator_end();
1367         for (ParIterator it = par_iterator_begin(); it != end; ++it)
1368                 it->changeLanguage(params(), from, to);
1369 }
1370
1371
1372 void Buffer::updateDocLang(Language const * nlang)
1373 {
1374         pimpl_->messages.reset(new Messages(nlang->code()));
1375 }
1376
1377
1378 bool Buffer::isMultiLingual()
1379 {
1380         ParIterator end = par_iterator_end();
1381         for (ParIterator it = par_iterator_begin(); it != end; ++it)
1382                 if (it->isMultiLingual(params()))
1383                         return true;
1384
1385         return false;
1386 }
1387
1388
1389 void Buffer::inset_iterator::setParagraph()
1390 {
1391         while (pit != pend) {
1392                 it = pit->insetlist.begin();
1393                 if (it != pit->insetlist.end())
1394                         return;
1395                 ++pit;
1396         }
1397 }
1398
1399
1400 ParIterator Buffer::getParFromID(int id) const
1401 {
1402 #warning FIXME: const correctness! (Andre)
1403         ParIterator it = const_cast<Buffer*>(this)->par_iterator_begin();
1404         ParIterator end = const_cast<Buffer*>(this)->par_iterator_end();
1405
1406 #warning FIXME, perhaps this func should return a ParIterator? (Lgb)
1407         if (id < 0) {
1408                 // John says this is called with id == -1 from undo
1409                 lyxerr << "getParFromID(), id: " << id << endl;
1410                 return end;
1411         }
1412
1413         for (; it != end; ++it)
1414                 if (it->id() == id)
1415                         return it;
1416
1417         return end;
1418 }
1419
1420
1421 bool Buffer::hasParWithID(int id) const
1422 {
1423         ParConstIterator it = par_iterator_begin();
1424         ParConstIterator end = par_iterator_end();
1425
1426         if (id < 0) {
1427                 // John says this is called with id == -1 from undo
1428                 lyxerr << "hasParWithID(), id: " << id << endl;
1429                 return 0;
1430         }
1431
1432         for (; it != end; ++it)
1433                 if (it->id() == id)
1434                         return true;
1435
1436         return false;
1437 }
1438
1439
1440 PosIterator Buffer::pos_iterator_begin()
1441 {
1442         return PosIterator(&paragraphs(), paragraphs().begin(), 0);
1443 }
1444
1445
1446 PosIterator Buffer::pos_iterator_end()
1447 {
1448         return PosIterator(&paragraphs(), paragraphs().end(), 0);
1449 }
1450
1451
1452 ParIterator Buffer::par_iterator_begin()
1453 {
1454         return ParIterator(paragraphs().begin(), paragraphs());
1455 }
1456
1457
1458 ParIterator Buffer::par_iterator_end()
1459 {
1460         return ParIterator(paragraphs().end(), paragraphs());
1461 }
1462
1463
1464 ParConstIterator Buffer::par_iterator_begin() const
1465 {
1466         ParagraphList const & pars = paragraphs();
1467         return ParConstIterator(const_cast<ParagraphList&>(pars).begin(), pars);
1468 }
1469
1470
1471 ParConstIterator Buffer::par_iterator_end() const
1472 {
1473         ParagraphList const & pars = paragraphs();
1474         return ParConstIterator(const_cast<ParagraphList&>(pars).end(), pars);
1475 }
1476
1477
1478 Language const * Buffer::getLanguage() const
1479 {
1480         return params().language;
1481 }
1482
1483
1484 string const Buffer::B_(string const & l10n) const
1485 {
1486         if (pimpl_->messages.get()) {
1487                 return pimpl_->messages->get(l10n);
1488         }
1489
1490         return _(l10n);
1491 }
1492
1493
1494 bool Buffer::isClean() const
1495 {
1496         return pimpl_->lyx_clean;
1497 }
1498
1499
1500 bool Buffer::isBakClean() const
1501 {
1502         return pimpl_->bak_clean;
1503 }
1504
1505
1506 void Buffer::markClean() const
1507 {
1508         if (!pimpl_->lyx_clean) {
1509                 pimpl_->lyx_clean = true;
1510                 updateTitles();
1511         }
1512         // if the .lyx file has been saved, we don't need an
1513         // autosave
1514         pimpl_->bak_clean = true;
1515 }
1516
1517
1518 void Buffer::markBakClean()
1519 {
1520         pimpl_->bak_clean = true;
1521 }
1522
1523
1524 void Buffer::setUnnamed(bool flag)
1525 {
1526         pimpl_->unnamed = flag;
1527 }
1528
1529
1530 bool Buffer::isUnnamed()
1531 {
1532         return pimpl_->unnamed;
1533 }
1534
1535
1536 void Buffer::markDirty()
1537 {
1538         if (pimpl_->lyx_clean) {
1539                 pimpl_->lyx_clean = false;
1540                 updateTitles();
1541         }
1542         pimpl_->bak_clean = false;
1543
1544         DepClean::iterator it = pimpl_->dep_clean.begin();
1545         DepClean::const_iterator const end = pimpl_->dep_clean.end();
1546
1547         for (; it != end; ++it) {
1548                 it->second = false;
1549         }
1550 }
1551
1552
1553 string const & Buffer::fileName() const
1554 {
1555         return pimpl_->filename;
1556 }
1557
1558
1559 string const & Buffer::filePath() const
1560 {
1561         return pimpl_->filepath;
1562 }
1563
1564
1565 bool Buffer::isReadonly() const
1566 {
1567         return pimpl_->read_only;
1568 }
1569
1570
1571 void Buffer::setParentName(string const & name)
1572 {
1573         params().parentname = name;
1574 }
1575
1576
1577 Buffer::inset_iterator::inset_iterator()
1578         : pit(), pend()
1579 {}
1580
1581
1582 Buffer::inset_iterator::inset_iterator(base_type p, base_type e)
1583         : pit(p), pend(e)
1584 {
1585         setParagraph();
1586 }
1587
1588
1589 Buffer::inset_iterator Buffer::inset_iterator_begin()
1590 {
1591         return inset_iterator(paragraphs().begin(), paragraphs().end());
1592 }
1593
1594
1595 Buffer::inset_iterator Buffer::inset_iterator_end()
1596 {
1597         return inset_iterator(paragraphs().end(), paragraphs().end());
1598 }
1599
1600
1601 Buffer::inset_iterator Buffer::inset_const_iterator_begin() const
1602 {
1603         ParagraphList & pars = const_cast<ParagraphList&>(paragraphs());
1604         return inset_iterator(pars.begin(), pars.end());
1605 }
1606
1607
1608 Buffer::inset_iterator Buffer::inset_const_iterator_end() const
1609 {
1610         ParagraphList & pars = const_cast<ParagraphList&>(paragraphs());
1611         return inset_iterator(pars.end(), pars.end());
1612 }
1613
1614
1615 Buffer::inset_iterator & Buffer::inset_iterator::operator++()
1616 {
1617         if (pit != pend) {
1618                 ++it;
1619                 if (it == pit->insetlist.end()) {
1620                         ++pit;
1621                         setParagraph();
1622                 }
1623         }
1624         return *this;
1625 }
1626
1627
1628 Buffer::inset_iterator Buffer::inset_iterator::operator++(int)
1629 {
1630         inset_iterator tmp = *this;
1631         ++*this;
1632         return tmp;
1633 }
1634
1635
1636 Buffer::inset_iterator::reference Buffer::inset_iterator::operator*()
1637 {
1638         return *it->inset;
1639 }
1640
1641
1642 Buffer::inset_iterator::pointer Buffer::inset_iterator::operator->()
1643 {
1644         return it->inset;
1645 }
1646
1647
1648 ParagraphList::iterator Buffer::inset_iterator::getPar() const
1649 {
1650         return pit;
1651 }
1652
1653
1654 lyx::pos_type Buffer::inset_iterator::getPos() const
1655 {
1656         return it->pos;
1657 }
1658
1659
1660 bool operator==(Buffer::inset_iterator const & iter1,
1661                 Buffer::inset_iterator const & iter2)
1662 {
1663         return iter1.pit == iter2.pit
1664                 && (iter1.pit == iter1.pend || iter1.it == iter2.it);
1665 }
1666
1667
1668 bool operator!=(Buffer::inset_iterator const & iter1,
1669                 Buffer::inset_iterator const & iter2)
1670 {
1671         return !(iter1 == iter2);
1672 }