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