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