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