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