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