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