]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
Create a grfx::Loader class and so move large chunks of code out of
[lyx.git] / src / support / filetools.C
1 /*
2         filetools.C (former paths.C) - part of LyX project
3         General path-mangling functions
4         Copyright 1996 Ivan Schreter
5         Parts Copyright 1996 Dirk Niggemann
6         Parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7         Parts Copyright 1996 Asger Alstrup
8
9         See also filetools.h.
10
11         lyx-filetool.C : tools functions for file/path handling
12         this file is part of LyX, the High Level Word Processor
13         Copyright 1995-1996, Matthias Ettrich and the LyX Team
14
15 */
16
17 #include <config.h>
18
19 #ifdef __GNUG__
20 #pragma implementation "filetools.h"
21 #endif
22
23 #include "debug.h"
24 #include "support/lstrings.h"
25 #include "support/systemcall.h"
26
27 #include "filetools.h"
28 #include "lstrings.h"
29 #include "frontends/Alert.h"
30 #include "FileInfo.h"
31 #include "support/path.h"        // I know it's OS/2 specific (SMiyata)
32 #include "gettext.h"
33 #include "lyxlib.h"
34 #include "os.h"
35
36 #include "Lsstream.h"
37
38 #include <cctype>
39 #include <cstdlib>
40 #include <cstdio>
41 #include <fcntl.h>
42 #include <cerrno>
43
44 #include <utility>
45 #include <fstream>
46
47
48 // Which part of this is still necessary? (JMarc).
49 #if HAVE_DIRENT_H
50 # include <dirent.h>
51 # define NAMLEN(dirent) strlen((dirent)->d_name)
52 #else
53 # define dirent direct
54 # define NAMLEN(dirent) (dirent)->d_namlen
55 # if HAVE_SYS_NDIR_H
56 #  include <sys/ndir.h>
57 # endif
58 # if HAVE_SYS_DIR_H
59 #  include <sys/dir.h>
60 # endif
61 # if HAVE_NDIR_H
62 #  include <ndir.h>
63 # endif
64 #endif
65
66 #ifndef CXX_GLOBAL_CSTD
67 using std::fgetc;
68 using std::isalpha;
69 using std::isalnum;
70 #endif
71
72 using std::make_pair;
73 using std::pair;
74 using std::endl;
75 using std::ifstream;
76 using std::vector;
77 using std::getline;
78
79 extern string system_lyxdir;
80 extern string build_lyxdir;
81 extern string user_lyxdir;
82 extern string system_tempdir;
83 extern string system_packageList;
84
85
86 bool IsLyXFilename(string const & filename)
87 {
88         return suffixIs(lowercase(filename), ".lyx");
89 }
90
91
92 bool IsSGMLFilename(string const & filename)
93 {
94         return suffixIs(lowercase(filename), ".sgml");
95 }
96
97
98 // Substitutes spaces with underscores in filename (and path)
99 string const MakeLatexName(string const & file)
100 {
101         string name = OnlyFilename(file);
102         string const path = OnlyPath(file);
103
104         for (string::size_type i = 0; i < name.length(); ++i) {
105                 name[i] &= 0x7f; // set 8th bit to 0
106         };
107
108         // ok so we scan through the string twice, but who cares.
109         string const keep("abcdefghijklmnopqrstuvwxyz"
110                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
111                 "@!\"'()*+,-./0123456789:;<=>?[]`|");
112
113         string::size_type pos = 0;
114         while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
115                 name[pos++] = '_';
116         }
117         return AddName(path, name);
118 }
119
120
121 // Substitutes spaces with underscores in filename (and path)
122 string const QuoteName(string const & name)
123 {
124         return (os::shell() == os::UNIX) ?
125                 "\'" + name + "\'":
126                 "\"" + name + "\"";
127 }
128
129
130 // Is a file readable ?
131 bool IsFileReadable (string const & path)
132 {
133         FileInfo file(path);
134         if (file.isOK() && file.isRegular() && file.readable())
135                 return true;
136         else
137                 return false;
138 }
139
140
141 // Is a file read_only?
142 // return 1 read-write
143 //        0 read_only
144 //       -1 error (doesn't exist, no access, anything else)
145 int IsFileWriteable (string const & path)
146 {
147         FileInfo fi(path);
148
149         if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
150                 return 1;
151         if (fi.readable()) // read-only
152                 return 0;
153         return -1; // everything else.
154 }
155
156
157 //returns true: dir writeable
158 //        false: not writeable
159 bool IsDirWriteable (string const & path)
160 {
161         lyxerr[Debug::FILES] << "IsDirWriteable: " << path << endl;
162
163         string const tmpfl(lyx::tempName(path, "lyxwritetest"));
164
165         if (tmpfl.empty())
166                 return false;
167
168         lyx::unlink(tmpfl);
169         return true;
170 }
171
172
173 // Uses a string of paths separated by ";"s to find a file to open.
174 // Can't cope with pathnames with a ';' in them. Returns full path to file.
175 // If path entry begins with $$LyX/, use system_lyxdir
176 // If path entry begins with $$User/, use user_lyxdir
177 // Example: "$$User/doc;$$LyX/doc"
178 string const FileOpenSearch (string const & path, string const & name,
179                              string const & ext)
180 {
181         string real_file, path_element;
182         bool notfound = true;
183         string tmppath = split(path, path_element, ';');
184
185         while (notfound && !path_element.empty()) {
186                 path_element = os::slashify_path(path_element);
187                 if (!suffixIs(path_element, '/'))
188                         path_element+= '/';
189                 path_element = subst(path_element, "$$LyX", system_lyxdir);
190                 path_element = subst(path_element, "$$User", user_lyxdir);
191
192                 real_file = FileSearch(path_element, name, ext);
193
194                 if (real_file.empty()) {
195                         do {
196                                 tmppath = split(tmppath, path_element, ';');
197                         } while (!tmppath.empty() && path_element.empty());
198                 } else {
199                         notfound = false;
200                 }
201         }
202 #ifdef __EMX__
203         if (ext.empty() && notfound) {
204                 real_file = FileOpenSearch(path, name, "exe");
205                 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
206         }
207 #endif
208         return real_file;
209 }
210
211
212 /// Returns a vector of all files in directory dir having extension ext.
213 vector<string> const DirList(string const & dir, string const & ext)
214 {
215         // This is a non-error checking C/system implementation
216         string extension(ext);
217         if (!extension.empty() && extension[0] != '.')
218                 extension.insert(0, ".");
219         vector<string> dirlist;
220         DIR * dirp = ::opendir(dir.c_str());
221         if (!dirp) {
222                 lyxerr[Debug::FILES]
223                         << "Directory \"" << dir
224                         << "\" does not exist to DirList." << endl;
225                 return dirlist;
226         }
227
228         dirent * dire;
229         while ((dire = ::readdir(dirp))) {
230                 string const fil = dire->d_name;
231                 if (suffixIs(fil, extension)) {
232                         dirlist.push_back(fil);
233                 }
234         }
235         ::closedir(dirp);
236         return dirlist;
237         /* I would have prefered to take a vector<string>& as parameter so
238            that we could avoid the copy of the vector when returning.
239            Then we would use:
240            dirlist.swap(argvec);
241            to avoid the copy. (Lgb)
242         */
243         /* A C++ implementaion will look like this:
244            string extension(ext);
245            if (extension[0] != '.') extension.insert(0, ".");
246            vector<string> dirlist;
247            directory_iterator dit("dir");
248            while (dit != directory_iterator()) {
249                    string fil = dit->filename;
250                    if (prefixIs(fil, extension)) {
251                            dirlist.push_back(fil);
252                    }
253                    ++dit;
254            }
255            dirlist.swap(argvec);
256            return;
257         */
258 }
259
260
261 // Returns the real name of file name in directory path, with optional
262 // extension ext.
263 string const FileSearch(string const & path, string const & name,
264                         string const & ext)
265 {
266         // if `name' is an absolute path, we ignore the setting of `path'
267         // Expand Environmentvariables in 'name'
268         string const tmpname = ReplaceEnvironmentPath(name);
269         string fullname = MakeAbsPath(tmpname, path);
270         // search first without extension, then with it.
271         if (IsFileReadable(fullname))
272                 return fullname;
273         else if (ext.empty())
274                 return string();
275         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
276                 fullname += '.';
277                 fullname += ext;
278                 if (IsFileReadable(fullname))
279                         return fullname;
280                 else
281                         return string();
282         }
283 }
284
285
286 // Search the file name.ext in the subdirectory dir of
287 //   1) user_lyxdir
288 //   2) build_lyxdir (if not empty)
289 //   3) system_lyxdir
290 string const LibFileSearch(string const & dir, string const & name,
291                            string const & ext)
292 {
293         string fullname = FileSearch(AddPath(user_lyxdir, dir), name, ext);
294         if (!fullname.empty())
295                 return fullname;
296
297         if (!build_lyxdir.empty())
298                 fullname = FileSearch(AddPath(build_lyxdir, dir), name, ext);
299         if (!fullname.empty())
300                 return fullname;
301
302         return FileSearch(AddPath(system_lyxdir, dir), name, ext);
303 }
304
305
306 string const
307 i18nLibFileSearch(string const & dir, string const & name,
308                   string const & ext)
309 {
310         // this comment is from intl/dcigettext.c. We try to mimick this
311         // behaviour here.
312         /* The highest priority value is the `LANGUAGE' environment
313            variable. But we don't use the value if the currently
314            selected locale is the C locale. This is a GNU extension. */
315
316         string const lc_all = GetEnv("LC_ALL");
317         string lang = GetEnv("LANGUAGE");
318         if (lang.empty() || lc_all == "C") {
319                 lang = lc_all;
320                 if (lang.empty()) {
321                         lang = GetEnv("LANG");
322                 }
323         }
324
325         lang = token(lang, '_', 0);
326
327         if (lang.empty() || lang == "C")
328                 return LibFileSearch(dir, name, ext);
329         else {
330                 string const tmp = LibFileSearch(dir, lang + '_' + name,
331                                                  ext);
332                 if (!tmp.empty())
333                         return tmp;
334                 else
335                         return LibFileSearch(dir, name, ext);
336         }
337 }
338
339
340 string const LibScriptSearch(string const & command)
341 {
342         string script;
343         string args = command;
344         split(args, script, ' ');
345         script = LibFileSearch("scripts", script);
346         if (script.empty())
347                 return command;
348         else if (args.empty())
349                 return script;
350         else
351                 return script + ' ' + args;
352 }
353
354
355 string const GetEnv(string const & envname)
356 {
357         // f.ex. what about error checking?
358         char const * const ch = getenv(envname.c_str());
359         string const envstr = !ch ? "" : ch;
360         return envstr;
361 }
362
363
364 string const GetEnvPath(string const & name)
365 {
366 #ifndef __EMX__
367         string const pathlist = subst(GetEnv(name), ':', ';');
368 #else
369         string const pathlist = os::slashify_path(GetEnv(name));
370 #endif
371         return strip(pathlist, ';');
372 }
373
374
375 bool PutEnv(string const & envstr)
376 {
377         // CHECK Look at and fix this.
378         // f.ex. what about error checking?
379
380 #if HAVE_PUTENV
381         // this leaks, but what can we do about it?
382         //   Is doing a getenv() and a free() of the older value
383         //   a good idea? (JMarc)
384         // Actually we don't have to leak...calling putenv like this
385         // should be enough: ... and this is obviously not enough if putenv
386         // does not make a copy of the string. It is also not very wise to
387         // put a string on the free store. If we have to leak we should do it
388         // like this:
389         char * leaker = new char[envstr.length() + 1];
390         envstr.copy(leaker, envstr.length());
391         leaker[envstr.length()] = '\0';
392         int const retval = lyx::putenv(leaker);
393
394         // If putenv does not make a copy of the char const * this
395         // is very dangerous. OTOH if it does take a copy this is the
396         // best solution.
397         // The  only implementation of putenv that I have seen does not
398         // allocate memory. _And_ after testing the putenv in glibc it
399         // seems that we need to make a copy of the string contents.
400         // I will enable the above.
401         //int retval = lyx::putenv(envstr.c_str());
402 #else
403 #ifdef HAVE_SETENV
404         string varname;
405         string const str = envstr.split(varname,'=');
406         int const retval = ::setenv(varname.c_str(), str.c_str(), true);
407 #else
408         // No environment setting function. Can this happen?
409         int const retval = 1; //return an error condition.
410 #endif
411 #endif
412         return retval == 0;
413 }
414
415
416 bool PutEnvPath(string const & envstr)
417 {
418         return PutEnv(envstr);
419 }
420
421
422 namespace {
423
424 int DeleteAllFilesInDir (string const & path)
425 {
426         // I have decided that we will be using parts from the boost
427         // library. Check out http://www.boost.org/
428         // For directory access we will then use the directory_iterator.
429         // Then the code will be something like:
430         // directory_iterator dit(path);
431         // directory_iterator dend;
432         // if (dit == dend) {
433         //         Alert::err_alert(_("Error! Cannot open directory:"), path);
434         //         return -1;
435         // }
436         // for (; dit != dend; ++dit) {
437         //         string filename(*dit);
438         //         if (filename == "." || filename == "..")
439         //                 continue;
440         //         string unlinkpath(AddName(path, filename));
441         //         if (lyx::unlink(unlinkpath))
442         //                 Alert::err_alert(_("Error! Could not remove file:"),
443         //                              unlinkpath);
444         // }
445         // return 0;
446         DIR * dir = ::opendir(path.c_str());
447         if (!dir) {
448                 Alert::err_alert (_("Error! Cannot open directory:"), path);
449                 return -1;
450         }
451         struct dirent * de;
452         int return_value = 0;
453         while ((de = readdir(dir))) {
454                 string const temp = de->d_name;
455                 if (temp == "." || temp == "..")
456                         continue;
457                 string const unlinkpath = AddName (path, temp);
458
459                 lyxerr[Debug::FILES] << "Deleting file: " << unlinkpath
460                                      << endl;
461
462                 bool deleted = true;
463                 FileInfo fi(unlinkpath);
464                 if (fi.isOK() && fi.isDir())
465                         deleted = (DeleteAllFilesInDir(unlinkpath) == 0);
466                 deleted &= (lyx::unlink(unlinkpath) == 0);
467                 if (!deleted) {
468                         Alert::err_alert(_("Error! Could not remove file:"),
469                                 unlinkpath);
470                         return_value = -1;
471                 }
472         }
473         closedir(dir);
474         return return_value;
475 }
476
477
478 string const CreateTmpDir(string const & tempdir, string const & mask)
479 {
480         lyxerr[Debug::FILES]
481                 << "CreateTmpDir: tempdir=`" << tempdir << "'\n"
482                 << "CreateTmpDir:    mask=`" << mask << "'" << endl;
483
484         string const tmpfl(lyx::tempName(tempdir, mask));
485         // lyx::tempName actually creates a file to make sure that it
486         // stays unique. So we have to delete it before we can create
487         // a dir with the same name. Note also that we are not thread
488         // safe because of the gap between unlink and mkdir. (Lgb)
489         lyx::unlink(tmpfl.c_str());
490
491         if (tmpfl.empty() || lyx::mkdir(tmpfl, 0700)) {
492                 Alert::err_alert(_("Error! Couldn't create temporary directory:"),
493                              tempdir);
494                 return string();
495         }
496         return MakeAbsPath(tmpfl);
497 }
498
499
500 int DestroyTmpDir(string const & tmpdir, bool Allfiles)
501 {
502 #ifdef __EMX__
503         Path p(user_lyxdir);
504 #endif
505         if (Allfiles && DeleteAllFilesInDir(tmpdir)) {
506                 return -1;
507         }
508         if (lyx::rmdir(tmpdir)) {
509                 Alert::err_alert(_("Error! Couldn't delete temporary directory:"),
510                              tmpdir);
511                 return -1;
512         }
513         return 0;
514 }
515
516 } // namespace anon
517
518
519 string const CreateBufferTmpDir(string const & pathfor)
520 {
521         static int count;
522         static string const tmpdir(pathfor.empty() ? os::getTmpDir() : pathfor);
523         // We are in our own directory.  Why bother to mangle name?
524         // In fact I wrote this code to circumvent a problematic behaviour (bug?)
525         // of EMX mkstemp().
526         string const tmpfl = tmpdir + "/lyx_tmpbuf" + tostr(count++);
527         if (lyx::mkdir(tmpfl, 0777)) {
528                 Alert::err_alert(_("Error! Couldn't create temporary directory:"),
529                              tmpdir);
530                 return string();
531         }
532         return tmpfl;
533 }
534
535
536 int DestroyBufferTmpDir(string const & tmpdir)
537 {
538         return DestroyTmpDir(tmpdir, true);
539 }
540
541
542 string const CreateLyXTmpDir(string const & deflt)
543 {
544         if ((!deflt.empty()) && (deflt  != "/tmp")) {
545                 if (lyx::mkdir(deflt, 0777)) {
546 #ifdef __EMX__
547                 Path p(user_lyxdir);
548 #endif
549                         return CreateTmpDir(deflt, "lyx_tmpdir");
550                 } else
551                         return deflt;
552         } else {
553 #ifdef __EMX__
554                 Path p(user_lyxdir);
555 #endif
556                 return CreateTmpDir("/tmp", "lyx_tmpdir");
557         }
558 }
559
560
561 // FIXME: no need for separate method like this ... 
562 int DestroyLyXTmpDir(string const & tmpdir)
563 {
564         return DestroyTmpDir(tmpdir, true); 
565 }
566
567
568 // Creates directory. Returns true if succesfull
569 bool createDirectory(string const & path, int permission)
570 {
571         string temp(strip(os::slashify_path(path), '/'));
572
573         if (temp.empty()) {
574                 Alert::alert(_("Internal error!"),
575                            _("Call to createDirectory with invalid name"));
576                 return false;
577         }
578
579         if (lyx::mkdir(temp, permission)) {
580                 Alert::err_alert (_("Error! Couldn't create directory:"), temp);
581                 return false;
582         }
583         return true;
584 }
585
586
587 // Strip filename from path name
588 string const OnlyPath(string const & Filename)
589 {
590         // If empty filename, return empty
591         if (Filename.empty()) return Filename;
592
593         // Find last / or start of filename
594         string::size_type j = Filename.rfind('/');
595         if (j == string::npos)
596                 return "./";
597         return Filename.substr(0, j + 1);
598 }
599
600
601 // Convert relative path into absolute path based on a basepath.
602 // If relpath is absolute, just use that.
603 // If basepath is empty, use CWD as base.
604 string const MakeAbsPath(string const & RelPath, string const & BasePath)
605 {
606         // checks for already absolute path
607         if (os::is_absolute_path(RelPath))
608                 return RelPath;
609
610         // Copies given paths
611         string TempRel(os::slashify_path(RelPath));
612         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
613         TempRel = subst(TempRel, "//", "/");
614
615         string TempBase;
616
617         if (os::is_absolute_path(BasePath))
618                 TempBase = BasePath;
619         else
620                 TempBase = AddPath(lyx::getcwd(), BasePath);
621
622         // Handle /./ at the end of the path
623         while (suffixIs(TempBase, "/./"))
624                 TempBase.erase(TempBase.length() - 2);
625
626         // processes relative path
627         string RTemp(TempRel);
628         string Temp;
629
630         while (!RTemp.empty()) {
631                 // Split by next /
632                 RTemp = split(RTemp, Temp, '/');
633
634                 if (Temp == ".") continue;
635                 if (Temp == "..") {
636                         // Remove one level of TempBase
637                         string::difference_type i = TempBase.length() - 2;
638 #ifndef __EMX__
639                         if (i < 0) i = 0;
640                         while (i > 0 && TempBase[i] != '/') --i;
641                         if (i > 0)
642 #else
643                         if (i < 2) i = 2;
644                         while (i > 2 && TempBase[i] != '/') --i;
645                         if (i > 2)
646 #endif
647                                 TempBase.erase(i, string::npos);
648                         else
649                                 TempBase += '/';
650                 } else if (Temp.empty() && !RTemp.empty()) {
651                                 TempBase = os::current_root() + RTemp;
652                                 RTemp.erase();
653                 } else {
654                         // Add this piece to TempBase
655                         if (!suffixIs(TempBase, '/'))
656                                 TempBase += '/';
657                         TempBase += Temp;
658                 }
659         }
660
661         // returns absolute path
662         return os::slashify_path(TempBase);
663 }
664
665
666 // Correctly append filename to the pathname.
667 // If pathname is '.', then don't use pathname.
668 // Chops any path of filename.
669 string const AddName(string const & path, string const & fname)
670 {
671         // Get basename
672         string const basename(OnlyFilename(fname));
673
674         string buf;
675
676         if (path != "." && path != "./" && !path.empty()) {
677                 buf = os::slashify_path(path);
678                 if (!suffixIs(path, '/'))
679                         buf += '/';
680         }
681
682         return buf + basename;
683 }
684
685
686 // Strips path from filename
687 string const OnlyFilename(string const & fname)
688 {
689         if (fname.empty())
690                 return fname;
691
692         string::size_type j = fname.rfind('/');
693         if (j == string::npos) // no '/' in fname
694                 return fname;
695
696         // Strip to basename
697         return fname.substr(j + 1);
698 }
699
700
701 /// Returns true is path is absolute
702 bool AbsolutePath(string const & path)
703 {
704         return os::is_absolute_path(path);
705 }
706
707
708
709 // Create absolute path. If impossible, don't do anything
710 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
711 string const ExpandPath(string const & path)
712 {
713         // checks for already absolute path
714         string RTemp(ReplaceEnvironmentPath(path));
715         if (os::is_absolute_path(RTemp))
716                 return RTemp;
717
718         string Temp;
719         string const copy(RTemp);
720
721         // Split by next /
722         RTemp = split(RTemp, Temp, '/');
723
724         if (Temp == ".") {
725                 return lyx::getcwd() /*GetCWD()*/ + '/' + RTemp;
726         }
727         if (Temp == "~") {
728                 return GetEnvPath("HOME") + '/' + RTemp;
729         }
730         if (Temp == "..") {
731                 return MakeAbsPath(copy);
732         }
733         // Don't know how to handle this
734         return copy;
735 }
736
737
738 // Normalize a path
739 // Constracts path/../path
740 // Can't handle "../../" or "/../" (Asger)
741 string const NormalizePath(string const & path)
742 {
743         string TempBase;
744         string RTemp;
745         string Temp;
746
747         if (os::is_absolute_path(path))
748                 RTemp = path;
749         else
750                 // Make implicit current directory explicit
751                 RTemp = "./" +path;
752
753         while (!RTemp.empty()) {
754                 // Split by next /
755                 RTemp = split(RTemp, Temp, '/');
756
757                 if (Temp == ".") {
758                         TempBase = "./";
759                 } else if (Temp == "..") {
760                         // Remove one level of TempBase
761                         string::difference_type i = TempBase.length() - 2;
762                         while (i > 0 && TempBase[i] != '/')
763                                 --i;
764                         if (i >= 0 && TempBase[i] == '/')
765                                 TempBase.erase(i + 1, string::npos);
766                         else
767                                 TempBase = "../";
768                 } else {
769                         TempBase += Temp + '/';
770                 }
771         }
772
773         // returns absolute path
774         return TempBase;
775 }
776
777
778 string const GetFileContents(string const & fname)
779 {
780         FileInfo finfo(fname);
781         if (finfo.exist()) {
782                 ifstream ifs(fname.c_str());
783                 ostringstream ofs;
784                 if (ifs && ofs) {
785                         ofs << ifs.rdbuf();
786                         ifs.close();
787                         return ofs.str().c_str();
788                 }
789         }
790         lyxerr << "LyX was not able to read file '" << fname << "'" << endl;
791         return string();
792 }
793
794
795 //
796 // Search ${...} as Variable-Name inside the string and replace it with
797 // the denoted environmentvariable
798 // Allow Variables according to
799 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
800 //
801
802 string const ReplaceEnvironmentPath(string const & path)
803 {
804 //
805 // CompareChar: Environmentvariables starts with this character
806 // PathChar:    Next path component start with this character
807 // while CompareChar found do:
808 //       Split String with PathChar
809 //       Search Environmentvariable
810 //       if found: Replace Strings
811 //
812         char const CompareChar = '$';
813         char const FirstChar = '{';
814         char const EndChar = '}';
815         char const UnderscoreChar = '_';
816         string EndString; EndString += EndChar;
817         string FirstString; FirstString += FirstChar;
818         string CompareString; CompareString += CompareChar;
819         string const RegExp("*}*"); // Exist EndChar inside a String?
820
821 // first: Search for a '$' - Sign.
822         //string copy(path);
823         string result1; //(copy);    // for split-calls
824         string result0 = split(path, result1, CompareChar);
825         while (!result0.empty()) {
826                 string copy1(result0); // contains String after $
827
828                 // Check, if there is an EndChar inside original String.
829
830                 if (!regexMatch(copy1, RegExp)) {
831                         // No EndChar inside. So we are finished
832                         result1 += CompareString + result0;
833                         result0.erase();
834                         continue;
835                 }
836
837                 string res1;
838                 string res0 = split(copy1, res1, EndChar);
839                 // Now res1 holds the environmentvariable
840                 // First, check, if Contents is ok.
841                 if (res1.empty()) { // No environmentvariable. Continue Loop.
842                         result1 += CompareString + FirstString;
843                         result0  = res0;
844                         continue;
845                 }
846                 // check contents of res1
847                 char const * res1_contents = res1.c_str();
848                 if (*res1_contents != FirstChar) {
849                         // Again No Environmentvariable
850                         result1 += CompareString;
851                         result0 = res0;
852                 }
853
854                 // Check for variable names
855                 // Situation ${} is detected as "No Environmentvariable"
856                 char const * cp1 = res1_contents + 1;
857                 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
858                 ++cp1;
859                 while (*cp1 && result) {
860                         result = isalnum(*cp1) ||
861                                 (*cp1 == UnderscoreChar);
862                         ++cp1;
863                 }
864
865                 if (!result) {
866                         // no correct variable name
867                         result1 += CompareString + res1 + EndString;
868                         result0  = split(res0, res1, CompareChar);
869                         result1 += res1;
870                         continue;
871                 }
872
873                 string env(GetEnv(res1_contents + 1));
874                 if (!env.empty()) {
875                         // Congratulations. Environmentvariable found
876                         result1 += env;
877                 } else {
878                         result1 += CompareString + res1 + EndString;
879                 }
880                 // Next $-Sign?
881                 result0  = split(res0, res1, CompareChar);
882                 result1 += res1;
883         }
884         return result1;
885 }  // ReplaceEnvironmentPath
886
887
888 // Make relative path out of two absolute paths
889 string const MakeRelPath(string const & abspath, string const & basepath)
890 // Makes relative path out of absolute path. If it is deeper than basepath,
891 // it's easy. If basepath and abspath share something (they are all deeper
892 // than some directory), it'll be rendered using ..'s. If they are completely
893 // different, then the absolute path will be used as relative path.
894 {
895         string::size_type const abslen = abspath.length();
896         string::size_type const baselen = basepath.length();
897
898         string::size_type i = os::common_path(abspath, basepath);
899
900         if (i == 0) {
901                 // actually no match - cannot make it relative
902                 return abspath;
903         }
904
905         // Count how many dirs there are in basepath above match
906         // and append as many '..''s into relpath
907         string buf;
908         string::size_type j = i;
909         while (j < baselen) {
910                 if (basepath[j] == '/') {
911                         if (j + 1 == baselen)
912                                 break;
913                         buf += "../";
914                 }
915                 ++j;
916         }
917
918         // Append relative stuff from common directory to abspath
919         if (abspath[i] == '/')
920                 ++i;
921         for (; i < abslen; ++i)
922                 buf += abspath[i];
923         // Remove trailing /
924         if (suffixIs(buf, '/'))
925                 buf.erase(buf.length() - 1);
926         // Substitute empty with .
927         if (buf.empty())
928                 buf = '.';
929         return buf;
930 }
931
932
933 // Append sub-directory(ies) to a path in an intelligent way
934 string const AddPath(string const & path, string const & path_2)
935 {
936         string buf;
937         string const path2 = os::slashify_path(path_2);
938
939         if (!path.empty() && path != "." && path != "./") {
940                 buf = os::slashify_path(path);
941                 if (path[path.length() - 1] != '/')
942                         buf += '/';
943         }
944
945         if (!path2.empty()) {
946                 string::size_type const p2start = path2.find_first_not_of('/');
947                 string::size_type const p2end = path2.find_last_not_of('/');
948                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
949                 buf += tmp + '/';
950         }
951         return buf;
952 }
953
954
955 /*
956  Change extension of oldname to extension.
957  Strips path off if no_path == true.
958  If no extension on oldname, just appends.
959  */
960 string const ChangeExtension(string const & oldname, string const & extension)
961 {
962         string::size_type const last_slash = oldname.rfind('/');
963         string::size_type last_dot = oldname.rfind('.');
964         if (last_dot < last_slash && last_slash != string::npos)
965                 last_dot = string::npos;
966
967         string ext;
968         // Make sure the extension starts with a dot
969         if (!extension.empty() && extension[0] != '.')
970                 ext= "." + extension;
971         else
972                 ext = extension;
973
974         return os::slashify_path(oldname.substr(0, last_dot) + ext);
975 }
976
977
978 /// Return the extension of the file (not including the .)
979 string const GetExtension(string const & name)
980 {
981         string::size_type const last_slash = name.rfind('/');
982         string::size_type const last_dot = name.rfind('.');
983         if (last_dot != string::npos &&
984             (last_slash == string::npos || last_dot > last_slash))
985                 return name.substr(last_dot + 1,
986                                    name.length() - (last_dot + 1));
987         else
988                 return string();
989 }
990
991 // the different filetypes and what they contain in one of the first lines
992 // (dots are any characters).           (Herbert 20020131)
993 // AGR  Grace...
994 // BMP  BM...
995 // EPS  %!PS-Adobe-3.0 EPSF...
996 // EPSI like EPS and with
997 //      %%BeginPreview...
998 // FITS ...BITPIX...
999 // GIF  GIF...
1000 // JPG  JFIF
1001 // PDF  %PDF-...
1002 // PNG  .PNG...
1003 // PBM  P1... or P4     (B/W)
1004 // PGM  P2... or P5     (Grayscale)
1005 // PPM  P3... or P6     (color)
1006 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
1007 // SGI  \001\332...     (decimal 474)
1008 // TGIF %TGIF...
1009 // TIFF II... or MM...
1010 // XBM  ..._bits[]...
1011 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
1012 //      ...static char *...
1013 // XWD  \000\000\000\151        (0x00006900) decimal 105
1014 //
1015 // GZIP \037\213\010\010...     http://www.ietf.org/rfc/rfc1952.txt
1016 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
1017 // Z    \037\177                UNIX compress
1018
1019 /// return the "extension" which belongs to the contents.
1020 /// for no knowing contents return the extension. Without
1021 /// an extension and unknown contents we return "user"
1022 string const getExtFromContents(string const & filename)
1023 {
1024         // paranoia check
1025         if (filename.empty() || !IsFileReadable(filename))
1026                 return string();
1027
1028         
1029         ifstream ifs(filename.c_str());
1030         if (!ifs)
1031                 // Couldn't open file...
1032                 return string();
1033
1034         // gnuzip
1035         string const gzipStamp = "\037\213\010\010";
1036
1037         // PKZIP
1038         string const zipStamp = "PK";
1039
1040         // compress
1041         string const compressStamp = "\037\177";
1042
1043         // Maximum strings to read
1044         int const max_count = 50;
1045         int count = 0;
1046
1047         string str, format;
1048         bool firstLine = true;
1049         while ((count++ < max_count) && format.empty()) {
1050                 if (ifs.eof()) {
1051                         lyxerr[Debug::GRAPHICS]
1052                                 << "filetools(getExtFromContents)\n"
1053                                 << "\tFile type not recognised before EOF!"
1054                                 << endl;
1055                         break;
1056                 }
1057
1058                 getline(ifs, str);
1059
1060                 lyxerr[Debug::GRAPHICS] << "Scanstring: " << str.substr(0,60)
1061                                         << endl;
1062
1063                 string const stamp = str.substr(0,2);
1064                 if (firstLine && str.size() >= 2) {
1065                         // at first we check for a zipped file, because this
1066                         // information is saved in the first bytes of the file!
1067                         // also some graphic formats which save the information
1068                         // in the first line, too.
1069                         if (prefixIs(str, gzipStamp)) {
1070                                 format =  "gzip";
1071
1072                         } else if (stamp == zipStamp) {
1073                                 format =  "zip";
1074
1075                         } else if (stamp == compressStamp) {
1076                                 format =  "compress";
1077
1078                         // the graphics part
1079                         } else if (stamp == "BM") {
1080                                 format =  "bmp";
1081
1082                         } else if (stamp == "\001\332") {
1083                                 format =  "sgi";
1084
1085                         // PBM family
1086                         // Don't need to use str.at(0), str.at(1) because
1087                         // we already know that str.size() >= 2
1088                         } else if (str[0] == 'P') {
1089                                 switch (str[1]) {
1090                                 case '1':
1091                                 case '4':
1092                                         format =  "pbm";
1093                                     break;
1094                                 case '2':
1095                                 case '5':
1096                                         format =  "pgm";
1097                                     break;
1098                                 case '3':
1099                                 case '6':
1100                                         format =  "ppm";
1101                                 }
1102                                 break;
1103
1104                         } else if ((stamp == "II") || (stamp == "MM")) {
1105                                 format =  "tiff";
1106
1107                         } else if (prefixIs(str,"%TGIF")) {
1108                                 format =  "tgif";
1109
1110                         } else if (prefixIs(str,"GIF")) {
1111                                 format =  "gif";
1112
1113                         } else if (str.size() > 3) {
1114                                 int const c = ((str[0] << 24) & (str[1] << 16) &
1115                                                (str[2] << 8)  & str[3]);
1116                                 if (c == 105) {
1117                                         format =  "xwd";
1118                                 }
1119                         }
1120
1121                         firstLine = false;
1122                 }
1123
1124                 if (!format.empty())
1125                     break;
1126                 else if (contains(str,"EPSF"))
1127                         // dummy, if we have wrong file description like
1128                         // %!PS-Adobe-2.0EPSF"
1129                         format =  "eps";
1130
1131                 else if (contains(str,"Grace"))
1132                         format =  "agr";
1133
1134                 else if (contains(str,"JFIF"))
1135                         format =  "jpg";
1136
1137                 else if (contains(str,"%PDF"))
1138                         format =  "pdf";
1139
1140                 else if (contains(str,"PNG"))
1141                         format =  "png";
1142
1143                 else if (contains(str,"%!PS-Adobe")) {
1144                         // eps or ps
1145                         ifs >> str;
1146                         if (contains(str,"EPSF")) 
1147                                 format = "eps";
1148                         else
1149                             format = "ps";
1150                 }
1151
1152                 else if (contains(str,"_bits[]"))
1153                         format = "xbm";
1154
1155                 else if (contains(str,"XPM") || contains(str, "static char *"))
1156                         format = "xpm";
1157
1158                 else if (contains(str,"BITPIX"))
1159                         format = "fits";
1160         }
1161
1162         if (!format.empty()) {
1163                 // if we have eps than epsi is also possible
1164                 // we have to check for a preview
1165                 if (format == "eps") {
1166                         lyxerr[Debug::GRAPHICS]
1167                                 << "\teps detected -> test for an epsi ..."
1168                                 << endl;
1169                         while (count++ < max_count) {
1170                                 if (ifs.eof())
1171                                         break;
1172                                 getline(ifs, str);
1173                                 if (contains(str, "BeginPreview")) {
1174                                         format = "epsi";
1175                                         count = max_count;
1176                                 }
1177                         }
1178                 }
1179                 lyxerr[Debug::GRAPHICS]
1180                         << "Recognised Fileformat: " << format << endl;
1181                 return format;
1182         }
1183
1184         string const ext(GetExtension(filename));
1185         lyxerr[Debug::GRAPHICS]
1186                 << "filetools(getExtFromContents)\n"
1187                 << "\tCouldn't find a known Type!\n";
1188         if (!ext.empty()) {
1189             lyxerr[Debug::GRAPHICS]
1190                 << "\twill take the file extension -> "
1191                 << ext << endl;
1192                 return ext;
1193         } else {
1194             lyxerr[Debug::GRAPHICS]
1195                 << "\twill use ext or a \"user\" defined format" << endl;
1196             return "user";
1197         }
1198 }
1199
1200
1201 /// check for zipped file
1202 bool zippedFile(string const & name)
1203 {
1204         string const type = getExtFromContents(name);
1205         if (contains("gzip zip compress", type) && !type.empty())
1206                 return true;
1207         return false;
1208 }
1209
1210
1211 string const unzipFile(string const & zipped_file)
1212 {
1213         string const file = ChangeExtension(zipped_file, string());
1214         string  const tempfile = lyx::tempName(string(), file);
1215         // Run gunzip
1216         string const command = "gunzip -c " + zipped_file + " > " + tempfile;
1217         Systemcall one;
1218         one.startscript(Systemcall::Wait, command);
1219         // test that command was executed successfully (anon)
1220         // yes, please do. (Lgb)
1221         return tempfile;
1222 }
1223
1224
1225 // Creates a nice compact path for displaying
1226 string const
1227 MakeDisplayPath (string const & path, unsigned int threshold)
1228 {
1229         string::size_type const l1 = path.length();
1230
1231         // First, we try a relative path compared to home
1232         string const home(GetEnvPath("HOME"));
1233         string relhome = MakeRelPath(path, home);
1234
1235         string::size_type l2 = relhome.length();
1236
1237         string prefix;
1238
1239         // If we backup from home or don't have a relative path,
1240         // this try is no good
1241         if (prefixIs(relhome, "../") || os::is_absolute_path(relhome)) {
1242                 // relative path was no good, just use the original path
1243                 relhome = path;
1244                 l2 = l1;
1245         } else {
1246                 prefix = "~/";
1247         }
1248
1249         // Is the path too long?
1250         if (l2 > threshold) {
1251                 // Yes, shortend it
1252                 prefix += ".../";
1253
1254                 string temp;
1255
1256                 while (relhome.length() > threshold)
1257                         relhome = split(relhome, temp, '/');
1258
1259                 // Did we shortend everything away?
1260                 if (relhome.empty()) {
1261                         // Yes, filename in itself is too long.
1262                         // Pick the start and the end of the filename.
1263                         relhome = OnlyFilename(path);
1264                         string const head = relhome.substr(0, threshold/2 - 3);
1265
1266                         l2 = relhome.length();
1267                         string const tail =
1268                                 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
1269                         relhome = head + "..." + tail;
1270                 }
1271         }
1272         return prefix + relhome;
1273 }
1274
1275
1276 bool LyXReadLink(string const & File, string & Link)
1277 {
1278         char LinkBuffer[512];
1279         // Should be PATH_MAX but that needs autconf support
1280         int const nRead = ::readlink(File.c_str(),
1281                                      LinkBuffer, sizeof(LinkBuffer) - 1);
1282         if (nRead <= 0)
1283                 return false;
1284         LinkBuffer[nRead] = '\0'; // terminator
1285         Link = LinkBuffer;
1286         return true;
1287 }
1288
1289
1290 namespace {
1291
1292 typedef pair<int, string> cmdret;
1293
1294 cmdret const do_popen(string const & cmd)
1295 {
1296         // One question is if we should use popen or
1297         // create our own popen based on fork, exec, pipe
1298         // of course the best would be to have a
1299         // pstream (process stream), with the
1300         // variants ipstream, opstream
1301
1302         FILE * inf = ::popen(cmd.c_str(), os::read_mode());
1303
1304         // (Claus Hentschel) Check if popen was succesful ;-)
1305         if (!inf)
1306                 return make_pair(-1, string());
1307
1308         string ret;
1309         int c = fgetc(inf);
1310         while (c != EOF) {
1311                 ret += static_cast<char>(c);
1312                 c = fgetc(inf);
1313         }
1314         int const pret = pclose(inf);
1315         return make_pair(pret, ret);
1316 }
1317
1318 } // namespace anon
1319
1320
1321 string const findtexfile(string const & fil, string const & /*format*/)
1322 {
1323         /* There is no problem to extend this function too use other
1324            methods to look for files. It could be setup to look
1325            in environment paths and also if wanted as a last resort
1326            to a recursive find. One of the easier extensions would
1327            perhaps be to use the LyX file lookup methods. But! I am
1328            going to implement this until I see some demand for it.
1329            Lgb
1330         */
1331
1332         // If the file can be found directly, we just return a
1333         // absolute path version of it.
1334         if (FileInfo(fil).exist())
1335                 return MakeAbsPath(fil);
1336
1337         // No we try to find it using kpsewhich.
1338         // It seems from the kpsewhich manual page that it is safe to use
1339         // kpsewhich without --format: "When the --format option is not
1340         // given, the search path used when looking for a file is inferred
1341         // from the name given, by looking for a known extension. If no
1342         // known extension is found, the search path for TeX source files
1343         // is used."
1344         // However, we want to take advantage of the format sine almost all
1345         // the different formats has environment variables that can be used
1346         // to controll which paths to search. f.ex. bib looks in
1347         // BIBINPUTS and TEXBIB. Small list follows:
1348         // bib - BIBINPUTS, TEXBIB
1349         // bst - BSTINPUTS
1350         // graphic/figure - TEXPICTS, TEXINPUTS
1351         // ist - TEXINDEXSTYLE, INDEXSTYLE
1352         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1353         // tex - TEXINPUTS
1354         // tfm - TFMFONTS, TEXFONTS
1355         // This means that to use kpsewhich in the best possible way we
1356         // should help it by setting additional path in the approp. envir.var.
1357         string const kpsecmd = "kpsewhich " + fil;
1358
1359         cmdret const c = do_popen(kpsecmd);
1360
1361         lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1362                  << "kpse result = `" << strip(c.second, '\n')
1363                  << "'" << endl;
1364         if (c.first != -1)
1365                 return os::internal_path(strip(strip(c.second, '\n'), '\r'));
1366         else
1367                 return string();
1368 }
1369
1370
1371 void removeAutosaveFile(string const & filename)
1372 {
1373         string a = OnlyPath(filename);
1374         a += '#';
1375         a += OnlyFilename(filename);
1376         a += '#';
1377         FileInfo const fileinfo(a);
1378         if (fileinfo.exist()) {
1379                 if (lyx::unlink(a) != 0) {
1380                         Alert::err_alert(_("Could not delete auto-save file!"), a);
1381                 }
1382         }
1383 }
1384
1385
1386 void readBB_lyxerrMessage(string const & file, bool & zipped, 
1387         string const & message) 
1388 {
1389         lyxerr[Debug::GRAPHICS] << "[readBB_from_PSFile] " 
1390                 << message << std::endl;
1391         if (zipped)
1392                 lyx::unlink(file);
1393 }
1394
1395
1396 string const readBB_from_PSFile(string const & file)
1397 {
1398         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1399         // It seems that every command in the header has an own line,
1400         // getline() should work for all files.
1401         // On the other hand some plot programs write the bb at the
1402         // end of the file. Than we have in the header:
1403         // %%BoundingBox: (atend)
1404         // In this case we must check the end.
1405         bool zipped = zippedFile(file);
1406         string const file_ = zipped ?
1407                 string(unzipFile(file)) : string(file);
1408         string const format = getExtFromContents(file_);
1409
1410         if (format != "eps" && format != "ps") {
1411                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format"); 
1412                 return string();
1413         }
1414
1415         std::ifstream is(file_.c_str());
1416         while (is) {
1417                 string s;
1418                 getline(is,s);
1419                 if (contains(s,"%%BoundingBox:") && !contains(s,"atend")) {
1420                         string const bb = frontStrip(s.substr(14));
1421                         readBB_lyxerrMessage(file_, zipped, bb);
1422                         return bb;
1423                 }
1424         }
1425         readBB_lyxerrMessage(file_, zipped, "no bb found");
1426         return string();
1427 }