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