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