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