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