]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
Modify LibScriptSearch to replace "$$s/" with an appropriate path.
[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/tostr.h"
23 #include "support/systemcall.h"
24 #include "support/LAssert.h"
25
26 #include "filetools.h"
27 #include "lstrings.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_in)
337 {
338         string const token_scriptpath("$$s/");
339
340         string command = command_in;
341         // Find the starting position of "$$s/"
342         string::size_type const pos1 = command.find(token_scriptpath);
343         if (pos1 == string::npos)
344                 return command;
345         // Find the end of the "$$s/some_script" word within command
346         string::size_type const start_script = pos1 + 4;
347         string::size_type const pos2 = command.find(' ', start_script);
348         string::size_type const size_script = pos2 == string::npos?
349                 (command.size() - start_script) : pos2 - start_script;
350
351         // Does this script file exist?
352         string const script =
353                 LibFileSearch("scripts", command.substr(start_script, size_script));
354
355         if (script.empty()) {
356                 // Replace "$$s/" with ""
357                 command.erase(pos1, 4);
358         } else {
359                 // Replace "$$s/some_script" with "$LYX_SCRIPT_PATH/some_script"
360                 string::size_type const size_replace = size_script + 4;
361                 command.replace(pos1, size_replace, script);
362         }
363
364         return command;
365 }
366
367
368 string const GetEnv(string const & envname)
369 {
370         // f.ex. what about error checking?
371         char const * const ch = getenv(envname.c_str());
372         string const envstr = !ch ? "" : ch;
373         return envstr;
374 }
375
376
377 string const GetEnvPath(string const & name)
378 {
379 #ifndef __EMX__
380         string const pathlist = subst(GetEnv(name), ':', ';');
381 #else
382         string const pathlist = os::slashify_path(GetEnv(name));
383 #endif
384         return rtrim(pathlist, ";");
385 }
386
387
388 namespace {
389
390 int DeleteAllFilesInDir(string const & path)
391 {
392         // I have decided that we will be using parts from the boost
393         // library. Check out http://www.boost.org/
394         // For directory access we will then use the directory_iterator.
395         // Then the code will be something like:
396         // directory_iterator dit(path);
397         // directory_iterator dend;
398         // if (dit == dend) {
399         //         return -1;
400         // }
401         // for (; dit != dend; ++dit) {
402         //         string filename(*dit);
403         //         if (filename == "." || filename == "..")
404         //                 continue;
405         //         string unlinkpath(AddName(path, filename));
406         //         lyx::unlink(unlinkpath);
407         // }
408         // return 0;
409         DIR * dir = ::opendir(path.c_str());
410         if (!dir)
411                 return -1;
412
413         struct dirent * de;
414         int return_value = 0;
415         while ((de = readdir(dir))) {
416                 string const temp = de->d_name;
417                 if (temp == "." || temp == "..")
418                         continue;
419                 string const unlinkpath = AddName (path, temp);
420
421                 lyxerr[Debug::FILES] << "Deleting file: " << unlinkpath
422                                      << endl;
423
424                 bool deleted = true;
425                 FileInfo fi(unlinkpath);
426                 if (fi.isOK() && fi.isDir())
427                         deleted = (DeleteAllFilesInDir(unlinkpath) == 0);
428                 deleted &= (lyx::unlink(unlinkpath) == 0);
429                 if (!deleted)
430                         return_value = -1;
431         }
432         closedir(dir);
433         return return_value;
434 }
435
436
437 string const CreateTmpDir(string const & tempdir, string const & mask)
438 {
439         lyxerr[Debug::FILES]
440                 << "CreateTmpDir: tempdir=`" << tempdir << "'\n"
441                 << "CreateTmpDir:    mask=`" << mask << '\'' << endl;
442
443         string const tmpfl(lyx::tempName(tempdir, mask));
444         // lyx::tempName actually creates a file to make sure that it
445         // stays unique. So we have to delete it before we can create
446         // a dir with the same name. Note also that we are not thread
447         // safe because of the gap between unlink and mkdir. (Lgb)
448         lyx::unlink(tmpfl.c_str());
449
450         if (tmpfl.empty() || lyx::mkdir(tmpfl, 0700))
451                 return string();
452
453         return MakeAbsPath(tmpfl);
454 }
455
456 } // namespace anon
457
458
459 int destroyDir(string const & tmpdir)
460 {
461 #ifdef __EMX__
462         Path p(user_lyxdir);
463 #endif
464         if (DeleteAllFilesInDir(tmpdir))
465                 return -1;
466
467         if (lyx::rmdir(tmpdir))
468                 return -1;
469
470         return 0;
471 }
472
473
474 string const CreateBufferTmpDir(string const & pathfor)
475 {
476         static int count;
477         static string const tmpdir(pathfor.empty() ? os::getTmpDir() : pathfor);
478         // We are in our own directory.  Why bother to mangle name?
479         // In fact I wrote this code to circumvent a problematic behaviour (bug?)
480         // of EMX mkstemp().
481         string const tmpfl = tmpdir + "/lyx_tmpbuf" + tostr(count++);
482         if (lyx::mkdir(tmpfl, 0777)) {
483                 return string();
484         }
485         return tmpfl;
486 }
487
488
489 string const CreateLyXTmpDir(string const & deflt)
490 {
491         if ((!deflt.empty()) && (deflt  != "/tmp")) {
492                 if (lyx::mkdir(deflt, 0777)) {
493 #ifdef __EMX__
494                 Path p(user_lyxdir);
495 #endif
496                         return CreateTmpDir(deflt, "lyx_tmpdir");
497                 } else
498                         return deflt;
499         } else {
500 #ifdef __EMX__
501                 Path p(user_lyxdir);
502 #endif
503                 return CreateTmpDir("/tmp", "lyx_tmpdir");
504         }
505 }
506
507
508 bool createDirectory(string const & path, int permission)
509 {
510         string temp(rtrim(os::slashify_path(path), "/"));
511
512         lyx::Assert(!temp.empty());
513
514         if (lyx::mkdir(temp, permission))
515                 return false;
516
517         return true;
518 }
519
520
521 // Strip filename from path name
522 string const OnlyPath(string const & Filename)
523 {
524         // If empty filename, return empty
525         if (Filename.empty()) return Filename;
526
527         // Find last / or start of filename
528         string::size_type j = Filename.rfind('/');
529         if (j == string::npos)
530                 return "./";
531         return Filename.substr(0, j + 1);
532 }
533
534
535 // Convert relative path into absolute path based on a basepath.
536 // If relpath is absolute, just use that.
537 // If basepath is empty, use CWD as base.
538 string const MakeAbsPath(string const & RelPath, string const & BasePath)
539 {
540         // checks for already absolute path
541         if (os::is_absolute_path(RelPath))
542                 return RelPath;
543
544         // Copies given paths
545         string TempRel(os::slashify_path(RelPath));
546         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
547         TempRel = subst(TempRel, "//", "/");
548
549         string TempBase;
550
551         if (os::is_absolute_path(BasePath))
552                 TempBase = BasePath;
553         else
554                 TempBase = AddPath(lyx::getcwd(), BasePath);
555
556         // Handle /./ at the end of the path
557         while (suffixIs(TempBase, "/./"))
558                 TempBase.erase(TempBase.length() - 2);
559
560         // processes relative path
561         string RTemp(TempRel);
562         string Temp;
563
564         while (!RTemp.empty()) {
565                 // Split by next /
566                 RTemp = split(RTemp, Temp, '/');
567
568                 if (Temp == ".") continue;
569                 if (Temp == "..") {
570                         // Remove one level of TempBase
571                         string::difference_type i = TempBase.length() - 2;
572 #ifndef __EMX__
573                         if (i < 0) i = 0;
574                         while (i > 0 && TempBase[i] != '/') --i;
575                         if (i > 0)
576 #else
577                         if (i < 2) i = 2;
578                         while (i > 2 && TempBase[i] != '/') --i;
579                         if (i > 2)
580 #endif
581                                 TempBase.erase(i, string::npos);
582                         else
583                                 TempBase += '/';
584                 } else if (Temp.empty() && !RTemp.empty()) {
585                                 TempBase = os::current_root() + RTemp;
586                                 RTemp.erase();
587                 } else {
588                         // Add this piece to TempBase
589                         if (!suffixIs(TempBase, '/'))
590                                 TempBase += '/';
591                         TempBase += Temp;
592                 }
593         }
594
595         // returns absolute path
596         return os::slashify_path(TempBase);
597 }
598
599
600 // Correctly append filename to the pathname.
601 // If pathname is '.', then don't use pathname.
602 // Chops any path of filename.
603 string const AddName(string const & path, string const & fname)
604 {
605         // Get basename
606         string const basename(OnlyFilename(fname));
607
608         string buf;
609
610         if (path != "." && path != "./" && !path.empty()) {
611                 buf = os::slashify_path(path);
612                 if (!suffixIs(path, '/'))
613                         buf += '/';
614         }
615
616         return buf + basename;
617 }
618
619
620 // Strips path from filename
621 string const OnlyFilename(string const & fname)
622 {
623         if (fname.empty())
624                 return fname;
625
626         string::size_type j = fname.rfind('/');
627         if (j == string::npos) // no '/' in fname
628                 return fname;
629
630         // Strip to basename
631         return fname.substr(j + 1);
632 }
633
634
635 /// Returns true is path is absolute
636 bool AbsolutePath(string const & path)
637 {
638         return os::is_absolute_path(path);
639 }
640
641
642
643 // Create absolute path. If impossible, don't do anything
644 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
645 string const ExpandPath(string const & path)
646 {
647         // checks for already absolute path
648         string RTemp(ReplaceEnvironmentPath(path));
649         if (os::is_absolute_path(RTemp))
650                 return RTemp;
651
652         string Temp;
653         string const copy(RTemp);
654
655         // Split by next /
656         RTemp = split(RTemp, Temp, '/');
657
658         if (Temp == ".") {
659                 return lyx::getcwd() /*GetCWD()*/ + '/' + RTemp;
660         }
661         if (Temp == "~") {
662                 return GetEnvPath("HOME") + '/' + RTemp;
663         }
664         if (Temp == "..") {
665                 return MakeAbsPath(copy);
666         }
667         // Don't know how to handle this
668         return copy;
669 }
670
671
672 // Normalize a path
673 // Constracts path/../path
674 // Can't handle "../../" or "/../" (Asger)
675 string const NormalizePath(string const & path)
676 {
677         string TempBase;
678         string RTemp;
679         string Temp;
680
681         if (os::is_absolute_path(path))
682                 RTemp = path;
683         else
684                 // Make implicit current directory explicit
685                 RTemp = "./" +path;
686
687         while (!RTemp.empty()) {
688                 // Split by next /
689                 RTemp = split(RTemp, Temp, '/');
690
691                 if (Temp == ".") {
692                         TempBase = "./";
693                 } else if (Temp == "..") {
694                         // Remove one level of TempBase
695                         string::difference_type i = TempBase.length() - 2;
696                         while (i > 0 && TempBase[i] != '/')
697                                 --i;
698                         if (i >= 0 && TempBase[i] == '/')
699                                 TempBase.erase(i + 1, string::npos);
700                         else
701                                 TempBase = "../";
702                 } else {
703                         TempBase += Temp + '/';
704                 }
705         }
706
707         // returns absolute path
708         return TempBase;
709 }
710
711
712 string const GetFileContents(string const & fname)
713 {
714         FileInfo finfo(fname);
715         if (finfo.exist()) {
716                 ifstream ifs(fname.c_str());
717                 ostringstream ofs;
718                 if (ifs && ofs) {
719                         ofs << ifs.rdbuf();
720                         ifs.close();
721                         return STRCONV(ofs.str());
722                 }
723         }
724         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
725         return string();
726 }
727
728
729 //
730 // Search ${...} as Variable-Name inside the string and replace it with
731 // the denoted environmentvariable
732 // Allow Variables according to
733 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
734 //
735
736 string const ReplaceEnvironmentPath(string const & path)
737 {
738         //
739         // CompareChar: Environment variables starts with this character
740         // PathChar:    Next path component start with this character
741         // while CompareChar found do:
742         //       Split String with PathChar
743         //       Search Environmentvariable
744         //       if found: Replace Strings
745         //
746         char const CompareChar = '$';
747         char const FirstChar = '{';
748         char const EndChar = '}';
749         char const UnderscoreChar = '_';
750         string EndString; EndString += EndChar;
751         string FirstString; FirstString += FirstChar;
752         string CompareString; CompareString += CompareChar;
753         string const RegExp("*}*"); // Exist EndChar inside a String?
754
755 // first: Search for a '$' - Sign.
756         //string copy(path);
757         string result1; //(copy);    // for split-calls
758         string result0 = split(path, result1, CompareChar);
759         while (!result0.empty()) {
760                 string copy1(result0); // contains String after $
761
762                 // Check, if there is an EndChar inside original String.
763
764                 if (!regexMatch(copy1, RegExp)) {
765                         // No EndChar inside. So we are finished
766                         result1 += CompareString + result0;
767                         result0.erase();
768                         continue;
769                 }
770
771                 string res1;
772                 string res0 = split(copy1, res1, EndChar);
773                 // Now res1 holds the environmentvariable
774                 // First, check, if Contents is ok.
775                 if (res1.empty()) { // No environmentvariable. Continue Loop.
776                         result1 += CompareString + FirstString;
777                         result0  = res0;
778                         continue;
779                 }
780                 // check contents of res1
781                 char const * res1_contents = res1.c_str();
782                 if (*res1_contents != FirstChar) {
783                         // Again No Environmentvariable
784                         result1 += CompareString;
785                         result0 = res0;
786                 }
787
788                 // Check for variable names
789                 // Situation ${} is detected as "No Environmentvariable"
790                 char const * cp1 = res1_contents + 1;
791                 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
792                 ++cp1;
793                 while (*cp1 && result) {
794                         result = isalnum(*cp1) ||
795                                 (*cp1 == UnderscoreChar);
796                         ++cp1;
797                 }
798
799                 if (!result) {
800                         // no correct variable name
801                         result1 += CompareString + res1 + EndString;
802                         result0  = split(res0, res1, CompareChar);
803                         result1 += res1;
804                         continue;
805                 }
806
807                 string env(GetEnv(res1_contents + 1));
808                 if (!env.empty()) {
809                         // Congratulations. Environmentvariable found
810                         result1 += env;
811                 } else {
812                         result1 += CompareString + res1 + EndString;
813                 }
814                 // Next $-Sign?
815                 result0  = split(res0, res1, CompareChar);
816                 result1 += res1;
817         }
818         return result1;
819 }
820
821
822 // Make relative path out of two absolute paths
823 string const MakeRelPath(string const & abspath, string const & basepath)
824 // Makes relative path out of absolute path. If it is deeper than basepath,
825 // it's easy. If basepath and abspath share something (they are all deeper
826 // than some directory), it'll be rendered using ..'s. If they are completely
827 // different, then the absolute path will be used as relative path.
828 {
829         string::size_type const abslen = abspath.length();
830         string::size_type const baselen = basepath.length();
831
832         string::size_type i = os::common_path(abspath, basepath);
833
834         if (i == 0) {
835                 // actually no match - cannot make it relative
836                 return abspath;
837         }
838
839         // Count how many dirs there are in basepath above match
840         // and append as many '..''s into relpath
841         string buf;
842         string::size_type j = i;
843         while (j < baselen) {
844                 if (basepath[j] == '/') {
845                         if (j + 1 == baselen)
846                                 break;
847                         buf += "../";
848                 }
849                 ++j;
850         }
851
852         // Append relative stuff from common directory to abspath
853         if (abspath[i] == '/')
854                 ++i;
855         for (; i < abslen; ++i)
856                 buf += abspath[i];
857         // Remove trailing /
858         if (suffixIs(buf, '/'))
859                 buf.erase(buf.length() - 1);
860         // Substitute empty with .
861         if (buf.empty())
862                 buf = '.';
863         return buf;
864 }
865
866
867 // Append sub-directory(ies) to a path in an intelligent way
868 string const AddPath(string const & path, string const & path_2)
869 {
870         string buf;
871         string const path2 = os::slashify_path(path_2);
872
873         if (!path.empty() && path != "." && path != "./") {
874                 buf = os::slashify_path(path);
875                 if (path[path.length() - 1] != '/')
876                         buf += '/';
877         }
878
879         if (!path2.empty()) {
880                 string::size_type const p2start = path2.find_first_not_of('/');
881                 string::size_type const p2end = path2.find_last_not_of('/');
882                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
883                 buf += tmp + '/';
884         }
885         return buf;
886 }
887
888
889 /*
890  Change extension of oldname to extension.
891  Strips path off if no_path == true.
892  If no extension on oldname, just appends.
893  */
894 string const ChangeExtension(string const & oldname, string const & extension)
895 {
896         string::size_type const last_slash = oldname.rfind('/');
897         string::size_type last_dot = oldname.rfind('.');
898         if (last_dot < last_slash && last_slash != string::npos)
899                 last_dot = string::npos;
900
901         string ext;
902         // Make sure the extension starts with a dot
903         if (!extension.empty() && extension[0] != '.')
904                 ext= '.' + extension;
905         else
906                 ext = extension;
907
908         return os::slashify_path(oldname.substr(0, last_dot) + ext);
909 }
910
911
912 /// Return the extension of the file (not including the .)
913 string const GetExtension(string const & name)
914 {
915         string::size_type const last_slash = name.rfind('/');
916         string::size_type const last_dot = name.rfind('.');
917         if (last_dot != string::npos &&
918             (last_slash == string::npos || last_dot > last_slash))
919                 return name.substr(last_dot + 1,
920                                    name.length() - (last_dot + 1));
921         else
922                 return string();
923 }
924
925 // the different filetypes and what they contain in one of the first lines
926 // (dots are any characters).           (Herbert 20020131)
927 // AGR  Grace...
928 // BMP  BM...
929 // EPS  %!PS-Adobe-3.0 EPSF...
930 // FIG  #FIG...
931 // FITS ...BITPIX...
932 // GIF  GIF...
933 // JPG  JFIF
934 // PDF  %PDF-...
935 // PNG  .PNG...
936 // PBM  P1... or P4     (B/W)
937 // PGM  P2... or P5     (Grayscale)
938 // PPM  P3... or P6     (color)
939 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
940 // SGI  \001\332...     (decimal 474)
941 // TGIF %TGIF...
942 // TIFF II... or MM...
943 // XBM  ..._bits[]...
944 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
945 //      ...static char *...
946 // XWD  \000\000\000\151        (0x00006900) decimal 105
947 //
948 // GZIP \037\213\010\010...     http://www.ietf.org/rfc/rfc1952.txt
949 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
950 // Z    \037\177                UNIX compress
951
952 /// return the "extension" which belongs to the contents.
953 /// for no knowing contents return the extension. Without
954 /// an extension and unknown contents we return "user"
955 string const getExtFromContents(string const & filename)
956 {
957         // paranoia check
958         if (filename.empty() || !IsFileReadable(filename))
959                 return string();
960
961
962         ifstream ifs(filename.c_str());
963         if (!ifs)
964                 // Couldn't open file...
965                 return string();
966
967         // gnuzip
968         string const gzipStamp = "\037\213\010\010";
969
970         // PKZIP
971         string const zipStamp = "PK";
972
973         // compress
974         string const compressStamp = "\037\177";
975
976         // Maximum strings to read
977         int const max_count = 50;
978         int count = 0;
979
980         string str, format;
981         bool firstLine = true;
982         while ((count++ < max_count) && format.empty()) {
983                 if (ifs.eof()) {
984                         lyxerr[Debug::GRAPHICS]
985                                 << "filetools(getExtFromContents)\n"
986                                 << "\tFile type not recognised before EOF!"
987                                 << endl;
988                         break;
989                 }
990
991                 getline(ifs, str);
992                 string const stamp = str.substr(0,2);
993                 if (firstLine && str.size() >= 2) {
994                         // at first we check for a zipped file, because this
995                         // information is saved in the first bytes of the file!
996                         // also some graphic formats which save the information
997                         // in the first line, too.
998                         if (prefixIs(str, gzipStamp)) {
999                                 format =  "gzip";
1000
1001                         } else if (stamp == zipStamp) {
1002                                 format =  "zip";
1003
1004                         } else if (stamp == compressStamp) {
1005                                 format =  "compress";
1006
1007                         // the graphics part
1008                         } else if (stamp == "BM") {
1009                                 format =  "bmp";
1010
1011                         } else if (stamp == "\001\332") {
1012                                 format =  "sgi";
1013
1014                         // PBM family
1015                         // Don't need to use str.at(0), str.at(1) because
1016                         // we already know that str.size() >= 2
1017                         } else if (str[0] == 'P') {
1018                                 switch (str[1]) {
1019                                 case '1':
1020                                 case '4':
1021                                         format =  "pbm";
1022                                     break;
1023                                 case '2':
1024                                 case '5':
1025                                         format =  "pgm";
1026                                     break;
1027                                 case '3':
1028                                 case '6':
1029                                         format =  "ppm";
1030                                 }
1031                                 break;
1032
1033                         } else if ((stamp == "II") || (stamp == "MM")) {
1034                                 format =  "tiff";
1035
1036                         } else if (prefixIs(str,"%TGIF")) {
1037                                 format =  "tgif";
1038
1039                         } else if (prefixIs(str,"#FIG")) {
1040                                 format =  "fig";
1041
1042                         } else if (prefixIs(str,"GIF")) {
1043                                 format =  "gif";
1044
1045                         } else if (str.size() > 3) {
1046                                 int const c = ((str[0] << 24) & (str[1] << 16) &
1047                                                (str[2] << 8)  & str[3]);
1048                                 if (c == 105) {
1049                                         format =  "xwd";
1050                                 }
1051                         }
1052
1053                         firstLine = false;
1054                 }
1055
1056                 if (!format.empty())
1057                     break;
1058                 else if (contains(str,"EPSF"))
1059                         // dummy, if we have wrong file description like
1060                         // %!PS-Adobe-2.0EPSF"
1061                         format =  "eps";
1062
1063                 else if (contains(str,"Grace"))
1064                         format =  "agr";
1065
1066                 else if (contains(str,"JFIF"))
1067                         format =  "jpg";
1068
1069                 else if (contains(str,"%PDF"))
1070                         format =  "pdf";
1071
1072                 else if (contains(str,"PNG"))
1073                         format =  "png";
1074
1075                 else if (contains(str,"%!PS-Adobe")) {
1076                         // eps or ps
1077                         ifs >> str;
1078                         if (contains(str,"EPSF"))
1079                                 format = "eps";
1080                         else
1081                             format = "ps";
1082                 }
1083
1084                 else if (contains(str,"_bits[]"))
1085                         format = "xbm";
1086
1087                 else if (contains(str,"XPM") || contains(str, "static char *"))
1088                         format = "xpm";
1089
1090                 else if (contains(str,"BITPIX"))
1091                         format = "fits";
1092         }
1093
1094         if (!format.empty()) {
1095                 lyxerr[Debug::GRAPHICS]
1096                         << "Recognised Fileformat: " << format << endl;
1097                 return format;
1098         }
1099
1100         string const ext(GetExtension(filename));
1101         lyxerr[Debug::GRAPHICS]
1102                 << "filetools(getExtFromContents)\n"
1103                 << "\tCouldn't find a known Type!\n";
1104         if (!ext.empty()) {
1105             lyxerr[Debug::GRAPHICS]
1106                 << "\twill take the file extension -> "
1107                 << ext << endl;
1108                 return ext;
1109         } else {
1110             lyxerr[Debug::GRAPHICS]
1111                 << "\twill use ext or a \"user\" defined format" << endl;
1112             return "user";
1113         }
1114 }
1115
1116
1117 /// check for zipped file
1118 bool zippedFile(string const & name)
1119 {
1120         string const type = getExtFromContents(name);
1121         if (contains("gzip zip compress", type) && !type.empty())
1122                 return true;
1123         return false;
1124 }
1125
1126
1127 string const unzipFile(string const & zipped_file)
1128 {
1129         string const file = ChangeExtension(zipped_file, string());
1130         string  const tempfile = lyx::tempName(string(), file);
1131         // Run gunzip
1132         string const command = "gunzip -c " + zipped_file + " > " + tempfile;
1133         Systemcall one;
1134         one.startscript(Systemcall::Wait, command);
1135         // test that command was executed successfully (anon)
1136         // yes, please do. (Lgb)
1137         return tempfile;
1138 }
1139
1140
1141 string const MakeDisplayPath(string const & path, unsigned int threshold)
1142 {
1143         string str = path;
1144
1145         string const home(GetEnvPath("HOME"));
1146
1147         // replace /home/blah with ~/
1148         if (prefixIs(str, home))
1149                 str = subst(str, home, "~");
1150
1151         if (str.length() <= threshold)
1152                 return str;
1153
1154         string const prefix = ".../";
1155         string temp;
1156
1157         while (str.length() > threshold)
1158                 str = split(str, temp, '/');
1159
1160         // Did we shorten everything away?
1161         if (str.empty()) {
1162                 // Yes, filename itself is too long.
1163                 // Pick the start and the end of the filename.
1164                 str = OnlyFilename(path);
1165                 string const head = str.substr(0, threshold / 2 - 3);
1166
1167                 string::size_type len = str.length();
1168                 string const tail =
1169                         str.substr(len - threshold / 2 - 2, len - 1);
1170                 str = head + "..." + tail;
1171         }
1172
1173         return prefix + str;
1174 }
1175
1176
1177 bool LyXReadLink(string const & file, string & link, bool resolve)
1178 {
1179         char linkbuffer[512];
1180         // Should be PATH_MAX but that needs autconf support
1181         int const nRead = ::readlink(file.c_str(),
1182                                      linkbuffer, sizeof(linkbuffer) - 1);
1183         if (nRead <= 0)
1184                 return false;
1185         linkbuffer[nRead] = '\0'; // terminator
1186         if (resolve)
1187                 link = MakeAbsPath(linkbuffer, OnlyPath(file));
1188         else
1189                 link = linkbuffer;
1190         return true;
1191 }
1192
1193
1194 cmd_ret const RunCommand(string const & cmd)
1195 {
1196         // One question is if we should use popen or
1197         // create our own popen based on fork, exec, pipe
1198         // of course the best would be to have a
1199         // pstream (process stream), with the
1200         // variants ipstream, opstream
1201
1202         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1203
1204         // (Claus Hentschel) Check if popen was succesful ;-)
1205         if (!inf)
1206                 return make_pair(-1, string());
1207
1208         string ret;
1209         int c = fgetc(inf);
1210         while (c != EOF) {
1211                 ret += static_cast<char>(c);
1212                 c = fgetc(inf);
1213         }
1214         int const pret = pclose(inf);
1215         return make_pair(pret, ret);
1216 }
1217
1218
1219 string const findtexfile(string const & fil, string const & /*format*/)
1220 {
1221         /* There is no problem to extend this function too use other
1222            methods to look for files. It could be setup to look
1223            in environment paths and also if wanted as a last resort
1224            to a recursive find. One of the easier extensions would
1225            perhaps be to use the LyX file lookup methods. But! I am
1226            going to implement this until I see some demand for it.
1227            Lgb
1228         */
1229
1230         // If the file can be found directly, we just return a
1231         // absolute path version of it.
1232         if (FileInfo(fil).exist())
1233                 return MakeAbsPath(fil);
1234
1235         // No we try to find it using kpsewhich.
1236         // It seems from the kpsewhich manual page that it is safe to use
1237         // kpsewhich without --format: "When the --format option is not
1238         // given, the search path used when looking for a file is inferred
1239         // from the name given, by looking for a known extension. If no
1240         // known extension is found, the search path for TeX source files
1241         // is used."
1242         // However, we want to take advantage of the format sine almost all
1243         // the different formats has environment variables that can be used
1244         // to controll which paths to search. f.ex. bib looks in
1245         // BIBINPUTS and TEXBIB. Small list follows:
1246         // bib - BIBINPUTS, TEXBIB
1247         // bst - BSTINPUTS
1248         // graphic/figure - TEXPICTS, TEXINPUTS
1249         // ist - TEXINDEXSTYLE, INDEXSTYLE
1250         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1251         // tex - TEXINPUTS
1252         // tfm - TFMFONTS, TEXFONTS
1253         // This means that to use kpsewhich in the best possible way we
1254         // should help it by setting additional path in the approp. envir.var.
1255         string const kpsecmd = "kpsewhich " + fil;
1256
1257         cmd_ret const c = RunCommand(kpsecmd);
1258
1259         lyxerr[Debug::LATEX] << "kpse status = " << c.first << '\n'
1260                  << "kpse result = `" << rtrim(c.second, "\n")
1261                  << '\'' << endl;
1262         if (c.first != -1)
1263                 return os::internal_path(rtrim(c.second, "\n\r"));
1264         else
1265                 return string();
1266 }
1267
1268
1269 void removeAutosaveFile(string const & filename)
1270 {
1271         string a = OnlyPath(filename);
1272         a += '#';
1273         a += OnlyFilename(filename);
1274         a += '#';
1275         FileInfo const fileinfo(a);
1276         if (fileinfo.exist())
1277                 lyx::unlink(a);
1278 }
1279
1280
1281 void readBB_lyxerrMessage(string const & file, bool & zipped,
1282         string const & message)
1283 {
1284         lyxerr[Debug::GRAPHICS] << "[readBB_from_PSFile] "
1285                 << message << std::endl;
1286         if (zipped)
1287                 lyx::unlink(file);
1288 }
1289
1290
1291 string const readBB_from_PSFile(string const & file)
1292 {
1293         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1294         // It seems that every command in the header has an own line,
1295         // getline() should work for all files.
1296         // On the other hand some plot programs write the bb at the
1297         // end of the file. Than we have in the header:
1298         // %%BoundingBox: (atend)
1299         // In this case we must check the end.
1300         bool zipped = zippedFile(file);
1301         string const file_ = zipped ?
1302                 string(unzipFile(file)) : string(file);
1303         string const format = getExtFromContents(file_);
1304
1305         if (format != "eps" && format != "ps") {
1306                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1307                 return string();
1308         }
1309
1310         std::ifstream is(file_.c_str());
1311         while (is) {
1312                 string s;
1313                 getline(is,s);
1314                 if (contains(s,"%%BoundingBox:") && !contains(s,"atend")) {
1315                         string const bb = ltrim(s.substr(14));
1316                         readBB_lyxerrMessage(file_, zipped, bb);
1317                         return bb;
1318                 }
1319         }
1320         readBB_lyxerrMessage(file_, zipped, "no bb found");
1321         return string();
1322 }