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