]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
Change Assert to BOOST_ASSERT.
[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/cregex.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
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 = STRCONV(regex.Merge(STRCONV(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 STRCONV(ofs.str());
733                 }
734         }
735         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
736         return string();
737 }
738
739
740 //
741 // Search ${...} as Variable-Name inside the string and replace it with
742 // the denoted environmentvariable
743 // Allow Variables according to
744 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
745 //
746
747 string const ReplaceEnvironmentPath(string const & path)
748 {
749         //
750         // CompareChar: Environment variables starts with this character
751         // PathChar:    Next path component start with this character
752         // while CompareChar found do:
753         //       Split String with PathChar
754         //       Search Environmentvariable
755         //       if found: Replace Strings
756         //
757         char const CompareChar = '$';
758         char const FirstChar = '{';
759         char const EndChar = '}';
760         char const UnderscoreChar = '_';
761         string EndString; EndString += EndChar;
762         string FirstString; FirstString += FirstChar;
763         string CompareString; CompareString += CompareChar;
764         string const RegExp("*}*"); // Exist EndChar inside a String?
765
766 // first: Search for a '$' - Sign.
767         //string copy(path);
768         string result1; //(copy);    // for split-calls
769         string result0 = split(path, result1, CompareChar);
770         while (!result0.empty()) {
771                 string copy1(result0); // contains String after $
772
773                 // Check, if there is an EndChar inside original String.
774
775                 if (!regexMatch(copy1, RegExp)) {
776                         // No EndChar inside. So we are finished
777                         result1 += CompareString + result0;
778                         result0.erase();
779                         continue;
780                 }
781
782                 string res1;
783                 string res0 = split(copy1, res1, EndChar);
784                 // Now res1 holds the environmentvariable
785                 // First, check, if Contents is ok.
786                 if (res1.empty()) { // No environmentvariable. Continue Loop.
787                         result1 += CompareString + FirstString;
788                         result0  = res0;
789                         continue;
790                 }
791                 // check contents of res1
792                 char const * res1_contents = res1.c_str();
793                 if (*res1_contents != FirstChar) {
794                         // Again No Environmentvariable
795                         result1 += CompareString;
796                         result0 = res0;
797                 }
798
799                 // Check for variable names
800                 // Situation ${} is detected as "No Environmentvariable"
801                 char const * cp1 = res1_contents + 1;
802                 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
803                 ++cp1;
804                 while (*cp1 && result) {
805                         result = isalnum(*cp1) ||
806                                 (*cp1 == UnderscoreChar);
807                         ++cp1;
808                 }
809
810                 if (!result) {
811                         // no correct variable name
812                         result1 += CompareString + res1 + EndString;
813                         result0  = split(res0, res1, CompareChar);
814                         result1 += res1;
815                         continue;
816                 }
817
818                 string env(GetEnv(res1_contents + 1));
819                 if (!env.empty()) {
820                         // Congratulations. Environmentvariable found
821                         result1 += env;
822                 } else {
823                         result1 += CompareString + res1 + EndString;
824                 }
825                 // Next $-Sign?
826                 result0  = split(res0, res1, CompareChar);
827                 result1 += res1;
828         }
829         return result1;
830 }
831
832
833 // Make relative path out of two absolute paths
834 string const MakeRelPath(string const & abspath, string const & basepath)
835 // Makes relative path out of absolute path. If it is deeper than basepath,
836 // it's easy. If basepath and abspath share something (they are all deeper
837 // than some directory), it'll be rendered using ..'s. If they are completely
838 // different, then the absolute path will be used as relative path.
839 {
840         string::size_type const abslen = abspath.length();
841         string::size_type const baselen = basepath.length();
842
843         string::size_type i = os::common_path(abspath, basepath);
844
845         if (i == 0) {
846                 // actually no match - cannot make it relative
847                 return abspath;
848         }
849
850         // Count how many dirs there are in basepath above match
851         // and append as many '..''s into relpath
852         string buf;
853         string::size_type j = i;
854         while (j < baselen) {
855                 if (basepath[j] == '/') {
856                         if (j + 1 == baselen)
857                                 break;
858                         buf += "../";
859                 }
860                 ++j;
861         }
862
863         // Append relative stuff from common directory to abspath
864         if (abspath[i] == '/')
865                 ++i;
866         for (; i < abslen; ++i)
867                 buf += abspath[i];
868         // Remove trailing /
869         if (suffixIs(buf, '/'))
870                 buf.erase(buf.length() - 1);
871         // Substitute empty with .
872         if (buf.empty())
873                 buf = '.';
874         return buf;
875 }
876
877
878 // Append sub-directory(ies) to a path in an intelligent way
879 string const AddPath(string const & path, string const & path_2)
880 {
881         string buf;
882         string const path2 = os::slashify_path(path_2);
883
884         if (!path.empty() && path != "." && path != "./") {
885                 buf = os::slashify_path(path);
886                 if (path[path.length() - 1] != '/')
887                         buf += '/';
888         }
889
890         if (!path2.empty()) {
891                 string::size_type const p2start = path2.find_first_not_of('/');
892                 string::size_type const p2end = path2.find_last_not_of('/');
893                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
894                 buf += tmp + '/';
895         }
896         return buf;
897 }
898
899
900 /*
901  Change extension of oldname to extension.
902  Strips path off if no_path == true.
903  If no extension on oldname, just appends.
904  */
905 string const ChangeExtension(string const & oldname, string const & extension)
906 {
907         string::size_type const last_slash = oldname.rfind('/');
908         string::size_type last_dot = oldname.rfind('.');
909         if (last_dot < last_slash && last_slash != string::npos)
910                 last_dot = string::npos;
911
912         string ext;
913         // Make sure the extension starts with a dot
914         if (!extension.empty() && extension[0] != '.')
915                 ext= '.' + extension;
916         else
917                 ext = extension;
918
919         return os::slashify_path(oldname.substr(0, last_dot) + ext);
920 }
921
922
923 /// Return the extension of the file (not including the .)
924 string const GetExtension(string const & name)
925 {
926         string::size_type const last_slash = name.rfind('/');
927         string::size_type const last_dot = name.rfind('.');
928         if (last_dot != string::npos &&
929             (last_slash == string::npos || last_dot > last_slash))
930                 return name.substr(last_dot + 1,
931                                    name.length() - (last_dot + 1));
932         else
933                 return string();
934 }
935
936 // the different filetypes and what they contain in one of the first lines
937 // (dots are any characters).           (Herbert 20020131)
938 // AGR  Grace...
939 // BMP  BM...
940 // EPS  %!PS-Adobe-3.0 EPSF...
941 // FIG  #FIG...
942 // FITS ...BITPIX...
943 // GIF  GIF...
944 // JPG  JFIF
945 // PDF  %PDF-...
946 // PNG  .PNG...
947 // PBM  P1... or P4     (B/W)
948 // PGM  P2... or P5     (Grayscale)
949 // PPM  P3... or P6     (color)
950 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
951 // SGI  \001\332...     (decimal 474)
952 // TGIF %TGIF...
953 // TIFF II... or MM...
954 // XBM  ..._bits[]...
955 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
956 //      ...static char *...
957 // XWD  \000\000\000\151        (0x00006900) decimal 105
958 //
959 // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
960 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
961 // Z    \037\235                UNIX compress
962
963 /// return the "extension" which belongs to the contents.
964 /// for no knowing contents return the extension. Without
965 /// an extension and unknown contents we return "user"
966 string const getExtFromContents(string const & filename)
967 {
968         // paranoia check
969         if (filename.empty() || !IsFileReadable(filename))
970                 return string();
971
972
973         ifstream ifs(filename.c_str());
974         if (!ifs)
975                 // Couldn't open file...
976                 return string();
977
978         // gnuzip
979         string const gzipStamp = "\037\213";
980
981         // PKZIP
982         string const zipStamp = "PK";
983
984         // compress
985         string const compressStamp = "\037\235";
986
987         // Maximum strings to read
988         int const max_count = 50;
989         int count = 0;
990
991         string str, format;
992         bool firstLine = true;
993         while ((count++ < max_count) && format.empty()) {
994                 if (ifs.eof()) {
995                         lyxerr[Debug::GRAPHICS]
996                                 << "filetools(getExtFromContents)\n"
997                                 << "\tFile type not recognised before EOF!"
998                                 << endl;
999                         break;
1000                 }
1001
1002                 getline(ifs, str);
1003                 string const stamp = str.substr(0,2);
1004                 if (firstLine && str.size() >= 2) {
1005                         // at first we check for a zipped file, because this
1006                         // information is saved in the first bytes of the file!
1007                         // also some graphic formats which save the information
1008                         // in the first line, too.
1009                         if (prefixIs(str, gzipStamp)) {
1010                                 format =  "gzip";
1011
1012                         } else if (stamp == zipStamp) {
1013                                 format =  "zip";
1014
1015                         } else if (stamp == compressStamp) {
1016                                 format =  "compress";
1017
1018                         // the graphics part
1019                         } else if (stamp == "BM") {
1020                                 format =  "bmp";
1021
1022                         } else if (stamp == "\001\332") {
1023                                 format =  "sgi";
1024
1025                         // PBM family
1026                         // Don't need to use str.at(0), str.at(1) because
1027                         // we already know that str.size() >= 2
1028                         } else if (str[0] == 'P') {
1029                                 switch (str[1]) {
1030                                 case '1':
1031                                 case '4':
1032                                         format =  "pbm";
1033                                     break;
1034                                 case '2':
1035                                 case '5':
1036                                         format =  "pgm";
1037                                     break;
1038                                 case '3':
1039                                 case '6':
1040                                         format =  "ppm";
1041                                 }
1042                                 break;
1043
1044                         } else if ((stamp == "II") || (stamp == "MM")) {
1045                                 format =  "tiff";
1046
1047                         } else if (prefixIs(str,"%TGIF")) {
1048                                 format =  "tgif";
1049
1050                         } else if (prefixIs(str,"#FIG")) {
1051                                 format =  "fig";
1052
1053                         } else if (prefixIs(str,"GIF")) {
1054                                 format =  "gif";
1055
1056                         } else if (str.size() > 3) {
1057                                 int const c = ((str[0] << 24) & (str[1] << 16) &
1058                                                (str[2] << 8)  & str[3]);
1059                                 if (c == 105) {
1060                                         format =  "xwd";
1061                                 }
1062                         }
1063
1064                         firstLine = false;
1065                 }
1066
1067                 if (!format.empty())
1068                     break;
1069                 else if (contains(str,"EPSF"))
1070                         // dummy, if we have wrong file description like
1071                         // %!PS-Adobe-2.0EPSF"
1072                         format =  "eps";
1073
1074                 else if (contains(str,"Grace"))
1075                         format =  "agr";
1076
1077                 else if (contains(str,"JFIF"))
1078                         format =  "jpg";
1079
1080                 else if (contains(str,"%PDF"))
1081                         format =  "pdf";
1082
1083                 else if (contains(str,"PNG"))
1084                         format =  "png";
1085
1086                 else if (contains(str,"%!PS-Adobe")) {
1087                         // eps or ps
1088                         ifs >> str;
1089                         if (contains(str,"EPSF"))
1090                                 format = "eps";
1091                         else
1092                             format = "ps";
1093                 }
1094
1095                 else if (contains(str,"_bits[]"))
1096                         format = "xbm";
1097
1098                 else if (contains(str,"XPM") || contains(str, "static char *"))
1099                         format = "xpm";
1100
1101                 else if (contains(str,"BITPIX"))
1102                         format = "fits";
1103         }
1104
1105         if (!format.empty()) {
1106                 lyxerr[Debug::GRAPHICS]
1107                         << "Recognised Fileformat: " << format << endl;
1108                 return format;
1109         }
1110
1111         string const ext(GetExtension(filename));
1112         lyxerr[Debug::GRAPHICS]
1113                 << "filetools(getExtFromContents)\n"
1114                 << "\tCouldn't find a known Type!\n";
1115         if (!ext.empty()) {
1116             lyxerr[Debug::GRAPHICS]
1117                 << "\twill take the file extension -> "
1118                 << ext << endl;
1119                 return ext;
1120         } else {
1121             lyxerr[Debug::GRAPHICS]
1122                 << "\twill use ext or a \"user\" defined format" << endl;
1123             return "user";
1124         }
1125 }
1126
1127
1128 /// check for zipped file
1129 bool zippedFile(string const & name)
1130 {
1131         string const type = getExtFromContents(name);
1132         if (contains("gzip zip compress", type) && !type.empty())
1133                 return true;
1134         return false;
1135 }
1136
1137
1138 string const unzippedFileName(string const & zipped_file)
1139 {
1140         string const ext = GetExtension(zipped_file);
1141         if (ext == "gz" || ext == "z" || ext == "Z")
1142                 return ChangeExtension(zipped_file, string());
1143         return "unzipped_" + zipped_file;
1144 }
1145
1146
1147 string const unzipFile(string const & zipped_file)
1148 {
1149         string  const tempfile = unzippedFileName(zipped_file);
1150         // Run gunzip
1151         string const command = "gunzip -c " + zipped_file + " > " + tempfile;
1152         Systemcall one;
1153         one.startscript(Systemcall::Wait, command);
1154         // test that command was executed successfully (anon)
1155         // yes, please do. (Lgb)
1156         return tempfile;
1157 }
1158
1159
1160 string const MakeDisplayPath(string const & path, unsigned int threshold)
1161 {
1162         string str = path;
1163
1164         string const home(GetEnvPath("HOME"));
1165
1166         // replace /home/blah with ~/
1167         if (prefixIs(str, home))
1168                 str = subst(str, home, "~");
1169
1170         if (str.length() <= threshold)
1171                 return str;
1172
1173         string const prefix = ".../";
1174         string temp;
1175
1176         while (str.length() > threshold)
1177                 str = split(str, temp, '/');
1178
1179         // Did we shorten everything away?
1180         if (str.empty()) {
1181                 // Yes, filename itself is too long.
1182                 // Pick the start and the end of the filename.
1183                 str = OnlyFilename(path);
1184                 string const head = str.substr(0, threshold / 2 - 3);
1185
1186                 string::size_type len = str.length();
1187                 string const tail =
1188                         str.substr(len - threshold / 2 - 2, len - 1);
1189                 str = head + "..." + tail;
1190         }
1191
1192         return prefix + str;
1193 }
1194
1195
1196 bool LyXReadLink(string const & file, string & link, bool resolve)
1197 {
1198         char linkbuffer[512];
1199         // Should be PATH_MAX but that needs autconf support
1200         int const nRead = ::readlink(file.c_str(),
1201                                      linkbuffer, sizeof(linkbuffer) - 1);
1202         if (nRead <= 0)
1203                 return false;
1204         linkbuffer[nRead] = '\0'; // terminator
1205         if (resolve)
1206                 link = MakeAbsPath(linkbuffer, OnlyPath(file));
1207         else
1208                 link = linkbuffer;
1209         return true;
1210 }
1211
1212
1213 cmd_ret const RunCommand(string const & cmd)
1214 {
1215         // One question is if we should use popen or
1216         // create our own popen based on fork, exec, pipe
1217         // of course the best would be to have a
1218         // pstream (process stream), with the
1219         // variants ipstream, opstream
1220
1221         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1222
1223         // (Claus Hentschel) Check if popen was succesful ;-)
1224         if (!inf)
1225                 return make_pair(-1, string());
1226
1227         string ret;
1228         int c = fgetc(inf);
1229         while (c != EOF) {
1230                 ret += static_cast<char>(c);
1231                 c = fgetc(inf);
1232         }
1233         int const pret = pclose(inf);
1234         return make_pair(pret, ret);
1235 }
1236
1237
1238 string const findtexfile(string const & fil, string const & /*format*/)
1239 {
1240         /* There is no problem to extend this function too use other
1241            methods to look for files. It could be setup to look
1242            in environment paths and also if wanted as a last resort
1243            to a recursive find. One of the easier extensions would
1244            perhaps be to use the LyX file lookup methods. But! I am
1245            going to implement this until I see some demand for it.
1246            Lgb
1247         */
1248
1249         // If the file can be found directly, we just return a
1250         // absolute path version of it.
1251         if (FileInfo(fil).exist())
1252                 return MakeAbsPath(fil);
1253
1254         // No we try to find it using kpsewhich.
1255         // It seems from the kpsewhich manual page that it is safe to use
1256         // kpsewhich without --format: "When the --format option is not
1257         // given, the search path used when looking for a file is inferred
1258         // from the name given, by looking for a known extension. If no
1259         // known extension is found, the search path for TeX source files
1260         // is used."
1261         // However, we want to take advantage of the format sine almost all
1262         // the different formats has environment variables that can be used
1263         // to controll which paths to search. f.ex. bib looks in
1264         // BIBINPUTS and TEXBIB. Small list follows:
1265         // bib - BIBINPUTS, TEXBIB
1266         // bst - BSTINPUTS
1267         // graphic/figure - TEXPICTS, TEXINPUTS
1268         // ist - TEXINDEXSTYLE, INDEXSTYLE
1269         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1270         // tex - TEXINPUTS
1271         // tfm - TFMFONTS, TEXFONTS
1272         // This means that to use kpsewhich in the best possible way we
1273         // should help it by setting additional path in the approp. envir.var.
1274         string const kpsecmd = "kpsewhich " + fil;
1275
1276         cmd_ret const c = RunCommand(kpsecmd);
1277
1278         lyxerr[Debug::LATEX] << "kpse status = " << c.first << '\n'
1279                  << "kpse result = `" << rtrim(c.second, "\n")
1280                  << '\'' << endl;
1281         if (c.first != -1)
1282                 return os::internal_path(rtrim(c.second, "\n\r"));
1283         else
1284                 return string();
1285 }
1286
1287
1288 void removeAutosaveFile(string const & filename)
1289 {
1290         string a = OnlyPath(filename);
1291         a += '#';
1292         a += OnlyFilename(filename);
1293         a += '#';
1294         FileInfo const fileinfo(a);
1295         if (fileinfo.exist())
1296                 unlink(a);
1297 }
1298
1299
1300 void readBB_lyxerrMessage(string const & file, bool & zipped,
1301         string const & message)
1302 {
1303         lyxerr[Debug::GRAPHICS] << "[readBB_from_PSFile] "
1304                 << message << std::endl;
1305 #warning Why is this func deleting a file? (Lgb)
1306         if (zipped)
1307                 unlink(file);
1308 }
1309
1310
1311 string const readBB_from_PSFile(string const & file)
1312 {
1313         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1314         // It seems that every command in the header has an own line,
1315         // getline() should work for all files.
1316         // On the other hand some plot programs write the bb at the
1317         // end of the file. Than we have in the header:
1318         // %%BoundingBox: (atend)
1319         // In this case we must check the end.
1320         bool zipped = zippedFile(file);
1321         string const file_ = zipped ?
1322                 string(unzipFile(file)) : string(file);
1323         string const format = getExtFromContents(file_);
1324
1325         if (format != "eps" && format != "ps") {
1326                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1327                 return string();
1328         }
1329
1330         std::ifstream is(file_.c_str());
1331         while (is) {
1332                 string s;
1333                 getline(is,s);
1334                 if (contains(s,"%%BoundingBox:") && !contains(s,"atend")) {
1335                         string const bb = ltrim(s.substr(14));
1336                         readBB_lyxerrMessage(file_, zipped, bb);
1337                         return bb;
1338                 }
1339         }
1340         readBB_lyxerrMessage(file_, zipped, "no bb found");
1341         return string();
1342 }
1343
1344
1345 int compare_timestamps(string const & file1, string const & file2)
1346 {
1347         BOOST_ASSERT(AbsolutePath(file1) && AbsolutePath(file2));
1348
1349         // If the original is newer than the copy, then copy the original
1350         // to the new directory.
1351         FileInfo f1(file1);
1352         FileInfo f2(file2);
1353
1354         int cmp = 0;
1355         if (f1.exist() && f2.exist()) {
1356                 double const tmp = difftime(f1.getModificationTime(),
1357                                             f2.getModificationTime());
1358                 if (tmp != 0)
1359                         cmp = tmp > 0 ? 1 : -1;
1360
1361         } else if (f1.exist()) {
1362                 cmp = 1;
1363         } else if (f2.exist()) {
1364                 cmp = -1;
1365         }
1366
1367         return cmp;
1368 }
1369
1370 } //namespace support
1371 } // namespace lyx