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