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