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