]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
small cleanup, doxygen, formatting changes
[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 "support/syscall.h"
36 #include "gettext.h"
37 #include "lyxlib.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 contains(filename, ".lyx");
72 }
73
74
75 bool IsSGMLFilename(string const & filename)
76 {
77         return contains(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         // CHECK Add proper emx support here!
108 #ifndef __EMX__
109         return "\'" + name + "\'";
110 #else
111         return name; 
112 #endif
113 }
114
115
116 // Is a file readable ?
117 bool IsFileReadable (string const & path)
118 {
119         FileInfo file(path);
120         if (file.isOK() && file.isRegular() && file.readable())
121                 return true;
122         else
123                 return false;
124 }
125
126
127 // Is a file read_only?
128 // return 1 read-write
129 //        0 read_only
130 //       -1 error (doesn't exist, no access, anything else) 
131 int IsFileWriteable (string const & path)
132 {
133         FileInfo fi(path);
134         if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
135                 return 1;
136         if (fi.readable()) // read-only
137                 return 0;
138         return -1; // everything else.
139 }
140
141
142 //returns 1: dir writeable
143 //        0: not writeable
144 //       -1: error- couldn't find out
145 int IsDirWriteable (string const & path)
146 {
147         string const tmpfl(lyx::tempName(path, "lyxwritetest"));
148         // We must unlink the tmpfl.
149         lyx::unlink(tmpfl);
150         
151         if (tmpfl.empty()) {
152                 WriteFSAlert(_("LyX Internal Error!"), 
153                              _("Could not test if directory is writeable"));
154                 return -1;
155         } else {
156                 FileInfo fi(path);
157                 if (fi.writable()) return 1;
158                 return 0;
159         }
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 = CleanupPath(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         
254         // search first without extension, then with it.
255         if (IsFileReadable(fullname))
256                 return fullname;
257         else if (ext.empty()) 
258                 return string();
259         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
260                 fullname += '.';
261                 fullname += ext;
262                 if (IsFileReadable(fullname))
263                         return fullname;
264                 else 
265                         return string();
266         }
267 }
268
269
270 // Search the file name.ext in the subdirectory dir of
271 //   1) user_lyxdir
272 //   2) build_lyxdir (if not empty)
273 //   3) system_lyxdir
274 string const LibFileSearch(string const & dir, string const & name, 
275                            string const & ext)
276 {
277         string fullname = FileSearch(AddPath(user_lyxdir, dir),
278                                      name, ext); 
279         if (!fullname.empty())
280                 return fullname;
281         
282         if (!build_lyxdir.empty()) 
283                 fullname = FileSearch(AddPath(build_lyxdir, dir), 
284                                       name, ext);
285         if (!fullname.empty())
286                 return fullname;
287         
288         return FileSearch(AddPath(system_lyxdir, dir), name, ext);
289 }
290
291
292 string const
293 i18nLibFileSearch(string const & dir, string const & name, 
294                   string const & ext)
295 {
296         string const lang = token(string(GetEnv("LANG")), '_', 0);
297         
298         if (lang.empty() || lang == "C")
299                 return LibFileSearch(dir, name, ext);
300         else {
301                 string const tmp = LibFileSearch(dir, lang + '_' + name,
302                                                  ext);
303                 if (!tmp.empty())
304                         return tmp;
305                 else
306                         return LibFileSearch(dir, name, ext);
307         }
308 }
309
310
311 string const GetEnv(string const & envname)
312 {
313         // f.ex. what about error checking?
314         char const * const ch = getenv(envname.c_str());
315         string const envstr = !ch ? "" : ch;
316         return envstr;
317 }
318
319
320 string const GetEnvPath(string const & name)
321 {
322 #ifndef __EMX__
323         string const pathlist = subst(GetEnv(name), ':', ';');
324 #else
325         string const pathlist = subst(GetEnv(name), '\\', '/');
326 #endif
327         return strip(pathlist, ';');
328 }
329
330
331 bool PutEnv(string const & envstr)
332 {
333         // CHECK Look at and fix this.
334         // f.ex. what about error checking?
335
336 #if HAVE_PUTENV
337         // this leaks, but what can we do about it?
338         //   Is doing a getenv() and a free() of the older value 
339         //   a good idea? (JMarc)
340         // Actually we don't have to leak...calling putenv like this
341         // should be enough: ... and this is obviously not enough if putenv
342         // does not make a copy of the string. It is also not very wise to
343         // put a string on the free store. If we have to leak we should do it
344         // like this:
345         char * leaker = new char[envstr.length() + 1];
346         envstr.copy(leaker, envstr.length());
347         leaker[envstr.length()] = '\0';
348         int const retval = lyx::putenv(leaker);
349
350         // If putenv does not make a copy of the char const * this
351         // is very dangerous. OTOH if it does take a copy this is the
352         // best solution.
353         // The  only implementation of putenv that I have seen does not
354         // allocate memory. _And_ after testing the putenv in glibc it
355         // seems that we need to make a copy of the string contents.
356         // I will enable the above.
357         //int retval = lyx::putenv(envstr.c_str());
358 #else
359 #ifdef HAVE_SETENV 
360         string varname;
361         string const str = envstr.split(varname,'=');
362         int const retval = ::setenv(varname.c_str(), str.c_str(), true);
363 #else
364         // No environment setting function. Can this happen?
365         int const retval = 1; //return an error condition.
366 #endif
367 #endif
368         return retval == 0;
369 }
370
371
372 bool PutEnvPath(string const & envstr)
373 {
374         return PutEnv(envstr);
375 }
376
377
378 static
379 int DeleteAllFilesInDir (string const & path)
380 {
381         // I have decided that we will be using parts from the boost
382         // library. Check out http://www.boost.org/
383         // For directory access we will then use the directory_iterator.
384         // Then the code will be something like:
385         // directory_iterator dit(path);
386         // directory_iterator dend;
387         // if (dit == dend) {
388         //         WriteFSAlert(_("Error! Cannot open directory:"), path);
389         //         return -1;
390         // }
391         // for (; dit != dend; ++dit) {
392         //         string filename(*dit);
393         //         if (filename == "." || filename == "..")
394         //                 continue;
395         //         string unlinkpath(AddName(path, filename));
396         //         if (lyx::unlink(unlinkpath))
397         //                 WriteFSAlert(_("Error! Could not remove file:"),
398         //                              unlinkpath);
399         // }
400         // return 0;
401         DIR * dir = ::opendir(path.c_str());
402         if (!dir) {
403                 WriteFSAlert (_("Error! Cannot open directory:"), path);
404                 return -1;
405         }
406         struct dirent * de;
407         int return_value = 0;
408         while ((de = readdir(dir))) {
409                 string const temp = de->d_name;
410                 if (temp == "." || temp == "..") 
411                         continue;
412                 string const unlinkpath = AddName (path, temp);
413
414                 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
415
416                 bool deleted = true;
417                 if (FileInfo(unlinkpath).isDir())
418                         deleted = (DeleteAllFilesInDir(unlinkpath) == 0);
419                 deleted &= (lyx::unlink(unlinkpath) == 0);
420                 if (!deleted) {
421                         WriteFSAlert (_("Error! Could not remove file:"), 
422                                       unlinkpath);
423                         return_value = -1;
424                 }
425         }
426         closedir(dir);
427         return return_value;
428 }
429
430
431 static
432 string const CreateTmpDir(string const & tempdir, string const & mask)
433 {
434         lyxerr[Debug::FILES]
435                 << "CreateTmpDir: tempdir=`" << tempdir << "'\n"
436                 << "CreateTmpDir:    mask=`" << mask << "'" << endl;
437         
438         string const tmpfl(lyx::tempName(tempdir, mask));
439         // lyx::tempName actually creates a file to make sure that it
440         // stays unique. So we have to delete it before we can create
441         // a dir with the same name. Note also that we are not thread
442         // safe because of the gap between unlink and mkdir. (Lgb)
443         lyx::unlink(tmpfl.c_str());
444         
445         if (tmpfl.empty() || lyx::mkdir(tmpfl, 0777)) {
446                 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
447                              tempdir);
448                 return string();
449         }
450         return MakeAbsPath(tmpfl);
451 }
452
453
454 static
455 int DestroyTmpDir(string const & tmpdir, bool Allfiles)
456 {
457 #ifdef __EMX__
458         Path p(user_lyxdir);
459 #endif
460         if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
461         if (lyx::rmdir(tmpdir)) { 
462                 WriteFSAlert(_("Error! Couldn't delete temporary directory:"), 
463                              tmpdir);
464                 return -1;
465         }
466         return 0; 
467
468
469
470 string const CreateBufferTmpDir(string const & pathfor)
471 {
472         return CreateTmpDir(pathfor, "lyx_tmpbuf");
473 }
474
475
476 int DestroyBufferTmpDir(string const & tmpdir)
477 {
478         return DestroyTmpDir(tmpdir, true);
479 }
480
481
482 string const CreateLyXTmpDir(string const & deflt)
483 {
484         if ((!deflt.empty()) && (deflt  != "/tmp")) {
485                 if (lyx::mkdir(deflt, 0777)) {
486 #ifdef __EMX__
487                         Path p(user_lyxdir);
488 #endif
489                         string const t(CreateTmpDir(deflt, "lyx_tmpdir"));
490                         return t;
491                 } else
492                         return deflt;
493         } else {
494 #ifdef __EMX__
495                 Path p(user_lyxdir);
496 #endif
497                 string const t(CreateTmpDir("/tmp", "lyx_tmpdir"));
498                 return t;
499         }
500 }
501
502
503 int DestroyLyXTmpDir(string const & tmpdir)
504 {
505        return DestroyTmpDir (tmpdir, false); // Why false?
506 }
507
508
509 // Creates directory. Returns true if succesfull
510 bool createDirectory(string const & path, int permission)
511 {
512         string temp(strip(CleanupPath(path), '/'));
513
514         if (temp.empty()) {
515                 WriteAlert(_("Internal error!"),
516                            _("Call to createDirectory with invalid name"));
517                 return false;
518         }
519
520         if (lyx::mkdir(temp, permission)) {
521                 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
522                 return false;
523         }
524         return true;
525 }
526
527
528 // Strip filename from path name
529 string const OnlyPath(string const & Filename)
530 {
531         // If empty filename, return empty
532         if (Filename.empty()) return Filename;
533
534         // Find last / or start of filename
535         string::size_type j = Filename.rfind('/');
536         if (j == string::npos)
537                 return "./";
538         return Filename.substr(0, j + 1);
539 }
540
541
542 // Convert relative path into absolute path based on a basepath.
543 // If relpath is absolute, just use that.
544 // If basepath is empty, use CWD as base.
545 string const MakeAbsPath(string const & RelPath, string const & BasePath)
546 {
547         // checks for already absolute path
548         if (AbsolutePath(RelPath))
549 #ifdef __EMX__
550                 if (RelPath[0]!= '/' && RelPath[0]!= '\\')
551 #endif
552                 return RelPath;
553
554         // Copies given paths
555         string TempRel(CleanupPath(RelPath));
556
557         string TempBase;
558
559         if (!BasePath.empty()) {
560 #ifndef __EMX__
561                 TempBase = BasePath;
562 #else
563                 char * with_drive = new char[_MAX_PATH];
564                 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
565                 TempBase = with_drive;
566                 delete[] with_drive;
567 #endif
568         } else
569                 TempBase = lyx::getcwd(); //GetCWD();
570 #ifdef __EMX__
571         if (AbsolutePath(TempRel))
572                 return TempBase.substr(0, 2) + TempRel;
573 #endif
574
575         // Handle /./ at the end of the path
576         while(suffixIs(TempBase, "/./"))
577                 TempBase.erase(TempBase.length() - 2);
578
579         // processes relative path
580         string RTemp(TempRel);
581         string Temp;
582
583         while (!RTemp.empty()) {
584                 // Split by next /
585                 RTemp = split(RTemp, Temp, '/');
586                 
587                 if (Temp == ".") continue;
588                 if (Temp == "..") {
589                         // Remove one level of TempBase
590                         string::difference_type i = TempBase.length() - 2;
591 #ifndef __EMX__
592                         if (i < 0) i = 0;
593                         while (i > 0 && TempBase[i] != '/') --i;
594                         if (i > 0)
595 #else
596                         if (i < 2) i = 2;
597                         while (i > 2 && TempBase[i] != '/') --i;
598                         if (i > 2)
599 #endif
600                                 TempBase.erase(i, string::npos);
601                         else
602                                 TempBase += '/';
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 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 = CleanupPath(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 // Strips filename from path
652 string const BasePath(string const & fname)
653 {
654         if (fname.empty())
655                 return fname;
656
657         string::size_type j = fname.rfind('/');
658         if (j == string::npos) // no '/' in fname
659                 return string();
660
661         // Strip to basename
662         return fname.substr(0,j + 1);
663 }
664
665
666 // Is a filename/path absolute?
667 bool AbsolutePath(string const & path)
668 {
669 #ifndef __EMX__
670         return (!path.empty() && path[0] == '/');
671 #else
672         return (!path.empty() && (path[0] == '/' || (isalpha(static_cast<unsigned char>(path[0])) && path.length()>1 && path[1] == ':')));
673 #endif
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 (AbsolutePath(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 (AbsolutePath(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 CleanupPath(string const & path) 
745 {
746 #ifdef __EMX__    /* SMiyata: This should fix searchpath bug. */
747         string temppath = subst(path, '\\', '/');
748         temppath = subst(temppath, "//", "/");
749         return lowercase(temppath);
750 #else // On unix, nothing to do
751         return path;
752 #endif
753 }
754
755
756 string const GetFileContents(string const & fname)
757 {
758         FileInfo finfo(fname);
759         if (finfo.exist()) {
760                 ifstream ifs(fname.c_str());
761                 std::ostringstream ofs;
762                 if (ifs && ofs) {
763                         ofs << ifs.rdbuf();
764                         ifs.close();
765                         return ofs.str().c_str();
766                 }
767         }
768         lyxerr << "LyX was not able to read file '" << fname << "'" << endl;
769         return string();
770 }
771
772
773 //
774 // Search ${...} as Variable-Name inside the string and replace it with
775 // the denoted environmentvariable
776 // Allow Variables according to 
777 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
778 //
779
780 string const ReplaceEnvironmentPath(string const & path)
781 {
782 // 
783 // CompareChar: Environmentvariables starts with this character
784 // PathChar:    Next path component start with this character
785 // while CompareChar found do:
786 //       Split String with PathChar
787 //       Search Environmentvariable
788 //       if found: Replace Strings
789 //
790         char const CompareChar = '$';
791         char const FirstChar = '{'; 
792         char const EndChar = '}'; 
793         char const UnderscoreChar = '_'; 
794         string EndString; EndString += EndChar;
795         string FirstString; FirstString += FirstChar;
796         string CompareString; CompareString += CompareChar;
797         string const RegExp("*}*"); // Exist EndChar inside a String?
798
799 // first: Search for a '$' - Sign.
800         //string copy(path);
801         string result1; //(copy);    // for split-calls
802         string result0 = split(path, result1, CompareChar);
803         while (!result0.empty()) {
804                 string copy1(result0); // contains String after $
805                 
806                 // Check, if there is an EndChar inside original String.
807                 
808                 if (!regexMatch(copy1, RegExp)) {
809                         // No EndChar inside. So we are finished
810                         result1 += CompareString + result0;
811                         result0.erase();
812                         continue;
813                 }
814
815                 string res1;
816                 string res0 = split(copy1, res1, EndChar);
817                 // Now res1 holds the environmentvariable
818                 // First, check, if Contents is ok.
819                 if (res1.empty()) { // No environmentvariable. Continue Loop.
820                         result1 += CompareString + FirstString;
821                         result0  = res0;
822                         continue;
823                 }
824                 // check contents of res1
825                 char const * res1_contents = res1.c_str();
826                 if (*res1_contents != FirstChar) {
827                         // Again No Environmentvariable
828                         result1 += CompareString;
829                         result0 = res0;
830                 }
831
832                 // Check for variable names
833                 // Situation ${} is detected as "No Environmentvariable"
834                 char const * cp1 = res1_contents + 1;
835                 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
836                 ++cp1;
837                 while (*cp1 && result) {
838                         result = isalnum(*cp1) || 
839                                 (*cp1 == UnderscoreChar); 
840                         ++cp1;
841                 }
842
843                 if (!result) {
844                         // no correct variable name
845                         result1 += CompareString + res1 + EndString;
846                         result0  = split(res0, res1, CompareChar);
847                         result1 += res1;
848                         continue;
849                 }
850             
851                 string env(GetEnv(res1_contents + 1));
852                 if (!env.empty()) {
853                         // Congratulations. Environmentvariable found
854                         result1 += env;
855                 } else {
856                         result1 += CompareString + res1 + EndString;
857                 }
858                 // Next $-Sign?
859                 result0  = split(res0, res1, CompareChar);
860                 result1 += res1;
861         } 
862         return result1;
863 }  // ReplaceEnvironmentPath
864
865
866 // Make relative path out of two absolute paths
867 string const MakeRelPath(string const & abspath0, string const & basepath0)
868 // Makes relative path out of absolute path. If it is deeper than basepath,
869 // it's easy. If basepath and abspath share something (they are all deeper
870 // than some directory), it'll be rendered using ..'s. If they are completely
871 // different, then the absolute path will be used as relative path.
872 {
873         // This is a hack. It should probaly be done in another way. Lgb.
874         string const abspath = CleanupPath(abspath0);
875         string const basepath = CleanupPath(basepath0);
876         if (abspath.empty())
877                 return "<unknown_path>";
878
879         string::size_type const abslen = abspath.length();
880         string::size_type const baselen = basepath.length();
881         
882         // Find first different character
883         string::size_type i = 0;
884         while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
885
886         // Go back to last /
887         if (i < abslen && i < baselen
888             || (i < abslen && abspath[i] != '/' && i == baselen)
889             || (i < baselen && basepath[i] != '/' && i == abslen))
890         {
891                 if (i) --i;     // here was the last match
892                 while (i && abspath[i] != '/') --i;
893         }
894
895         if (i == 0) {
896                 // actually no match - cannot make it relative
897                 return abspath;
898         }
899
900         // Count how many dirs there are in basepath above match
901         // and append as many '..''s into relpath
902         string buf;
903         string::size_type j = i;
904         while (j < baselen) {
905                 if (basepath[j] == '/') {
906                         if (j + 1 == baselen) break;
907                         buf += "../";
908                 }
909                 ++j;
910         }
911
912         // Append relative stuff from common directory to abspath
913         if (abspath[i] == '/') ++i;
914         for (; i < abslen; ++i)
915                 buf += abspath[i];
916         // Remove trailing /
917         if (suffixIs(buf, '/'))
918                 buf.erase(buf.length() - 1);
919         // Substitute empty with .
920         if (buf.empty())
921                 buf = '.';
922         return buf;
923 }
924
925
926 // Append sub-directory(ies) to a path in an intelligent way
927 string const AddPath(string const & path, string const & path_2)
928 {
929         string buf;
930         string const path2 = CleanupPath(path_2);
931
932         if (!path.empty() && path != "." && path != "./") {
933                 buf = CleanupPath(path);
934                 if (path[path.length() - 1] != '/')
935                         buf += '/';
936         }
937
938         if (!path2.empty())
939                 buf += frontStrip(strip(path2, '/'), '/') + '/';
940
941         return buf;
942 }
943
944
945 /* 
946  Change extension of oldname to extension.
947  Strips path off if no_path == true.
948  If no extension on oldname, just appends.
949  */
950 string const
951 ChangeExtension(string const & oldname, string const & extension)
952 {
953         string::size_type const last_slash = oldname.rfind('/');
954         string::size_type last_dot = oldname.rfind('.');
955         if (last_dot < last_slash && last_slash != string::npos)
956                 last_dot = string::npos;
957         
958         string ext;
959         // Make sure the extension starts with a dot
960         if (!extension.empty() && extension[0] != '.')
961                 ext= "." + extension;
962         else
963                 ext = extension;
964
965         return CleanupPath(oldname.substr(0, last_dot) + ext);
966 }
967
968
969 /// Return the extension of the file (not including the .)
970 string const GetExtension(string const & name)
971 {
972         string::size_type const last_slash = name.rfind('/');
973         string::size_type const last_dot = name.rfind('.');
974         if (last_dot != string::npos &&
975             (last_slash == string::npos || last_dot > last_slash))
976                 return name.substr(last_dot + 1,
977                                    name.length() - (last_dot + 1));
978         else
979                 return string();
980 }
981
982
983 // Creates a nice compact path for displaying
984 string const
985 MakeDisplayPath (string const & path, unsigned int threshold)
986 {
987         string::size_type const l1 = path.length();
988
989         // First, we try a relative path compared to home
990         string const home(GetEnvPath("HOME"));
991         string relhome = MakeRelPath(path, home);
992
993         string::size_type l2 = relhome.length();
994
995         string prefix;
996
997         // If we backup from home or don't have a relative path,
998         // this try is no good
999         if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
1000                 // relative path was no good, just use the original path
1001                 relhome = path;
1002                 l2 = l1;
1003         } else {
1004                 prefix = "~/";
1005         }
1006
1007         // Is the path too long?
1008         if (l2 > threshold) {
1009                 // Yes, shortend it
1010                 prefix += ".../";
1011                 
1012                 string temp;
1013                 
1014                 while (relhome.length() > threshold)
1015                         relhome = split(relhome, temp, '/');
1016
1017                 // Did we shortend everything away?
1018                 if (relhome.empty()) {
1019                         // Yes, filename in itself is too long.
1020                         // Pick the start and the end of the filename.
1021                         relhome = OnlyFilename(path);
1022                         string const head = relhome.substr(0, threshold/2 - 3);
1023
1024                         l2 = relhome.length();
1025                         string const tail =
1026                                 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
1027                         relhome = head + "..." + tail;
1028                 }
1029         }
1030         return prefix + relhome;
1031 }
1032
1033
1034 bool LyXReadLink(string const & File, string & Link)
1035 {
1036         char LinkBuffer[512];
1037         // Should be PATH_MAX but that needs autconf support
1038         int const nRead = ::readlink(File.c_str(),
1039                                      LinkBuffer, sizeof(LinkBuffer) - 1);
1040         if (nRead <= 0)
1041                 return false;
1042         LinkBuffer[nRead] = '\0'; // terminator
1043         Link = LinkBuffer;
1044         return true;
1045 }
1046
1047
1048 typedef pair<int, string> cmdret;
1049 static
1050 cmdret const do_popen(string const & cmd)
1051 {
1052         // One question is if we should use popen or
1053         // create our own popen based on fork, exec, pipe
1054         // of course the best would be to have a
1055         // pstream (process stream), with the
1056         // variants ipstream, opstream
1057         FILE * inf = ::popen(cmd.c_str(), "r");
1058         string ret;
1059         int c = fgetc(inf);
1060         while (c != EOF) {
1061                 ret += static_cast<char>(c);
1062                 c = fgetc(inf);
1063         }
1064         int const pret = pclose(inf);
1065         return make_pair(pret, ret);
1066 }
1067
1068
1069 string const
1070 findtexfile(string const & fil, string const & /*format*/)
1071 {
1072         /* There is no problem to extend this function too use other
1073            methods to look for files. It could be setup to look
1074            in environment paths and also if wanted as a last resort
1075            to a recursive find. One of the easier extensions would
1076            perhaps be to use the LyX file lookup methods. But! I am
1077            going to implement this until I see some demand for it.
1078            Lgb
1079         */
1080         
1081         // If the file can be found directly, we just return a
1082         // absolute path version of it. 
1083         if (FileInfo(fil).exist())
1084                 return MakeAbsPath(fil);
1085
1086         // No we try to find it using kpsewhich.
1087         // It seems from the kpsewhich manual page that it is safe to use
1088         // kpsewhich without --format: "When the --format option is not
1089         // given, the search path used when looking for a file is inferred
1090         // from the name given, by looking for a known extension. If no
1091         // known extension is found, the search path for TeX source files
1092         // is used."
1093         // However, we want to take advantage of the format sine almost all
1094         // the different formats has environment variables that can be used
1095         // to controll which paths to search. f.ex. bib looks in
1096         // BIBINPUTS and TEXBIB. Small list follows:
1097         // bib - BIBINPUTS, TEXBIB
1098         // bst - BSTINPUTS
1099         // graphic/figure - TEXPICTS, TEXINPUTS
1100         // ist - TEXINDEXSTYLE, INDEXSTYLE
1101         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1102         // tex - TEXINPUTS
1103         // tfm - TFMFONTS, TEXFONTS
1104         // This means that to use kpsewhich in the best possible way we
1105         // should help it by setting additional path in the approp. envir.var.
1106         string const kpsecmd = "kpsewhich " + fil;
1107
1108         cmdret const c = do_popen(kpsecmd);
1109         
1110         lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1111                              << "kpse result = `" << strip(c.second, '\n') 
1112                              << "'" << endl;
1113         return c.first != -1 ? strip(c.second, '\n') : string();
1114 }
1115
1116
1117 void removeAutosaveFile(string const & filename)
1118 {
1119         string a = OnlyPath(filename);
1120         a += '#';
1121         a += OnlyFilename(filename);
1122         a += '#';
1123         FileInfo const fileinfo(a);
1124         if (fileinfo.exist()) {
1125                 if (lyx::unlink(a) != 0) {
1126                         WriteFSAlert(_("Could not delete auto-save file!"), a);
1127                 }
1128         }
1129 }