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