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