]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
Hotfix to move cursor to the right when pressing M-m g x in text mode.
[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 <cstdlib>
31 #include <cstdio>
32 #include <fcntl.h>
33 #include <cerrno>
34 #include "debug.h"
35 #include "support/lstrings.h"
36 #include "support/systemcall.h"
37
38 #include "filetools.h"
39 #include "LSubstring.h"
40 #include "frontends/Alert.h"
41 #include "FileInfo.h"
42 #include "support/path.h"        // I know it's OS/2 specific (SMiyata)
43 #include "gettext.h"
44 #include "lyxlib.h"
45 #include "os.h"
46
47 // Which part of this is still necessary? (JMarc).
48 #if HAVE_DIRENT_H
49 # include <dirent.h>
50 # define NAMLEN(dirent) strlen((dirent)->d_name)
51 #else
52 # define dirent direct
53 # define NAMLEN(dirent) (dirent)->d_namlen
54 # if HAVE_SYS_NDIR_H
55 #  include <sys/ndir.h>
56 # endif
57 # if HAVE_SYS_DIR_H
58 #  include <sys/dir.h>
59 # endif
60 # if HAVE_NDIR_H
61 #  include <ndir.h>
62 # endif
63 #endif
64
65 using std::make_pair;
66 using std::pair;
67 using std::endl;
68 using std::ifstream;
69 using std::vector;
70
71 extern string system_lyxdir;
72 extern string build_lyxdir;
73 extern string user_lyxdir;
74 extern string system_tempdir;
75 extern string system_packageList;
76
77
78 bool IsLyXFilename(string const & filename)
79 {
80         return suffixIs(lowercase(filename), ".lyx");
81 }
82
83
84 bool IsSGMLFilename(string const & filename)
85 {
86         return suffixIs(lowercase(filename), ".sgml");
87 }
88
89
90 // Substitutes spaces with underscores in filename (and path)
91 string const MakeLatexName(string const & file)
92 {
93         string name = OnlyFilename(file);
94         string const path = OnlyPath(file);
95
96         for (string::size_type i = 0; i < name.length(); ++i) {
97                 name[i] &= 0x7f; // set 8th bit to 0
98         };
99
100         // ok so we scan through the string twice, but who cares.
101         string const keep("abcdefghijklmnopqrstuvwxyz"
102                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
103                 "@!\"'()*+,-./0123456789:;<=>?[]`|");
104
105         string::size_type pos = 0;
106         while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
107                 name[pos++] = '_';
108         }
109         return AddName(path, name);
110 }
111
112
113 // Substitutes spaces with underscores in filename (and path)
114 string const QuoteName(string const & name)
115 {
116         return (os::shell() == os::UNIX) ?
117                 "\'" + name + "\'":
118                 "\"" + name + "\"";
119 }
120
121
122 // Is a file readable ?
123 bool IsFileReadable (string const & path)
124 {
125         FileInfo file(path);
126         if (file.isOK() && file.isRegular() && file.readable())
127                 return true;
128         else
129                 return false;
130 }
131
132
133 // Is a file read_only?
134 // return 1 read-write
135 //        0 read_only
136 //       -1 error (doesn't exist, no access, anything else)
137 int IsFileWriteable (string const & path)
138 {
139         FileInfo fi(path);
140
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 true: dir writeable
150 //        false: not writeable
151 bool IsDirWriteable (string const & path)
152 {
153         lyxerr[Debug::FILES] << "IsDirWriteable: " << path << endl;
154
155         string const tmpfl(lyx::tempName(path, "lyxwritetest"));
156
157         if (tmpfl.empty())
158                 return false;
159
160         lyx::unlink(tmpfl);
161         return true;
162 }
163
164
165 // Uses a string of paths separated by ";"s to find a file to open.
166 // Can't cope with pathnames with a ';' in them. Returns full path to file.
167 // If path entry begins with $$LyX/, use system_lyxdir
168 // If path entry begins with $$User/, use user_lyxdir
169 // Example: "$$User/doc;$$LyX/doc"
170 string const FileOpenSearch (string const & path, string const & name,
171                              string const & ext)
172 {
173         string real_file, path_element;
174         bool notfound = true;
175         string tmppath = split(path, path_element, ';');
176
177         while (notfound && !path_element.empty()) {
178                 path_element = os::slashify_path(path_element);
179                 if (!suffixIs(path_element, '/'))
180                         path_element+= '/';
181                 path_element = subst(path_element, "$$LyX", system_lyxdir);
182                 path_element = subst(path_element, "$$User", user_lyxdir);
183
184                 real_file = FileSearch(path_element, name, ext);
185
186                 if (real_file.empty()) {
187                         do {
188                                 tmppath = split(tmppath, path_element, ';');
189                         } while (!tmppath.empty() && path_element.empty());
190                 } else {
191                         notfound = false;
192                 }
193         }
194 #ifdef __EMX__
195         if (ext.empty() && notfound) {
196                 real_file = FileOpenSearch(path, name, "exe");
197                 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
198         }
199 #endif
200         return real_file;
201 }
202
203
204 /// Returns a vector of all files in directory dir having extension ext.
205 vector<string> const DirList(string const & dir, string const & ext)
206 {
207         // This is a non-error checking C/system implementation
208         string extension(ext);
209         if (!extension.empty() && extension[0] != '.')
210                 extension.insert(0, ".");
211         vector<string> dirlist;
212         DIR * dirp = ::opendir(dir.c_str());
213         if (!dirp) {
214                 lyxerr[Debug::FILES]
215                         << "Directory \"" << dir
216                         << "\" does not exist to DirList." << endl;
217                 return dirlist;
218         }
219
220         dirent * dire;
221         while ((dire = ::readdir(dirp))) {
222                 string const fil = dire->d_name;
223                 if (suffixIs(fil, extension)) {
224                         dirlist.push_back(fil);
225                 }
226         }
227         ::closedir(dirp);
228         return dirlist;
229         /* I would have prefered to take a vector<string>& as parameter so
230            that we could avoid the copy of the vector when returning.
231            Then we would use:
232            dirlist.swap(argvec);
233            to avoid the copy. (Lgb)
234         */
235         /* A C++ implementaion will look like this:
236            string extension(ext);
237            if (extension[0] != '.') extension.insert(0, ".");
238            vector<string> dirlist;
239            directory_iterator dit("dir");
240            while (dit != directory_iterator()) {
241                    string fil = dit->filename;
242                    if (prefixIs(fil, extension)) {
243                            dirlist.push_back(fil);
244                    }
245                    ++dit;
246            }
247            dirlist.swap(argvec);
248            return;
249         */
250 }
251
252
253 // Returns the real name of file name in directory path, with optional
254 // extension ext.
255 string const FileSearch(string const & path, string const & name,
256                         string const & ext)
257 {
258         // if `name' is an absolute path, we ignore the setting of `path'
259         // Expand Environmentvariables in 'name'
260         string const tmpname = ReplaceEnvironmentPath(name);
261         string fullname = MakeAbsPath(tmpname, path);
262         // search first without extension, then with it.
263         if (IsFileReadable(fullname))
264                 return fullname;
265         else if (ext.empty())
266                 return string();
267         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
268                 fullname += '.';
269                 fullname += ext;
270                 if (IsFileReadable(fullname))
271                         return fullname;
272                 else
273                         return string();
274         }
275 }
276
277
278 // Search the file name.ext in the subdirectory dir of
279 //   1) user_lyxdir
280 //   2) build_lyxdir (if not empty)
281 //   3) system_lyxdir
282 string const LibFileSearch(string const & dir, string const & name,
283                            string const & ext)
284 {
285         string fullname = FileSearch(AddPath(user_lyxdir, dir), name, ext);
286         if (!fullname.empty())
287                 return fullname;
288
289         if (!build_lyxdir.empty())
290                 fullname = FileSearch(AddPath(build_lyxdir, dir), name, ext);
291         if (!fullname.empty())
292                 return fullname;
293
294         return FileSearch(AddPath(system_lyxdir, dir), name, ext);
295 }
296
297
298 string const
299 i18nLibFileSearch(string const & dir, string const & name,
300                   string const & ext)
301 {
302         // this comment is from intl/dcigettext.c. We try to mimick this
303         // behaviour here.
304         /* The highest priority value is the `LANGUAGE' environment
305            variable. But we don't use the value if the currently
306            selected locale is the C locale. This is a GNU extension. */
307
308         string const lc_all = GetEnv("LC_ALL");
309         string lang = GetEnv("LANGUAGE");
310         if (lang.empty() || lc_all == "C") {
311                 lang = lc_all;
312                 if (lang.empty()) {
313                         lang = GetEnv("LANG");
314                 }
315         }
316
317         lang = token(lang, '_', 0);
318
319         if (lang.empty() || lang == "C")
320                 return LibFileSearch(dir, name, ext);
321         else {
322                 string const tmp = LibFileSearch(dir, lang + '_' + name,
323                                                  ext);
324                 if (!tmp.empty())
325                         return tmp;
326                 else
327                         return LibFileSearch(dir, name, ext);
328         }
329 }
330
331
332 string const LibScriptSearch(string const & command)
333 {
334         string script;
335         string args = command;
336         split(args, script, ' ');
337         script = LibFileSearch("scripts", script);
338         if (script.empty())
339                 return command;
340         else if (args.empty())
341                 return script;
342         else
343                 return script + ' ' + args;
344 }
345
346
347 string const GetEnv(string const & envname)
348 {
349         // f.ex. what about error checking?
350         char const * const ch = getenv(envname.c_str());
351         string const envstr = !ch ? "" : ch;
352         return envstr;
353 }
354
355
356 string const GetEnvPath(string const & name)
357 {
358 #ifndef __EMX__
359         string const pathlist = subst(GetEnv(name), ':', ';');
360 #else
361         string const pathlist = os::slashify_path(GetEnv(name));
362 #endif
363         return strip(pathlist, ';');
364 }
365
366
367 bool PutEnv(string const & envstr)
368 {
369         // CHECK Look at and fix this.
370         // f.ex. what about error checking?
371
372 #if HAVE_PUTENV
373         // this leaks, but what can we do about it?
374         //   Is doing a getenv() and a free() of the older value
375         //   a good idea? (JMarc)
376         // Actually we don't have to leak...calling putenv like this
377         // should be enough: ... and this is obviously not enough if putenv
378         // does not make a copy of the string. It is also not very wise to
379         // put a string on the free store. If we have to leak we should do it
380         // like this:
381         char * leaker = new char[envstr.length() + 1];
382         envstr.copy(leaker, envstr.length());
383         leaker[envstr.length()] = '\0';
384         int const retval = lyx::putenv(leaker);
385
386         // If putenv does not make a copy of the char const * this
387         // is very dangerous. OTOH if it does take a copy this is the
388         // best solution.
389         // The  only implementation of putenv that I have seen does not
390         // allocate memory. _And_ after testing the putenv in glibc it
391         // seems that we need to make a copy of the string contents.
392         // I will enable the above.
393         //int retval = lyx::putenv(envstr.c_str());
394 #else
395 #ifdef HAVE_SETENV
396         string varname;
397         string const str = envstr.split(varname,'=');
398         int const retval = ::setenv(varname.c_str(), str.c_str(), true);
399 #else
400         // No environment setting function. Can this happen?
401         int const retval = 1; //return an error condition.
402 #endif
403 #endif
404         return retval == 0;
405 }
406
407
408 bool PutEnvPath(string const & envstr)
409 {
410         return PutEnv(envstr);
411 }
412
413
414 namespace {
415
416 int DeleteAllFilesInDir (string const & path)
417 {
418         // I have decided that we will be using parts from the boost
419         // library. Check out http://www.boost.org/
420         // For directory access we will then use the directory_iterator.
421         // Then the code will be something like:
422         // directory_iterator dit(path);
423         // directory_iterator dend;
424         // if (dit == dend) {
425         //         Alert::err_alert(_("Error! Cannot open directory:"), path);
426         //         return -1;
427         // }
428         // for (; dit != dend; ++dit) {
429         //         string filename(*dit);
430         //         if (filename == "." || filename == "..")
431         //                 continue;
432         //         string unlinkpath(AddName(path, filename));
433         //         if (lyx::unlink(unlinkpath))
434         //                 Alert::err_alert(_("Error! Could not remove file:"),
435         //                              unlinkpath);
436         // }
437         // return 0;
438         DIR * dir = ::opendir(path.c_str());
439         if (!dir) {
440                 Alert::err_alert (_("Error! Cannot open directory:"), path);
441                 return -1;
442         }
443         struct dirent * de;
444         int return_value = 0;
445         while ((de = readdir(dir))) {
446                 string const temp = de->d_name;
447                 if (temp == "." || temp == "..")
448                         continue;
449                 string const unlinkpath = AddName (path, temp);
450
451                 lyxerr[Debug::FILES] << "Deleting file: " << unlinkpath
452                                      << endl;
453
454                 bool deleted = true;
455                 FileInfo fi(unlinkpath);
456                 if (fi.isOK() && fi.isDir())
457                         deleted = (DeleteAllFilesInDir(unlinkpath) == 0);
458                 deleted &= (lyx::unlink(unlinkpath) == 0);
459                 if (!deleted) {
460                         Alert::err_alert(_("Error! Could not remove file:"),
461                                 unlinkpath);
462                         return_value = -1;
463                 }
464         }
465         closedir(dir);
466         return return_value;
467 }
468
469
470 string const CreateTmpDir(string const & tempdir, string const & mask)
471 {
472         lyxerr[Debug::FILES]
473                 << "CreateTmpDir: tempdir=`" << tempdir << "'\n"
474                 << "CreateTmpDir:    mask=`" << mask << "'" << endl;
475
476         string const tmpfl(lyx::tempName(tempdir, mask));
477         // lyx::tempName actually creates a file to make sure that it
478         // stays unique. So we have to delete it before we can create
479         // a dir with the same name. Note also that we are not thread
480         // safe because of the gap between unlink and mkdir. (Lgb)
481         lyx::unlink(tmpfl.c_str());
482
483         if (tmpfl.empty() || lyx::mkdir(tmpfl, 0700)) {
484                 Alert::err_alert(_("Error! Couldn't create temporary directory:"),
485                              tempdir);
486                 return string();
487         }
488         return MakeAbsPath(tmpfl);
489 }
490
491
492 int DestroyTmpDir(string const & tmpdir, bool Allfiles)
493 {
494 #ifdef __EMX__
495         Path p(user_lyxdir);
496 #endif
497         if (Allfiles && DeleteAllFilesInDir(tmpdir)) {
498                 return -1;
499         }
500         if (lyx::rmdir(tmpdir)) {
501                 Alert::err_alert(_("Error! Couldn't delete temporary directory:"),
502                              tmpdir);
503                 return -1;
504         }
505         return 0;
506 }
507
508 } // namespace anon
509
510
511 string const CreateBufferTmpDir(string const & pathfor)
512 {
513         static int count;
514         static string const tmpdir(pathfor.empty() ? os::getTmpDir() : pathfor);
515         // We are in our own directory.  Why bother to mangle name?
516         // In fact I wrote this code to circumvent a problematic behaviour (bug?)
517         // of EMX mkstemp().
518         string const tmpfl = tmpdir + "/lyx_tmpbuf" + tostr(count++);
519         if (lyx::mkdir(tmpfl, 0777)) {
520                 Alert::err_alert(_("Error! Couldn't create temporary directory:"),
521                              tmpdir);
522                 return string();
523         }
524         return tmpfl;
525 }
526
527
528 int DestroyBufferTmpDir(string const & tmpdir)
529 {
530         return DestroyTmpDir(tmpdir, true);
531 }
532
533
534 string const CreateLyXTmpDir(string const & deflt)
535 {
536         if ((!deflt.empty()) && (deflt  != "/tmp")) {
537                 if (lyx::mkdir(deflt, 0777)) {
538 #ifdef __EMX__
539                 Path p(user_lyxdir);
540 #endif
541                         return CreateTmpDir(deflt, "lyx_tmpdir");
542                 } else
543                         return deflt;
544         } else {
545 #ifdef __EMX__
546                 Path p(user_lyxdir);
547 #endif
548                 return CreateTmpDir("/tmp", "lyx_tmpdir");
549         }
550 }
551
552
553 int DestroyLyXTmpDir(string const & tmpdir)
554 {
555         return DestroyTmpDir (tmpdir, false); // Why false?
556 }
557
558
559 // Creates directory. Returns true if succesfull
560 bool createDirectory(string const & path, int permission)
561 {
562         string temp(strip(os::slashify_path(path), '/'));
563
564         if (temp.empty()) {
565                 Alert::alert(_("Internal error!"),
566                            _("Call to createDirectory with invalid name"));
567                 return false;
568         }
569
570         if (lyx::mkdir(temp, permission)) {
571                 Alert::err_alert (_("Error! Couldn't create directory:"), temp);
572                 return false;
573         }
574         return true;
575 }
576
577
578 // Strip filename from path name
579 string const OnlyPath(string const & Filename)
580 {
581         // If empty filename, return empty
582         if (Filename.empty()) return Filename;
583
584         // Find last / or start of filename
585         string::size_type j = Filename.rfind('/');
586         if (j == string::npos)
587                 return "./";
588         return Filename.substr(0, j + 1);
589 }
590
591
592 // Convert relative path into absolute path based on a basepath.
593 // If relpath is absolute, just use that.
594 // If basepath is empty, use CWD as base.
595 string const MakeAbsPath(string const & RelPath, string const & BasePath)
596 {
597         // checks for already absolute path
598         if (os::is_absolute_path(RelPath))
599                 return RelPath;
600
601         // Copies given paths
602         string TempRel(os::slashify_path(RelPath));
603         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
604         TempRel = subst(TempRel, "//", "/");
605
606         string TempBase;
607
608         if (os::is_absolute_path(BasePath))
609                 TempBase = BasePath;
610         else
611                 TempBase = AddPath(lyx::getcwd(), BasePath);
612
613         // Handle /./ at the end of the path
614         while (suffixIs(TempBase, "/./"))
615                 TempBase.erase(TempBase.length() - 2);
616
617         // processes relative path
618         string RTemp(TempRel);
619         string Temp;
620
621         while (!RTemp.empty()) {
622                 // Split by next /
623                 RTemp = split(RTemp, Temp, '/');
624
625                 if (Temp == ".") continue;
626                 if (Temp == "..") {
627                         // Remove one level of TempBase
628                         string::difference_type i = TempBase.length() - 2;
629 #ifndef __EMX__
630                         if (i < 0) i = 0;
631                         while (i > 0 && TempBase[i] != '/') --i;
632                         if (i > 0)
633 #else
634                         if (i < 2) i = 2;
635                         while (i > 2 && TempBase[i] != '/') --i;
636                         if (i > 2)
637 #endif
638                                 TempBase.erase(i, string::npos);
639                         else
640                                 TempBase += '/';
641                 } else if (Temp.empty() && !RTemp.empty()) {
642                                 TempBase = os::current_root() + RTemp;
643                                 RTemp.erase();
644                 } else {
645                         // Add this piece to TempBase
646                         if (!suffixIs(TempBase, '/'))
647                                 TempBase += '/';
648                         TempBase += Temp;
649                 }
650         }
651
652         // returns absolute path
653         return os::slashify_path(TempBase);
654 }
655
656
657 // Correctly append filename to the pathname.
658 // If pathname is '.', then don't use pathname.
659 // Chops any path of filename.
660 string const AddName(string const & path, string const & fname)
661 {
662         // Get basename
663         string const basename(OnlyFilename(fname));
664
665         string buf;
666
667         if (path != "." && path != "./" && !path.empty()) {
668                 buf = os::slashify_path(path);
669                 if (!suffixIs(path, '/'))
670                         buf += '/';
671         }
672
673         return buf + basename;
674 }
675
676
677 // Strips path from filename
678 string const OnlyFilename(string const & fname)
679 {
680         if (fname.empty())
681                 return fname;
682
683         string::size_type j = fname.rfind('/');
684         if (j == string::npos) // no '/' in fname
685                 return fname;
686
687         // Strip to basename
688         return fname.substr(j + 1);
689 }
690
691
692 /// Returns true is path is absolute
693 bool AbsolutePath(string const & path)
694 {
695         return os::is_absolute_path(path);
696 }
697
698
699
700 // Create absolute path. If impossible, don't do anything
701 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
702 string const ExpandPath(string const & path)
703 {
704         // checks for already absolute path
705         string RTemp(ReplaceEnvironmentPath(path));
706         if (os::is_absolute_path(RTemp))
707                 return RTemp;
708
709         string Temp;
710         string const copy(RTemp);
711
712         // Split by next /
713         RTemp = split(RTemp, Temp, '/');
714
715         if (Temp == ".") {
716                 return lyx::getcwd() /*GetCWD()*/ + '/' + RTemp;
717         }
718         if (Temp == "~") {
719                 return GetEnvPath("HOME") + '/' + RTemp;
720         }
721         if (Temp == "..") {
722                 return MakeAbsPath(copy);
723         }
724         // Don't know how to handle this
725         return copy;
726 }
727
728
729 // Normalize a path
730 // Constracts path/../path
731 // Can't handle "../../" or "/../" (Asger)
732 string const NormalizePath(string const & path)
733 {
734         string TempBase;
735         string RTemp;
736         string Temp;
737
738         if (os::is_absolute_path(path))
739                 RTemp = path;
740         else
741                 // Make implicit current directory explicit
742                 RTemp = "./" +path;
743
744         while (!RTemp.empty()) {
745                 // Split by next /
746                 RTemp = split(RTemp, Temp, '/');
747
748                 if (Temp == ".") {
749                         TempBase = "./";
750                 } else if (Temp == "..") {
751                         // Remove one level of TempBase
752                         string::difference_type i = TempBase.length() - 2;
753                         while (i > 0 && TempBase[i] != '/')
754                                 --i;
755                         if (i >= 0 && TempBase[i] == '/')
756                                 TempBase.erase(i + 1, string::npos);
757                         else
758                                 TempBase = "../";
759                 } else {
760                         TempBase += Temp + '/';
761                 }
762         }
763
764         // returns absolute path
765         return TempBase;
766 }
767
768
769 string const GetFileContents(string const & fname)
770 {
771         FileInfo finfo(fname);
772         if (finfo.exist()) {
773                 ifstream ifs(fname.c_str());
774                 ostringstream ofs;
775                 if (ifs && ofs) {
776                         ofs << ifs.rdbuf();
777                         ifs.close();
778                         return ofs.str().c_str();
779                 }
780         }
781         lyxerr << "LyX was not able to read file '" << fname << "'" << endl;
782         return string();
783 }
784
785
786 //
787 // Search ${...} as Variable-Name inside the string and replace it with
788 // the denoted environmentvariable
789 // Allow Variables according to
790 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
791 //
792
793 string const ReplaceEnvironmentPath(string const & path)
794 {
795 //
796 // CompareChar: Environmentvariables starts with this character
797 // PathChar:    Next path component start with this character
798 // while CompareChar found do:
799 //       Split String with PathChar
800 //       Search Environmentvariable
801 //       if found: Replace Strings
802 //
803         char const CompareChar = '$';
804         char const FirstChar = '{';
805         char const EndChar = '}';
806         char const UnderscoreChar = '_';
807         string EndString; EndString += EndChar;
808         string FirstString; FirstString += FirstChar;
809         string CompareString; CompareString += CompareChar;
810         string const RegExp("*}*"); // Exist EndChar inside a String?
811
812 // first: Search for a '$' - Sign.
813         //string copy(path);
814         string result1; //(copy);    // for split-calls
815         string result0 = split(path, result1, CompareChar);
816         while (!result0.empty()) {
817                 string copy1(result0); // contains String after $
818
819                 // Check, if there is an EndChar inside original String.
820
821                 if (!regexMatch(copy1, RegExp)) {
822                         // No EndChar inside. So we are finished
823                         result1 += CompareString + result0;
824                         result0.erase();
825                         continue;
826                 }
827
828                 string res1;
829                 string res0 = split(copy1, res1, EndChar);
830                 // Now res1 holds the environmentvariable
831                 // First, check, if Contents is ok.
832                 if (res1.empty()) { // No environmentvariable. Continue Loop.
833                         result1 += CompareString + FirstString;
834                         result0  = res0;
835                         continue;
836                 }
837                 // check contents of res1
838                 char const * res1_contents = res1.c_str();
839                 if (*res1_contents != FirstChar) {
840                         // Again No Environmentvariable
841                         result1 += CompareString;
842                         result0 = res0;
843                 }
844
845                 // Check for variable names
846                 // Situation ${} is detected as "No Environmentvariable"
847                 char const * cp1 = res1_contents + 1;
848                 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
849                 ++cp1;
850                 while (*cp1 && result) {
851                         result = isalnum(*cp1) ||
852                                 (*cp1 == UnderscoreChar);
853                         ++cp1;
854                 }
855
856                 if (!result) {
857                         // no correct variable name
858                         result1 += CompareString + res1 + EndString;
859                         result0  = split(res0, res1, CompareChar);
860                         result1 += res1;
861                         continue;
862                 }
863
864                 string env(GetEnv(res1_contents + 1));
865                 if (!env.empty()) {
866                         // Congratulations. Environmentvariable found
867                         result1 += env;
868                 } else {
869                         result1 += CompareString + res1 + EndString;
870                 }
871                 // Next $-Sign?
872                 result0  = split(res0, res1, CompareChar);
873                 result1 += res1;
874         }
875         return result1;
876 }  // ReplaceEnvironmentPath
877
878
879 // Make relative path out of two absolute paths
880 string const MakeRelPath(string const & abspath, string const & basepath)
881 // Makes relative path out of absolute path. If it is deeper than basepath,
882 // it's easy. If basepath and abspath share something (they are all deeper
883 // than some directory), it'll be rendered using ..'s. If they are completely
884 // different, then the absolute path will be used as relative path.
885 {
886         string::size_type const abslen = abspath.length();
887         string::size_type const baselen = basepath.length();
888
889         string::size_type i = os::common_path(abspath, basepath);
890
891         if (i == 0) {
892                 // actually no match - cannot make it relative
893                 return abspath;
894         }
895
896         // Count how many dirs there are in basepath above match
897         // and append as many '..''s into relpath
898         string buf;
899         string::size_type j = i;
900         while (j < baselen) {
901                 if (basepath[j] == '/') {
902                         if (j + 1 == baselen)
903                                 break;
904                         buf += "../";
905                 }
906                 ++j;
907         }
908
909         // Append relative stuff from common directory to abspath
910         if (abspath[i] == '/')
911                 ++i;
912         for (; i < abslen; ++i)
913                 buf += abspath[i];
914         // Remove trailing /
915         if (suffixIs(buf, '/'))
916                 buf.erase(buf.length() - 1);
917         // Substitute empty with .
918         if (buf.empty())
919                 buf = '.';
920         return buf;
921 }
922
923
924 // Append sub-directory(ies) to a path in an intelligent way
925 string const AddPath(string const & path, string const & path_2)
926 {
927         string buf;
928         string const path2 = os::slashify_path(path_2);
929
930         if (!path.empty() && path != "." && path != "./") {
931                 buf = os::slashify_path(path);
932                 if (path[path.length() - 1] != '/')
933                         buf += '/';
934         }
935
936         if (!path2.empty()) {
937                 string::size_type const p2start = path2.find_first_not_of('/');
938                 string::size_type const p2end = path2.find_last_not_of('/');
939                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
940                 buf += tmp + '/';
941         }
942         return buf;
943 }
944
945
946 /*
947  Change extension of oldname to extension.
948  Strips path off if no_path == true.
949  If no extension on oldname, just appends.
950  */
951 string const ChangeExtension(string const & oldname, string const & extension)
952 {
953         string::size_type const last_slash = oldname.rfind('/');
954         string::size_type last_dot = oldname.rfind('.');
955         if (last_dot < last_slash && last_slash != string::npos)
956                 last_dot = string::npos;
957
958         string ext;
959         // Make sure the extension starts with a dot
960         if (!extension.empty() && extension[0] != '.')
961                 ext= "." + extension;
962         else
963                 ext = extension;
964
965         return os::slashify_path(oldname.substr(0, last_dot) + ext);
966 }
967
968
969 /// Return the extension of the file (not including the .)
970 string const GetExtension(string const & name)
971 {
972         string::size_type const last_slash = name.rfind('/');
973         string::size_type const last_dot = name.rfind('.');
974         if (last_dot != string::npos &&
975             (last_slash == string::npos || last_dot > last_slash))
976                 return name.substr(last_dot + 1,
977                                    name.length() - (last_dot + 1));
978         else
979                 return string();
980 }
981
982 // the different filetypes and what they contain in one of the first lines
983 // (dots are any characters).           (Herbert 20020131)
984 // AGR  Grace...
985 // BMP  BM...
986 // EPS  %!PS-Adobe-3.0 EPSF...
987 // FITS ...BITPIX...
988 // GIF  GIF...
989 // JPG  JFIF
990 // PDF  %PDF-...
991 // PNG  .PNG...
992 // PBM  P1... or P4     (B/W)
993 // PGM  P2... or P5     (Grayscale)
994 // PPM  P3... or P6     (color)
995 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
996 // SGI  \001\332...     (decimal 474)
997 // TGIF %TGIF...
998 // TIFF II... or MM...
999 // XBM  ..._bits[]...
1000 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
1001 //      ...static char *...
1002 // XWD  \000\000\000\151        (0x00006900) decimal 105
1003 //
1004 // GZIP \037\213\010\010...     http://www.ietf.org/rfc/rfc1952.txt
1005 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
1006 // Z    \037\177                UNIX compress
1007
1008 /// return the "extension" which belongs to the contents.
1009 /// for no knowing contents return the extension. Without
1010 /// an extension and unknown contents we return "user"
1011 string const getExtFromContents(string const & filename)
1012 {
1013         // paranoia check
1014         if (filename.empty() || !IsFileReadable(filename))
1015                 return string();
1016
1017         ifstream ifs(filename.c_str());
1018         if (!ifs)
1019                 // Couldn't open file...
1020                 return string();
1021
1022         // gnuzip
1023         string const gzipStamp = "\037\213\010\010";
1024
1025         // PKZIP
1026         string const zipStamp = "PK";
1027
1028         // compress
1029         string const compressStamp = "\037\177";
1030
1031         // Maximum strings to read
1032         int const max_count = 50;
1033         int count = 0;
1034
1035         string str, format;
1036         bool firstLine = true;
1037         while ((count++ < max_count) && format.empty()) {
1038                 if (ifs.eof()) {
1039                         lyxerr[Debug::GRAPHICS]
1040                                 << "filetools(getExtFromContents)\n"
1041                                 << "\tFile type not recognised before EOF!"
1042                                 << endl;
1043                         break;
1044                 }
1045
1046                 std::getline(ifs, str);
1047                 lyxerr[Debug::GRAPHICS] << "Scanstring: " << str << endl;
1048
1049                 string const stamp = str.substr(0,2);
1050                 if (firstLine && str.size() >= 2) {
1051                         // at first we check for a zipped file, because this
1052                         // information is saved in the first bytes of the file!
1053                         // also some graphic formats which save the information
1054                         // in the first line, too.
1055                         if (prefixIs(str, gzipStamp)) {
1056                                 format =  "gzip";
1057
1058                         } else if (stamp == zipStamp) {
1059                                 format =  "zip";
1060
1061                         } else if (stamp == compressStamp) {
1062                                 format =  "compress";
1063
1064                         // the graphics part
1065                         } else if (stamp == "BM") {
1066                                 format =  "bmp";
1067
1068                         } else if (stamp == "\001\332") {
1069                                 format =  "sgi";
1070
1071                         // PBM family
1072                         // Don't need to use str.at(0), str.at(1) because
1073                         // we already know that str.size() >= 2
1074                         } else if (str[0] == 'P') {
1075                                 switch (str[1]) {
1076                                 case '1':
1077                                 case '4':
1078                                         format =  "pbm";
1079                                     break;
1080                                 case '2':
1081                                 case '5':
1082                                         format =  "pgm";
1083                                     break;
1084                                 case '3':
1085                                 case '6':
1086                                         format =  "ppm";
1087                                 }
1088                                 break;
1089
1090                         } else if ((stamp == "II") || (stamp == "MM")) {
1091                                 format =  "tiff";
1092
1093                         } else if (prefixIs(str,"%TGIF")) {
1094                                 format =  "tgif";
1095
1096                         } else if (prefixIs(str,"GIF")) {
1097                                 format =  "gif";
1098
1099                         } else if (str.size() > 3) {
1100                                 int const c = ((str[0] << 24) & (str[1] << 16) &
1101                                                (str[2] << 8)  & str[3]);
1102                                 if (c == 105) {
1103                                         format =  "xwd";
1104                                 }
1105                         }
1106                         
1107                         firstLine = false;
1108                 }
1109
1110                 if (!format.empty())
1111                     break;
1112                 else if (contains(str,"EPSF"))
1113                         // dummy, if we have wrong file description like
1114                         // %!PS-Adobe-2.0EPSF"
1115                         format =  "eps";
1116
1117                 else if (contains(str,"Grace"))
1118                         format =  "agr";
1119
1120                 else if (contains(str,"JFIF"))
1121                         format =  "jpg";
1122
1123                 else if (contains(str,"%PDF"))
1124                         format =  "pdf";
1125
1126                 else if (contains(str,"PNG"))
1127                         format =  "png";
1128
1129                 else if (contains(str,"%!PS-Adobe")) {
1130                         // eps or ps
1131                         ifs >> str;
1132                         if (contains(str,"EPSF"))
1133                                 format = "eps";
1134                         else
1135                             format = "ps";
1136                 }
1137
1138                 else if (contains(str,"_bits[]"))
1139                         format = "xbm";
1140
1141                 else if (contains(str,"XPM") || contains(str, "static char *"))
1142                         format = "xpm";
1143
1144                 else if (contains(str,"BITPIX"))
1145                         format = "fits";
1146         }
1147
1148         if (!format.empty()) {
1149             lyxerr[Debug::GRAPHICS]
1150                 << "Recognised Fileformat: " << format << endl;
1151             return format;
1152         }
1153
1154         string const ext(GetExtension(filename));
1155         lyxerr[Debug::GRAPHICS]
1156                 << "filetools(getExtFromContents)\n"
1157                 << "\tCouldn't find a known Type!\n";
1158         if (!ext.empty()) {
1159             lyxerr[Debug::GRAPHICS]
1160                 << "\twill take the file extension -> "
1161                 << ext << endl;
1162                 return ext;
1163         } else {
1164             lyxerr[Debug::GRAPHICS]
1165                 << "\twill use ext or a \"user\" defined format" << endl;
1166             return "user";
1167         }
1168 }
1169
1170
1171 /// check for zipped file
1172 bool zippedFile(string const & name)
1173 {
1174         string const type = getExtFromContents(name);
1175         if (contains("gzip zip compress", type) && !type.empty())
1176                 return true;
1177         return false;
1178 }
1179
1180
1181 string const unzipFile(string const & zipped_file)
1182 {
1183         string const file = ChangeExtension(zipped_file, string());
1184         string  const tempfile = lyx::tempName(string(), file);
1185         // Run gunzip
1186         string const command = "gunzip -c " + zipped_file + " > " + tempfile;
1187         Systemcall one;
1188         one.startscript(Systemcall::Wait, command);
1189         // test that command was executed successfully (anon)
1190         // yes, please do. (Lgb)
1191         return tempfile;
1192 }
1193
1194
1195 // Creates a nice compact path for displaying
1196 string const
1197 MakeDisplayPath (string const & path, unsigned int threshold)
1198 {
1199         string::size_type const l1 = path.length();
1200
1201         // First, we try a relative path compared to home
1202         string const home(GetEnvPath("HOME"));
1203         string relhome = MakeRelPath(path, home);
1204
1205         string::size_type l2 = relhome.length();
1206
1207         string prefix;
1208
1209         // If we backup from home or don't have a relative path,
1210         // this try is no good
1211         if (prefixIs(relhome, "../") || os::is_absolute_path(relhome)) {
1212                 // relative path was no good, just use the original path
1213                 relhome = path;
1214                 l2 = l1;
1215         } else {
1216                 prefix = "~/";
1217         }
1218
1219         // Is the path too long?
1220         if (l2 > threshold) {
1221                 // Yes, shortend it
1222                 prefix += ".../";
1223
1224                 string temp;
1225
1226                 while (relhome.length() > threshold)
1227                         relhome = split(relhome, temp, '/');
1228
1229                 // Did we shortend everything away?
1230                 if (relhome.empty()) {
1231                         // Yes, filename in itself is too long.
1232                         // Pick the start and the end of the filename.
1233                         relhome = OnlyFilename(path);
1234                         string const head = relhome.substr(0, threshold/2 - 3);
1235
1236                         l2 = relhome.length();
1237                         string const tail =
1238                                 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
1239                         relhome = head + "..." + tail;
1240                 }
1241         }
1242         return prefix + relhome;
1243 }
1244
1245
1246 bool LyXReadLink(string const & File, string & Link)
1247 {
1248         char LinkBuffer[512];
1249         // Should be PATH_MAX but that needs autconf support
1250         int const nRead = ::readlink(File.c_str(),
1251                                      LinkBuffer, sizeof(LinkBuffer) - 1);
1252         if (nRead <= 0)
1253                 return false;
1254         LinkBuffer[nRead] = '\0'; // terminator
1255         Link = LinkBuffer;
1256         return true;
1257 }
1258
1259
1260 namespace {
1261
1262 typedef pair<int, string> cmdret;
1263
1264 cmdret const do_popen(string const & cmd)
1265 {
1266         // One question is if we should use popen or
1267         // create our own popen based on fork, exec, pipe
1268         // of course the best would be to have a
1269         // pstream (process stream), with the
1270         // variants ipstream, opstream
1271
1272         FILE * inf = ::popen(cmd.c_str(), os::read_mode());
1273
1274         // (Claus Hentschel) Check if popen was succesful ;-)
1275         if (!inf)
1276                 return make_pair(-1, string());
1277
1278         string ret;
1279         int c = fgetc(inf);
1280         while (c != EOF) {
1281                 ret += static_cast<char>(c);
1282                 c = fgetc(inf);
1283         }
1284         int const pret = pclose(inf);
1285         return make_pair(pret, ret);
1286 }
1287
1288 } // namespace anon
1289
1290
1291 string const findtexfile(string const & fil, string const & /*format*/)
1292 {
1293         /* There is no problem to extend this function too use other
1294            methods to look for files. It could be setup to look
1295            in environment paths and also if wanted as a last resort
1296            to a recursive find. One of the easier extensions would
1297            perhaps be to use the LyX file lookup methods. But! I am
1298            going to implement this until I see some demand for it.
1299            Lgb
1300         */
1301
1302         // If the file can be found directly, we just return a
1303         // absolute path version of it.
1304         if (FileInfo(fil).exist())
1305                 return MakeAbsPath(fil);
1306
1307         // No we try to find it using kpsewhich.
1308         // It seems from the kpsewhich manual page that it is safe to use
1309         // kpsewhich without --format: "When the --format option is not
1310         // given, the search path used when looking for a file is inferred
1311         // from the name given, by looking for a known extension. If no
1312         // known extension is found, the search path for TeX source files
1313         // is used."
1314         // However, we want to take advantage of the format sine almost all
1315         // the different formats has environment variables that can be used
1316         // to controll which paths to search. f.ex. bib looks in
1317         // BIBINPUTS and TEXBIB. Small list follows:
1318         // bib - BIBINPUTS, TEXBIB
1319         // bst - BSTINPUTS
1320         // graphic/figure - TEXPICTS, TEXINPUTS
1321         // ist - TEXINDEXSTYLE, INDEXSTYLE
1322         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1323         // tex - TEXINPUTS
1324         // tfm - TFMFONTS, TEXFONTS
1325         // This means that to use kpsewhich in the best possible way we
1326         // should help it by setting additional path in the approp. envir.var.
1327         string const kpsecmd = "kpsewhich " + fil;
1328
1329         cmdret const c = do_popen(kpsecmd);
1330
1331         lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1332                  << "kpse result = `" << strip(c.second, '\n')
1333                  << "'" << endl;
1334         if (c.first != -1)
1335                 return os::internal_path(strip(strip(c.second, '\n'), '\r'));
1336         else
1337                 return string();
1338 }
1339
1340
1341 void removeAutosaveFile(string const & filename)
1342 {
1343         string a = OnlyPath(filename);
1344         a += '#';
1345         a += OnlyFilename(filename);
1346         a += '#';
1347         FileInfo const fileinfo(a);
1348         if (fileinfo.exist()) {
1349                 if (lyx::unlink(a) != 0) {
1350                         Alert::err_alert(_("Could not delete auto-save file!"), a);
1351                 }
1352         }
1353 }
1354
1355
1356 string const readBB_from_PSFile(string const & file)
1357 {
1358         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1359         // It seems that every command in the header has an own line,
1360         // getline() should work for all files.
1361         // On the other hand some plot programs write the bb at the
1362         // end of the file. Than we have in the header:
1363         // %%BoundingBox: (atend)
1364         // In this case we must check the end.
1365         string const file_ = zippedFile(file) ? 
1366                 string(unzipFile(file)) : string(file);
1367         string const format = getExtFromContents(file_);
1368         if (format != "eps" && format != "ps")
1369                 return string();
1370
1371         std::ifstream is(file_.c_str());
1372         while (is) {
1373                 string s;
1374                 std::getline(is,s);
1375                 if (contains(s,"%%BoundingBox:") && !contains(s,"atend")) 
1376                         return (frontStrip(s.substr(14)));
1377         }
1378         return string();
1379 }
1380