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