]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
Compaq cxx 6.5 will now compile lyx.
[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 "frontends/Alert.h"
29 #include "FileInfo.h"
30 #include "support/path.h"        // I know it's OS/2 specific (SMiyata)
31 #include "gettext.h"
32 #include "lyxlib.h"
33 #include "os.h"
34
35 #include "Lsstream.h"
36
37 #include <cctype>
38 #include <cstdlib>
39 #include <cstdio>
40 #include <fcntl.h>
41 #include <cerrno>
42
43 #include <utility>
44 #include <fstream>
45
46
47 // Which part of this is still necessary? (JMarc).
48 #if HAVE_DIRENT_H
49 # include <dirent.h>
50 # define NAMLEN(dirent) strlen((dirent)->d_name)
51 #else
52 # define dirent direct
53 # define NAMLEN(dirent) (dirent)->d_namlen
54 # if HAVE_SYS_NDIR_H
55 #  include <sys/ndir.h>
56 # endif
57 # if HAVE_SYS_DIR_H
58 #  include <sys/dir.h>
59 # endif
60 # if HAVE_NDIR_H
61 #  include <ndir.h>
62 # endif
63 #endif
64
65 #ifndef CXX_GLOBAL_CSTD
66 using std::fgetc;
67 using std::isalpha;
68 using std::isalnum;
69 using std::pclose;
70 using std::popen;
71 #endif
72
73 using std::make_pair;
74 using std::pair;
75 using std::endl;
76 using std::ifstream;
77 using std::vector;
78 using std::getline;
79
80 extern string system_lyxdir;
81 extern string build_lyxdir;
82 extern string user_lyxdir;
83 extern string system_tempdir;
84 extern string system_packageList;
85
86
87 bool IsLyXFilename(string const & filename)
88 {
89         return suffixIs(lowercase(filename), ".lyx");
90 }
91
92
93 bool IsSGMLFilename(string const & filename)
94 {
95         return suffixIs(lowercase(filename), ".sgml");
96 }
97
98
99 // Substitutes spaces with underscores in filename (and path)
100 string const MakeLatexName(string const & file)
101 {
102         string name = OnlyFilename(file);
103         string const path = OnlyPath(file);
104
105         for (string::size_type i = 0; i < name.length(); ++i) {
106                 name[i] &= 0x7f; // set 8th bit to 0
107         };
108
109         // ok so we scan through the string twice, but who cares.
110         string const keep("abcdefghijklmnopqrstuvwxyz"
111                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
112                 "@!\"'()*+,-./0123456789:;<=>?[]`|");
113
114         string::size_type pos = 0;
115         while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
116                 name[pos++] = '_';
117         }
118         return AddName(path, name);
119 }
120
121
122 // Substitutes spaces with underscores in filename (and path)
123 string const QuoteName(string const & name)
124 {
125         return (os::shell() == os::UNIX) ?
126                 "\'" + name + "\'":
127                 "\"" + name + "\"";
128 }
129
130
131 // Is a file readable ?
132 bool IsFileReadable (string const & path)
133 {
134         FileInfo file(path);
135         if (file.isOK() && file.isRegular() && file.readable())
136                 return true;
137         else
138                 return false;
139 }
140
141
142 // Is a file read_only?
143 // return 1 read-write
144 //        0 read_only
145 //       -1 error (doesn't exist, no access, anything else)
146 int IsFileWriteable (string const & path)
147 {
148         FileInfo fi(path);
149
150         if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
151                 return 1;
152         if (fi.readable()) // read-only
153                 return 0;
154         return -1; // everything else.
155 }
156
157
158 //returns true: dir writeable
159 //        false: not writeable
160 bool IsDirWriteable (string const & path)
161 {
162         lyxerr[Debug::FILES] << "IsDirWriteable: " << path << endl;
163
164         string const tmpfl(lyx::tempName(path, "lyxwritetest"));
165
166         if (tmpfl.empty())
167                 return false;
168
169         lyx::unlink(tmpfl);
170         return true;
171 }
172
173
174 // Uses a string of paths separated by ";"s to find a file to open.
175 // Can't cope with pathnames with a ';' in them. Returns full path to file.
176 // If path entry begins with $$LyX/, use system_lyxdir
177 // If path entry begins with $$User/, use user_lyxdir
178 // Example: "$$User/doc;$$LyX/doc"
179 string const FileOpenSearch (string const & path, string const & name,
180                              string const & ext)
181 {
182         string real_file, path_element;
183         bool notfound = true;
184         string tmppath = split(path, path_element, ';');
185
186         while (notfound && !path_element.empty()) {
187                 path_element = os::slashify_path(path_element);
188                 if (!suffixIs(path_element, '/'))
189                         path_element+= '/';
190                 path_element = subst(path_element, "$$LyX", system_lyxdir);
191                 path_element = subst(path_element, "$$User", user_lyxdir);
192
193                 real_file = FileSearch(path_element, name, ext);
194
195                 if (real_file.empty()) {
196                         do {
197                                 tmppath = split(tmppath, path_element, ';');
198                         } while (!tmppath.empty() && path_element.empty());
199                 } else {
200                         notfound = false;
201                 }
202         }
203 #ifdef __EMX__
204         if (ext.empty() && notfound) {
205                 real_file = FileOpenSearch(path, name, "exe");
206                 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
207         }
208 #endif
209         return real_file;
210 }
211
212
213 /// Returns a vector of all files in directory dir having extension ext.
214 vector<string> const DirList(string const & dir, string const & ext)
215 {
216         // This is a non-error checking C/system implementation
217         string extension(ext);
218         if (!extension.empty() && extension[0] != '.')
219                 extension.insert(0, ".");
220         vector<string> dirlist;
221         DIR * dirp = ::opendir(dir.c_str());
222         if (!dirp) {
223                 lyxerr[Debug::FILES]
224                         << "Directory \"" << dir
225                         << "\" does not exist to DirList." << endl;
226                 return dirlist;
227         }
228
229         dirent * dire;
230         while ((dire = ::readdir(dirp))) {
231                 string const fil = dire->d_name;
232                 if (suffixIs(fil, extension)) {
233                         dirlist.push_back(fil);
234                 }
235         }
236         ::closedir(dirp);
237         return dirlist;
238         /* I would have prefered to take a vector<string>& as parameter so
239            that we could avoid the copy of the vector when returning.
240            Then we would use:
241            dirlist.swap(argvec);
242            to avoid the copy. (Lgb)
243         */
244         /* A C++ implementaion will look like this:
245            string extension(ext);
246            if (extension[0] != '.') extension.insert(0, ".");
247            vector<string> dirlist;
248            directory_iterator dit("dir");
249            while (dit != directory_iterator()) {
250                    string fil = dit->filename;
251                    if (prefixIs(fil, extension)) {
252                            dirlist.push_back(fil);
253                    }
254                    ++dit;
255            }
256            dirlist.swap(argvec);
257            return;
258         */
259 }
260
261
262 // Returns the real name of file name in directory path, with optional
263 // extension ext.
264 string const FileSearch(string const & path, string const & name,
265                         string const & ext)
266 {
267         // if `name' is an absolute path, we ignore the setting of `path'
268         // Expand Environmentvariables in 'name'
269         string const tmpname = ReplaceEnvironmentPath(name);
270         string fullname = MakeAbsPath(tmpname, path);
271         // search first without extension, then with it.
272         if (IsFileReadable(fullname))
273                 return fullname;
274         else if (ext.empty())
275                 return string();
276         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
277                 fullname += '.';
278                 fullname += ext;
279                 if (IsFileReadable(fullname))
280                         return fullname;
281                 else
282                         return string();
283         }
284 }
285
286
287 // Search the file name.ext in the subdirectory dir of
288 //   1) user_lyxdir
289 //   2) build_lyxdir (if not empty)
290 //   3) system_lyxdir
291 string const LibFileSearch(string const & dir, string const & name,
292                            string const & ext)
293 {
294         string fullname = FileSearch(AddPath(user_lyxdir, dir), name, ext);
295         if (!fullname.empty())
296                 return fullname;
297
298         if (!build_lyxdir.empty())
299                 fullname = FileSearch(AddPath(build_lyxdir, dir), name, ext);
300         if (!fullname.empty())
301                 return fullname;
302
303         return FileSearch(AddPath(system_lyxdir, dir), name, ext);
304 }
305
306
307 string const
308 i18nLibFileSearch(string const & dir, string const & name,
309                   string const & ext)
310 {
311         // this comment is from intl/dcigettext.c. We try to mimick this
312         // behaviour here.
313         /* The highest priority value is the `LANGUAGE' environment
314            variable. But we don't use the value if the currently
315            selected locale is the C locale. This is a GNU extension. */
316
317         string const lc_all = GetEnv("LC_ALL");
318         string lang = GetEnv("LANGUAGE");
319         if (lang.empty() || lc_all == "C") {
320                 lang = lc_all;
321                 if (lang.empty()) {
322                         lang = GetEnv("LANG");
323                 }
324         }
325
326         lang = token(lang, '_', 0);
327
328         if (lang.empty() || lang == "C")
329                 return LibFileSearch(dir, name, ext);
330         else {
331                 string const tmp = LibFileSearch(dir, lang + '_' + name,
332                                                  ext);
333                 if (!tmp.empty())
334                         return tmp;
335                 else
336                         return LibFileSearch(dir, name, ext);
337         }
338 }
339
340
341 string const LibScriptSearch(string const & command)
342 {
343         string script;
344         string args = command;
345         split(args, script, ' ');
346         script = LibFileSearch("scripts", script);
347         if (script.empty())
348                 return command;
349         else if (args.empty())
350                 return script;
351         else
352                 return script + ' ' + args;
353 }
354
355
356 string const GetEnv(string const & envname)
357 {
358         // f.ex. what about error checking?
359         char const * const ch = getenv(envname.c_str());
360         string const envstr = !ch ? "" : ch;
361         return envstr;
362 }
363
364
365 string const GetEnvPath(string const & name)
366 {
367 #ifndef __EMX__
368         string const pathlist = subst(GetEnv(name), ':', ';');
369 #else
370         string const pathlist = os::slashify_path(GetEnv(name));
371 #endif
372         return strip(pathlist, ';');
373 }
374
375
376 bool PutEnv(string const & envstr)
377 {
378         // CHECK Look at and fix this.
379         // f.ex. what about error checking?
380
381 #if HAVE_PUTENV
382         // this leaks, but what can we do about it?
383         //   Is doing a getenv() and a free() of the older value
384         //   a good idea? (JMarc)
385         // Actually we don't have to leak...calling putenv like this
386         // should be enough: ... and this is obviously not enough if putenv
387         // does not make a copy of the string. It is also not very wise to
388         // put a string on the free store. If we have to leak we should do it
389         // like this:
390         char * leaker = new char[envstr.length() + 1];
391         envstr.copy(leaker, envstr.length());
392         leaker[envstr.length()] = '\0';
393         int const retval = lyx::putenv(leaker);
394
395         // If putenv does not make a copy of the char const * this
396         // is very dangerous. OTOH if it does take a copy this is the
397         // best solution.
398         // The  only implementation of putenv that I have seen does not
399         // allocate memory. _And_ after testing the putenv in glibc it
400         // seems that we need to make a copy of the string contents.
401         // I will enable the above.
402         //int retval = lyx::putenv(envstr.c_str());
403 #else
404 #ifdef HAVE_SETENV
405         string varname;
406         string const str = envstr.split(varname,'=');
407         int const retval = ::setenv(varname.c_str(), str.c_str(), true);
408 #else
409         // No environment setting function. Can this happen?
410         int const retval = 1; //return an error condition.
411 #endif
412 #endif
413         return retval == 0;
414 }
415
416
417 bool PutEnvPath(string const & envstr)
418 {
419         return PutEnv(envstr);
420 }
421
422
423 namespace {
424
425 int DeleteAllFilesInDir (string const & path)
426 {
427         // I have decided that we will be using parts from the boost
428         // library. Check out http://www.boost.org/
429         // For directory access we will then use the directory_iterator.
430         // Then the code will be something like:
431         // directory_iterator dit(path);
432         // directory_iterator dend;
433         // if (dit == dend) {
434         //         Alert::err_alert(_("Error! Cannot open directory:"), path);
435         //         return -1;
436         // }
437         // for (; dit != dend; ++dit) {
438         //         string filename(*dit);
439         //         if (filename == "." || filename == "..")
440         //                 continue;
441         //         string unlinkpath(AddName(path, filename));
442         //         if (lyx::unlink(unlinkpath))
443         //                 Alert::err_alert(_("Error! Could not remove file:"),
444         //                              unlinkpath);
445         // }
446         // return 0;
447         DIR * dir = ::opendir(path.c_str());
448         if (!dir) {
449                 Alert::err_alert (_("Error! Cannot open directory:"), path);
450                 return -1;
451         }
452         struct dirent * de;
453         int return_value = 0;
454         while ((de = readdir(dir))) {
455                 string const temp = de->d_name;
456                 if (temp == "." || temp == "..")
457                         continue;
458                 string const unlinkpath = AddName (path, temp);
459
460                 lyxerr[Debug::FILES] << "Deleting file: " << unlinkpath
461                                      << endl;
462
463                 bool deleted = true;
464                 FileInfo fi(unlinkpath);
465                 if (fi.isOK() && fi.isDir())
466                         deleted = (DeleteAllFilesInDir(unlinkpath) == 0);
467                 deleted &= (lyx::unlink(unlinkpath) == 0);
468                 if (!deleted) {
469                         Alert::err_alert(_("Error! Could not remove file:"),
470                                 unlinkpath);
471                         return_value = -1;
472                 }
473         }
474         closedir(dir);
475         return return_value;
476 }
477
478
479 string const CreateTmpDir(string const & tempdir, string const & mask)
480 {
481         lyxerr[Debug::FILES]
482                 << "CreateTmpDir: tempdir=`" << tempdir << "'\n"
483                 << "CreateTmpDir:    mask=`" << mask << "'" << endl;
484
485         string const tmpfl(lyx::tempName(tempdir, mask));
486         // lyx::tempName actually creates a file to make sure that it
487         // stays unique. So we have to delete it before we can create
488         // a dir with the same name. Note also that we are not thread
489         // safe because of the gap between unlink and mkdir. (Lgb)
490         lyx::unlink(tmpfl.c_str());
491
492         if (tmpfl.empty() || lyx::mkdir(tmpfl, 0700)) {
493                 Alert::err_alert(_("Error! Couldn't create temporary directory:"),
494                              tempdir);
495                 return string();
496         }
497         return MakeAbsPath(tmpfl);
498 }
499
500
501 int DestroyTmpDir(string const & tmpdir, bool Allfiles)
502 {
503 #ifdef __EMX__
504         Path p(user_lyxdir);
505 #endif
506         if (Allfiles && DeleteAllFilesInDir(tmpdir)) {
507                 return -1;
508         }
509         if (lyx::rmdir(tmpdir)) {
510                 Alert::err_alert(_("Error! Couldn't delete temporary directory:"),
511                              tmpdir);
512                 return -1;
513         }
514         return 0;
515 }
516
517 } // namespace anon
518
519
520 string const CreateBufferTmpDir(string const & pathfor)
521 {
522         static int count;
523         static string const tmpdir(pathfor.empty() ? os::getTmpDir() : pathfor);
524         // We are in our own directory.  Why bother to mangle name?
525         // In fact I wrote this code to circumvent a problematic behaviour (bug?)
526         // of EMX mkstemp().
527         string const tmpfl = tmpdir + "/lyx_tmpbuf" + tostr(count++);
528         if (lyx::mkdir(tmpfl, 0777)) {
529                 Alert::err_alert(_("Error! Couldn't create temporary directory:"),
530                              tmpdir);
531                 return string();
532         }
533         return tmpfl;
534 }
535
536
537 int DestroyBufferTmpDir(string const & tmpdir)
538 {
539         return DestroyTmpDir(tmpdir, true);
540 }
541
542
543 string const CreateLyXTmpDir(string const & deflt)
544 {
545         if ((!deflt.empty()) && (deflt  != "/tmp")) {
546                 if (lyx::mkdir(deflt, 0777)) {
547 #ifdef __EMX__
548                 Path p(user_lyxdir);
549 #endif
550                         return CreateTmpDir(deflt, "lyx_tmpdir");
551                 } else
552                         return deflt;
553         } else {
554 #ifdef __EMX__
555                 Path p(user_lyxdir);
556 #endif
557                 return CreateTmpDir("/tmp", "lyx_tmpdir");
558         }
559 }
560
561
562 int DestroyLyXTmpDir(string const & tmpdir)
563 {
564         return DestroyTmpDir (tmpdir, false); // Why false?
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 // FITS ...BITPIX...
997 // GIF  GIF...
998 // JPG  JFIF
999 // PDF  %PDF-...
1000 // PNG  .PNG...
1001 // PBM  P1... or P4     (B/W)
1002 // PGM  P2... or P5     (Grayscale)
1003 // PPM  P3... or P6     (color)
1004 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
1005 // SGI  \001\332...     (decimal 474)
1006 // TGIF %TGIF...
1007 // TIFF II... or MM...
1008 // XBM  ..._bits[]...
1009 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
1010 //      ...static char *...
1011 // XWD  \000\000\000\151        (0x00006900) decimal 105
1012 //
1013 // GZIP \037\213\010\010...     http://www.ietf.org/rfc/rfc1952.txt
1014 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
1015 // Z    \037\177                UNIX compress
1016
1017 /// return the "extension" which belongs to the contents.
1018 /// for no knowing contents return the extension. Without
1019 /// an extension and unknown contents we return "user"
1020 string const getExtFromContents(string const & filename)
1021 {
1022         // paranoia check
1023         if (filename.empty() || !IsFileReadable(filename))
1024                 return string();
1025
1026         ifstream ifs(filename.c_str());
1027         if (!ifs)
1028                 // Couldn't open file...
1029                 return string();
1030
1031         // gnuzip
1032         string const gzipStamp = "\037\213\010\010";
1033
1034         // PKZIP
1035         string const zipStamp = "PK";
1036
1037         // compress
1038         string const compressStamp = "\037\177";
1039
1040         // Maximum strings to read
1041         int const max_count = 50;
1042         int count = 0;
1043
1044         string str, format;
1045         bool firstLine = true;
1046         while ((count++ < max_count) && format.empty()) {
1047                 if (ifs.eof()) {
1048                         lyxerr[Debug::GRAPHICS]
1049                                 << "filetools(getExtFromContents)\n"
1050                                 << "\tFile type not recognised before EOF!"
1051                                 << endl;
1052                         break;
1053                 }
1054
1055                 getline(ifs, str);
1056
1057                 lyxerr[Debug::GRAPHICS] << "Scanstring: " << str.substr(0,60)
1058                                         << endl;
1059
1060                 string const stamp = str.substr(0,2);
1061                 if (firstLine && str.size() >= 2) {
1062                         // at first we check for a zipped file, because this
1063                         // information is saved in the first bytes of the file!
1064                         // also some graphic formats which save the information
1065                         // in the first line, too.
1066                         if (prefixIs(str, gzipStamp)) {
1067                                 format =  "gzip";
1068
1069                         } else if (stamp == zipStamp) {
1070                                 format =  "zip";
1071
1072                         } else if (stamp == compressStamp) {
1073                                 format =  "compress";
1074
1075                         // the graphics part
1076                         } else if (stamp == "BM") {
1077                                 format =  "bmp";
1078
1079                         } else if (stamp == "\001\332") {
1080                                 format =  "sgi";
1081
1082                         // PBM family
1083                         // Don't need to use str.at(0), str.at(1) because
1084                         // we already know that str.size() >= 2
1085                         } else if (str[0] == 'P') {
1086                                 switch (str[1]) {
1087                                 case '1':
1088                                 case '4':
1089                                         format =  "pbm";
1090                                     break;
1091                                 case '2':
1092                                 case '5':
1093                                         format =  "pgm";
1094                                     break;
1095                                 case '3':
1096                                 case '6':
1097                                         format =  "ppm";
1098                                 }
1099                                 break;
1100
1101                         } else if ((stamp == "II") || (stamp == "MM")) {
1102                                 format =  "tiff";
1103
1104                         } else if (prefixIs(str,"%TGIF")) {
1105                                 format =  "tgif";
1106
1107                         } else if (prefixIs(str,"GIF")) {
1108                                 format =  "gif";
1109
1110                         } else if (str.size() > 3) {
1111                                 int const c = ((str[0] << 24) & (str[1] << 16) &
1112                                                (str[2] << 8)  & str[3]);
1113                                 if (c == 105) {
1114                                         format =  "xwd";
1115                                 }
1116                         }
1117
1118                         firstLine = false;
1119                 }
1120
1121                 if (!format.empty())
1122                     break;
1123                 else if (contains(str,"EPSF"))
1124                         // dummy, if we have wrong file description like
1125                         // %!PS-Adobe-2.0EPSF"
1126                         format =  "eps";
1127
1128                 else if (contains(str,"Grace"))
1129                         format =  "agr";
1130
1131                 else if (contains(str,"JFIF"))
1132                         format =  "jpg";
1133
1134                 else if (contains(str,"%PDF"))
1135                         format =  "pdf";
1136
1137                 else if (contains(str,"PNG"))
1138                         format =  "png";
1139
1140                 else if (contains(str,"%!PS-Adobe")) {
1141                         // eps or ps
1142                         ifs >> str;
1143                         if (contains(str,"EPSF"))
1144                                 format = "eps";
1145                         else
1146                             format = "ps";
1147                 }
1148
1149                 else if (contains(str,"_bits[]"))
1150                         format = "xbm";
1151
1152                 else if (contains(str,"XPM") || contains(str, "static char *"))
1153                         format = "xpm";
1154
1155                 else if (contains(str,"BITPIX"))
1156                         format = "fits";
1157         }
1158
1159         if (!format.empty()) {
1160             lyxerr[Debug::GRAPHICS]
1161                 << "Recognised Fileformat: " << format << endl;
1162             return format;
1163         }
1164
1165         string const ext(GetExtension(filename));
1166         lyxerr[Debug::GRAPHICS]
1167                 << "filetools(getExtFromContents)\n"
1168                 << "\tCouldn't find a known Type!\n";
1169         if (!ext.empty()) {
1170             lyxerr[Debug::GRAPHICS]
1171                 << "\twill take the file extension -> "
1172                 << ext << endl;
1173                 return ext;
1174         } else {
1175             lyxerr[Debug::GRAPHICS]
1176                 << "\twill use ext or a \"user\" defined format" << endl;
1177             return "user";
1178         }
1179 }
1180
1181
1182 /// check for zipped file
1183 bool zippedFile(string const & name)
1184 {
1185         string const type = getExtFromContents(name);
1186         if (contains("gzip zip compress", type) && !type.empty())
1187                 return true;
1188         return false;
1189 }
1190
1191
1192 string const unzipFile(string const & zipped_file)
1193 {
1194         string const file = ChangeExtension(zipped_file, string());
1195         string  const tempfile = lyx::tempName(string(), file);
1196         // Run gunzip
1197         string const command = "gunzip -c " + zipped_file + " > " + tempfile;
1198         Systemcall one;
1199         one.startscript(Systemcall::Wait, command);
1200         // test that command was executed successfully (anon)
1201         // yes, please do. (Lgb)
1202         return tempfile;
1203 }
1204
1205
1206 // Creates a nice compact path for displaying
1207 string const
1208 MakeDisplayPath (string const & path, unsigned int threshold)
1209 {
1210         string::size_type const l1 = path.length();
1211
1212         // First, we try a relative path compared to home
1213         string const home(GetEnvPath("HOME"));
1214         string relhome = MakeRelPath(path, home);
1215
1216         string::size_type l2 = relhome.length();
1217
1218         string prefix;
1219
1220         // If we backup from home or don't have a relative path,
1221         // this try is no good
1222         if (prefixIs(relhome, "../") || os::is_absolute_path(relhome)) {
1223                 // relative path was no good, just use the original path
1224                 relhome = path;
1225                 l2 = l1;
1226         } else {
1227                 prefix = "~/";
1228         }
1229
1230         // Is the path too long?
1231         if (l2 > threshold) {
1232                 // Yes, shortend it
1233                 prefix += ".../";
1234
1235                 string temp;
1236
1237                 while (relhome.length() > threshold)
1238                         relhome = split(relhome, temp, '/');
1239
1240                 // Did we shortend everything away?
1241                 if (relhome.empty()) {
1242                         // Yes, filename in itself is too long.
1243                         // Pick the start and the end of the filename.
1244                         relhome = OnlyFilename(path);
1245                         string const head = relhome.substr(0, threshold/2 - 3);
1246
1247                         l2 = relhome.length();
1248                         string const tail =
1249                                 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
1250                         relhome = head + "..." + tail;
1251                 }
1252         }
1253         return prefix + relhome;
1254 }
1255
1256
1257 bool LyXReadLink(string const & File, string & Link)
1258 {
1259         char LinkBuffer[512];
1260         // Should be PATH_MAX but that needs autconf support
1261         int const nRead = ::readlink(File.c_str(),
1262                                      LinkBuffer, sizeof(LinkBuffer) - 1);
1263         if (nRead <= 0)
1264                 return false;
1265         LinkBuffer[nRead] = '\0'; // terminator
1266         Link = LinkBuffer;
1267         return true;
1268 }
1269
1270
1271 namespace {
1272
1273 typedef pair<int, string> cmdret;
1274
1275 cmdret const do_popen(string const & cmd)
1276 {
1277         // One question is if we should use popen or
1278         // create our own popen based on fork, exec, pipe
1279         // of course the best would be to have a
1280         // pstream (process stream), with the
1281         // variants ipstream, opstream
1282
1283         FILE * inf = ::popen(cmd.c_str(), os::read_mode());
1284
1285         // (Claus Hentschel) Check if popen was succesful ;-)
1286         if (!inf)
1287                 return make_pair(-1, string());
1288
1289         string ret;
1290         int c = fgetc(inf);
1291         while (c != EOF) {
1292                 ret += static_cast<char>(c);
1293                 c = fgetc(inf);
1294         }
1295         int const pret = pclose(inf);
1296         return make_pair(pret, ret);
1297 }
1298
1299 } // namespace anon
1300
1301
1302 string const findtexfile(string const & fil, string const & /*format*/)
1303 {
1304         /* There is no problem to extend this function too use other
1305            methods to look for files. It could be setup to look
1306            in environment paths and also if wanted as a last resort
1307            to a recursive find. One of the easier extensions would
1308            perhaps be to use the LyX file lookup methods. But! I am
1309            going to implement this until I see some demand for it.
1310            Lgb
1311         */
1312
1313         // If the file can be found directly, we just return a
1314         // absolute path version of it.
1315         if (FileInfo(fil).exist())
1316                 return MakeAbsPath(fil);
1317
1318         // No we try to find it using kpsewhich.
1319         // It seems from the kpsewhich manual page that it is safe to use
1320         // kpsewhich without --format: "When the --format option is not
1321         // given, the search path used when looking for a file is inferred
1322         // from the name given, by looking for a known extension. If no
1323         // known extension is found, the search path for TeX source files
1324         // is used."
1325         // However, we want to take advantage of the format sine almost all
1326         // the different formats has environment variables that can be used
1327         // to controll which paths to search. f.ex. bib looks in
1328         // BIBINPUTS and TEXBIB. Small list follows:
1329         // bib - BIBINPUTS, TEXBIB
1330         // bst - BSTINPUTS
1331         // graphic/figure - TEXPICTS, TEXINPUTS
1332         // ist - TEXINDEXSTYLE, INDEXSTYLE
1333         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1334         // tex - TEXINPUTS
1335         // tfm - TFMFONTS, TEXFONTS
1336         // This means that to use kpsewhich in the best possible way we
1337         // should help it by setting additional path in the approp. envir.var.
1338         string const kpsecmd = "kpsewhich " + fil;
1339
1340         cmdret const c = do_popen(kpsecmd);
1341
1342         lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1343                  << "kpse result = `" << strip(c.second, '\n')
1344                  << "'" << endl;
1345         if (c.first != -1)
1346                 return os::internal_path(strip(strip(c.second, '\n'), '\r'));
1347         else
1348                 return string();
1349 }
1350
1351
1352 void removeAutosaveFile(string const & filename)
1353 {
1354         string a = OnlyPath(filename);
1355         a += '#';
1356         a += OnlyFilename(filename);
1357         a += '#';
1358         FileInfo const fileinfo(a);
1359         if (fileinfo.exist()) {
1360                 if (lyx::unlink(a) != 0) {
1361                         Alert::err_alert(_("Could not delete auto-save file!"), a);
1362                 }
1363         }
1364 }
1365
1366
1367 string const readBB_from_PSFile(string const & file)
1368 {
1369         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1370         // It seems that every command in the header has an own line,
1371         // getline() should work for all files.
1372         // On the other hand some plot programs write the bb at the
1373         // end of the file. Than we have in the header:
1374         // %%BoundingBox: (atend)
1375         // In this case we must check the end.
1376         string const file_ = zippedFile(file) ?
1377                 string(unzipFile(file)) : string(file);
1378         string const format = getExtFromContents(file_);
1379         if (format != "eps" && format != "ps")
1380                 return string();
1381
1382         std::ifstream is(file_.c_str());
1383         while (is) {
1384                 string s;
1385                 getline(is,s);
1386                 if (contains(s,"%%BoundingBox:") && !contains(s,"atend"))
1387                         return (frontStrip(s.substr(14)));
1388         }
1389         return string();
1390 }