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