]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
Provide do_put methods for inserting all remaining basic type values
[lyx.git] / src / support / filetools.C
1 /**
2  * \file filetools.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7  *
8  * \author Ivan Schreter
9  * \author Dirk Niggemann
10  * \author Asger Alstrup
11  * \author Lars Gullik Bjønnes
12  * \author Jean-Marc Lasgouttes
13  * \author Angus Leeming
14  * \author John Levon
15  * \author Herbert Voß
16  *
17  * Full author contact details are available in file CREDITS.
18  *
19  * General path-mangling functions
20  */
21
22 #include <config.h>
23
24 #include "support/convert.h"
25 #include "support/environment.h"
26 #include "support/filetools.h"
27 #include "support/fs_extras.h"
28 #include "support/lstrings.h"
29 #include "support/lyxlib.h"
30 #include "support/os.h"
31 #include "support/package.h"
32 #include "support/path.h"
33 #include "support/systemcall.h"
34
35 // FIXME Interface violation
36 #include "gettext.h"
37 #include "debug.h"
38
39 #include <boost/assert.hpp>
40 #include <boost/filesystem/operations.hpp>
41 #include <boost/regex.hpp>
42
43 #include <fcntl.h>
44
45 #include <cctype>
46 #include <cerrno>
47 #include <cstdlib>
48 #include <cstdio>
49
50 #include <utility>
51 #include <fstream>
52 #include <sstream>
53
54 #ifndef CXX_GLOBAL_CSTD
55 using std::fgetc;
56 using std::isalnum;
57 using std::isalpha;
58 #endif
59
60 using std::endl;
61 using std::getline;
62 using std::make_pair;
63 using std::string;
64 using std::ifstream;
65 using std::ostringstream;
66 using std::vector;
67
68 namespace fs = boost::filesystem;
69
70 namespace lyx {
71 namespace support {
72
73 bool isLyXFilename(string const & filename)
74 {
75         return suffixIs(ascii_lowercase(filename), ".lyx");
76 }
77
78
79 bool isSGMLFilename(string const & filename)
80 {
81         return suffixIs(ascii_lowercase(filename), ".sgml");
82 }
83
84
85 string const latex_path(string const & original_path,
86                 latex_path_extension extension,
87                 latex_path_dots dots)
88 {
89         // On cygwin, we may need windows or posix style paths.
90         string path = os::latex_path(original_path);
91         path = subst(path, "~", "\\string~");
92         if (path.find(' ') != string::npos) {
93                 // We can't use '"' because " is sometimes active (e.g. if
94                 // babel is loaded with the "german" option)
95                 if (extension == EXCLUDE_EXTENSION) {
96                         // ChangeExtension calls os::internal_path internally
97                         // so don't use it to remove the extension.
98                         string const ext = getExtension(path);
99                         string const base = ext.empty() ?
100                                 path :
101                                 path.substr(0, path.length() - ext.length() - 1);
102                         // ChangeExtension calls os::internal_path internally
103                         // so don't use it to re-add the extension.
104                         path = "\\string\"" + base + "\\string\"." + ext;
105                 } else {
106                         path = "\\string\"" + path + "\\string\"";
107                 }
108         }
109
110         return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
111 }
112
113
114 // Substitutes spaces with underscores in filename (and path)
115 string const makeLatexName(string const & file)
116 {
117         string name = onlyFilename(file);
118         string const path = onlyPath(file);
119
120         for (string::size_type i = 0; i < name.length(); ++i)
121                 name[i] &= 0x7f; // set 8th bit to 0
122
123         // ok so we scan through the string twice, but who cares.
124         string const keep = "abcdefghijklmnopqrstuvwxyz"
125                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
126                 "@!\"'()*+,-./0123456789:;<=>?[]`|";
127
128         string::size_type pos = 0;
129         while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
130                 name[pos++] = '_';
131
132         return addName(path, name);
133 }
134
135
136 string const quoteName(string const & name, quote_style style)
137 {
138         switch(style) {
139         case quote_shell:
140                 // This does not work for filenames containing " (windows)
141                 // or ' (all other OSes). This can't be changed easily, since
142                 // we would need to adapt the command line parser in
143                 // Forkedcall::generateChild. Therefore we don't pass user
144                 // filenames to child processes if possible. We store them in
145                 // a python script instead, where we don't have these
146                 // limitations.
147                 return (os::shell() == os::UNIX) ?
148                         '\'' + name + '\'':
149                         '"' + name + '"';
150         case quote_python:
151                 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
152                      + "\"";
153         }
154         // shut up stupid compiler
155         return string();
156 }
157
158
159 bool isFileReadable(FileName const & filename)
160 {
161         std::string const path = filename.toFilesystemEncoding();
162         return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
163 }
164
165
166 //returns true: dir writeable
167 //        false: not writeable
168 bool isDirWriteable(string const & path)
169 {
170         lyxerr[Debug::FILES] << "isDirWriteable: " << path << endl;
171
172         string const tmpfl = tempName(path, "lyxwritetest");
173
174         if (tmpfl.empty())
175                 return false;
176
177         unlink(FileName(tmpfl));
178         return true;
179 }
180
181
182 // Uses a string of paths separated by ";"s to find a file to open.
183 // Can't cope with pathnames with a ';' in them. Returns full path to file.
184 // If path entry begins with $$LyX/, use system_lyxdir
185 // If path entry begins with $$User/, use user_lyxdir
186 // Example: "$$User/doc;$$LyX/doc"
187 FileName const fileOpenSearch(string const & path, string const & name,
188                              string const & ext)
189 {
190         FileName real_file;
191         string path_element;
192         bool notfound = true;
193         string tmppath = split(path, path_element, ';');
194
195         while (notfound && !path_element.empty()) {
196                 path_element = os::internal_path(path_element);
197                 if (!suffixIs(path_element, '/'))
198                         path_element += '/';
199                 path_element = subst(path_element, "$$LyX",
200                                      package().system_support());
201                 path_element = subst(path_element, "$$User",
202                                      package().user_support());
203
204                 real_file = fileSearch(path_element, name, ext);
205
206                 if (real_file.empty()) {
207                         do {
208                                 tmppath = split(tmppath, path_element, ';');
209                         } while (!tmppath.empty() && path_element.empty());
210                 } else {
211                         notfound = false;
212                 }
213         }
214         return real_file;
215 }
216
217
218 /// Returns a vector of all files in directory dir having extension ext.
219 vector<string> const dirList(FileName const & dir, string const & ext)
220 {
221         // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
222         vector<string> dirlist;
223
224         string const encoded_dir = dir.toFilesystemEncoding();
225         if (!(fs::exists(encoded_dir) && fs::is_directory(encoded_dir))) {
226                 lyxerr[Debug::FILES]
227                         << "Directory \"" << dir
228                         << "\" does not exist to DirList." << endl;
229                 return dirlist;
230         }
231
232         string extension;
233         if (!ext.empty() && ext[0] != '.')
234                 extension += '.';
235         extension += ext;
236
237         fs::directory_iterator dit(encoded_dir);
238         fs::directory_iterator end;
239         for (; dit != end; ++dit) {
240                 string const & fil = dit->leaf();
241                 if (suffixIs(fil, extension)) {
242                         // FIXME UNICODE: We need to convert from filesystem encoding to utf8
243                         dirlist.push_back(fil);
244                 }
245         }
246         return dirlist;
247 }
248
249
250 // Returns the real name of file name in directory path, with optional
251 // extension ext.
252 FileName const fileSearch(string const & path, string const & name,
253                         string const & ext)
254 {
255         // if `name' is an absolute path, we ignore the setting of `path'
256         // Expand Environmentvariables in 'name'
257         string const tmpname = replaceEnvironmentPath(name);
258         FileName fullname(makeAbsPath(tmpname, path));
259         // search first without extension, then with it.
260         if (isFileReadable(fullname))
261                 return fullname;
262         if (ext.empty())
263                 return FileName();
264         fullname = FileName(changeExtension(fullname.absFilename(), ext));
265         return isFileReadable(fullname) ? fullname : FileName();
266 }
267
268
269 // Search the file name.ext in the subdirectory dir of
270 //   1) user_lyxdir
271 //   2) build_lyxdir (if not empty)
272 //   3) system_lyxdir
273 FileName const libFileSearch(string const & dir, string const & name,
274                            string const & ext)
275 {
276         FileName fullname = fileSearch(addPath(package().user_support(), dir),
277                                      name, ext);
278         if (!fullname.empty())
279                 return fullname;
280
281         if (!package().build_support().empty())
282                 fullname = fileSearch(addPath(package().build_support(), dir),
283                                       name, ext);
284         if (!fullname.empty())
285                 return fullname;
286
287         return fileSearch(addPath(package().system_support(), dir), name, ext);
288 }
289
290
291 FileName const i18nLibFileSearch(string const & dir, string const & name,
292                   string const & ext)
293 {
294         // the following comments are from intl/dcigettext.c. We try
295         // to mimick this behaviour here.
296         /* The highest priority value is the `LANGUAGE' environment
297            variable. But we don't use the value if the currently
298            selected locale is the C locale. This is a GNU extension. */
299         /* [Otherwise] We have to proceed with the POSIX methods of
300            looking to `LC_ALL', `LC_xxx', and `LANG'. */
301
302         string lang = getEnv("LC_ALL");
303         if (lang.empty()) {
304                 lang = getEnv("LC_MESSAGES");
305                 if (lang.empty()) {
306                         lang = getEnv("LANG");
307                         if (lang.empty())
308                                 lang = "C";
309                 }
310         }
311
312         string const language = getEnv("LANGUAGE");
313         if (lang != "C" && lang != "POSIX" && !language.empty())
314                 lang = language;
315
316         string l;
317         lang = split(lang, l, ':');
318         while (!l.empty() && l != "C" && l != "POSIX") {
319                 FileName const tmp = libFileSearch(dir,
320                                                  token(l, '_', 0) + '_' + name,
321                                                  ext);
322                 if (!tmp.empty())
323                         return tmp;
324                 lang = split(lang, l, ':');
325         }
326
327         return libFileSearch(dir, name, ext);
328 }
329
330
331 string const libScriptSearch(string const & command_in, quote_style style)
332 {
333         static string const token_scriptpath = "$$s/";
334
335         string command = command_in;
336         // Find the starting position of "$$s/"
337         string::size_type const pos1 = command.find(token_scriptpath);
338         if (pos1 == string::npos)
339                 return command;
340         // Find the end of the "$$s/some_subdir/some_script" word within
341         // command. Assumes that the script name does not contain spaces.
342         string::size_type const start_script = pos1 + 4;
343         string::size_type const pos2 = command.find(' ', start_script);
344         string::size_type const size_script = pos2 == string::npos?
345                 (command.size() - start_script) : pos2 - start_script;
346
347         // Does this script file exist?
348         string const script = 
349                 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
350
351         if (script.empty()) {
352                 // Replace "$$s/" with ""
353                 command.erase(pos1, 4);
354         } else {
355                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
356                 string::size_type const size_replace = size_script + 4;
357                 command.replace(pos1, size_replace, quoteName(script, style));
358         }
359
360         return command;
361 }
362
363
364 namespace {
365
366 FileName const createTmpDir(FileName const & tempdir, string const & mask)
367 {
368         lyxerr[Debug::FILES]
369                 << "createTmpDir: tempdir=`" << tempdir << "'\n"
370                 << "createTmpDir:    mask=`" << mask << '\'' << endl;
371
372         string const tmpfl = tempName(tempdir.absFilename(), mask);
373         // lyx::tempName actually creates a file to make sure that it
374         // stays unique. So we have to delete it before we can create
375         // a dir with the same name. Note also that we are not thread
376         // safe because of the gap between unlink and mkdir. (Lgb)
377         unlink(FileName(tmpfl));
378
379         if (tmpfl.empty() || mkdir(FileName(tmpfl), 0700)) {
380                 lyxerr << "LyX could not create the temporary directory '"
381                        << tmpfl << "'" << endl;
382                 return FileName();
383         }
384
385         return FileName(tmpfl);
386 }
387
388 } // namespace anon
389
390
391 bool destroyDir(FileName const & tmpdir)
392 {
393         try {
394                 return fs::remove_all(tmpdir.toFilesystemEncoding()) > 0;
395         } catch (fs::filesystem_error const & fe){
396                 lyxerr << "Could not delete " << tmpdir << ". (" << fe.what() << ")" << std::endl;
397                 return false;
398         }
399 }
400
401
402 string const createBufferTmpDir()
403 {
404         static int count;
405         // We are in our own directory.  Why bother to mangle name?
406         // In fact I wrote this code to circumvent a problematic behaviour
407         // (bug?) of EMX mkstemp().
408         string const tmpfl =
409                 package().temp_dir() + "/lyx_tmpbuf" +
410                 convert<string>(count++);
411
412         if (mkdir(FileName(tmpfl), 0777)) {
413                 lyxerr << "LyX could not create the temporary directory '"
414                        << tmpfl << "'" << endl;
415                 return string();
416         }
417         return tmpfl;
418 }
419
420
421 FileName const createLyXTmpDir(FileName const & deflt)
422 {
423         if (!deflt.empty() && deflt.absFilename() != "/tmp") {
424                 if (mkdir(deflt, 0777)) {
425                         if (isDirWriteable(deflt.absFilename())) {
426                                 // deflt could not be created because it
427                                 // did exist already, so let's create our own
428                                 // dir inside deflt.
429                                 return createTmpDir(deflt, "lyx_tmpdir");
430                         } else {
431                                 // some other error occured.
432                                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
433                         }
434                 } else
435                         return deflt;
436         } else {
437                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
438         }
439 }
440
441
442 bool createDirectory(string const & path, int permission)
443 {
444         string temp = rtrim(os::internal_path(path), "/");
445         BOOST_ASSERT(!temp.empty());
446         return mkdir(FileName(temp), permission) == 0;
447 }
448
449
450 // Strip filename from path name
451 string const onlyPath(string const & filename)
452 {
453         // If empty filename, return empty
454         if (filename.empty())
455                 return filename;
456
457         // Find last / or start of filename
458         string::size_type j = filename.rfind('/');
459         return j == string::npos ? "./" : filename.substr(0, j + 1);
460 }
461
462
463 // Convert relative path into absolute path based on a basepath.
464 // If relpath is absolute, just use that.
465 // If basepath is empty, use CWD as base.
466 string const makeAbsPath(string const & relPath, string const & basePath)
467 {
468         // checks for already absolute path
469         if (os::is_absolute_path(relPath))
470                 return relPath;
471
472         // Copies given paths
473         string tempRel = os::internal_path(relPath);
474         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
475         tempRel = subst(tempRel, "//", "/");
476
477         string tempBase;
478
479         if (os::is_absolute_path(basePath))
480                 tempBase = basePath;
481         else
482                 tempBase = addPath(getcwd(), basePath);
483
484         // Handle /./ at the end of the path
485         while (suffixIs(tempBase, "/./"))
486                 tempBase.erase(tempBase.length() - 2);
487
488         // processes relative path
489         string rTemp = tempRel;
490         string temp;
491
492         while (!rTemp.empty()) {
493                 // Split by next /
494                 rTemp = split(rTemp, temp, '/');
495
496                 if (temp == ".") continue;
497                 if (temp == "..") {
498                         // Remove one level of TempBase
499                         string::difference_type i = tempBase.length() - 2;
500                         if (i < 0)
501                                 i = 0;
502                         while (i > 0 && tempBase[i] != '/')
503                                 --i;
504                         if (i > 0)
505                                 tempBase.erase(i, string::npos);
506                         else
507                                 tempBase += '/';
508                 } else if (temp.empty() && !rTemp.empty()) {
509                                 tempBase = os::current_root() + rTemp;
510                                 rTemp.erase();
511                 } else {
512                         // Add this piece to TempBase
513                         if (!suffixIs(tempBase, '/'))
514                                 tempBase += '/';
515                         tempBase += temp;
516                 }
517         }
518
519         // returns absolute path
520         return os::internal_path(tempBase);
521 }
522
523
524 // Correctly append filename to the pathname.
525 // If pathname is '.', then don't use pathname.
526 // Chops any path of filename.
527 string const addName(string const & path, string const & fname)
528 {
529         string const basename = onlyFilename(fname);
530         string buf;
531
532         if (path != "." && path != "./" && !path.empty()) {
533                 buf = os::internal_path(path);
534                 if (!suffixIs(path, '/'))
535                         buf += '/';
536         }
537
538         return buf + basename;
539 }
540
541
542 // Strips path from filename
543 string const onlyFilename(string const & fname)
544 {
545         if (fname.empty())
546                 return fname;
547
548         string::size_type j = fname.rfind('/');
549         if (j == string::npos) // no '/' in fname
550                 return fname;
551
552         // Strip to basename
553         return fname.substr(j + 1);
554 }
555
556
557 /// Returns true is path is absolute
558 bool absolutePath(string const & path)
559 {
560         return os::is_absolute_path(path);
561 }
562
563
564 // Create absolute path. If impossible, don't do anything
565 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
566 string const expandPath(string const & path)
567 {
568         // checks for already absolute path
569         string rTemp = replaceEnvironmentPath(path);
570         if (os::is_absolute_path(rTemp))
571                 return rTemp;
572
573         string temp;
574         string const copy = rTemp;
575
576         // Split by next /
577         rTemp = split(rTemp, temp, '/');
578
579         if (temp == ".")
580                 return getcwd() + '/' + rTemp;
581
582         if (temp == "~")
583                 return package().home_dir() + '/' + rTemp;
584
585         if (temp == "..")
586                 return makeAbsPath(copy);
587
588         // Don't know how to handle this
589         return copy;
590 }
591
592
593 // Normalize a path. Constracts path/../path
594 // Can't handle "../../" or "/../" (Asger)
595 // Also converts paths like /foo//bar ==> /foo/bar
596 string const normalizePath(string const & path)
597 {
598         // Normalize paths like /foo//bar ==> /foo/bar
599         static boost::regex regex("/{2,}");
600         string const tmppath = boost::regex_merge(path, regex, "/");
601
602         fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
603
604         if (!npath.is_complete())
605                 return "./" + npath.string() + '/';
606
607         return npath.string() + '/';
608 }
609
610
611 string const getFileContents(FileName const & fname)
612 {
613         string const encodedname = fname.toFilesystemEncoding();
614         if (fs::exists(encodedname)) {
615                 ifstream ifs(encodedname.c_str());
616                 ostringstream ofs;
617                 if (ifs && ofs) {
618                         ofs << ifs.rdbuf();
619                         ifs.close();
620                         return ofs.str();
621                 }
622         }
623         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
624         return string();
625 }
626
627
628 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
629 string const replaceEnvironmentPath(string const & path)
630 {
631         // ${VAR} is defined as
632         // $\{[A-Za-z_][A-Za-z_0-9]*\}
633         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
634
635         // $VAR is defined as:
636         // $\{[A-Za-z_][A-Za-z_0-9]*\}
637         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
638
639         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
640         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
641         boost::smatch what;
642
643         string result = path;
644         while (1) {
645                 regex_match(result, what, envvar_br_re);
646                 if (!what[0].matched) {
647                         regex_match(result, what, envvar_re);
648                         if (!what[0].matched)
649                                 break;
650                 }
651                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
652         }
653         return result;
654 }
655
656
657 // Make relative path out of two absolute paths
658 string const makeRelPath(string const & abspath, string const & basepath)
659 // Makes relative path out of absolute path. If it is deeper than basepath,
660 // it's easy. If basepath and abspath share something (they are all deeper
661 // than some directory), it'll be rendered using ..'s. If they are completely
662 // different, then the absolute path will be used as relative path.
663 {
664         string::size_type const abslen = abspath.length();
665         string::size_type const baselen = basepath.length();
666
667         string::size_type i = os::common_path(abspath, basepath);
668
669         if (i == 0) {
670                 // actually no match - cannot make it relative
671                 return abspath;
672         }
673
674         // Count how many dirs there are in basepath above match
675         // and append as many '..''s into relpath
676         string buf;
677         string::size_type j = i;
678         while (j < baselen) {
679                 if (basepath[j] == '/') {
680                         if (j + 1 == baselen)
681                                 break;
682                         buf += "../";
683                 }
684                 ++j;
685         }
686
687         // Append relative stuff from common directory to abspath
688         if (abspath[i] == '/')
689                 ++i;
690         for (; i < abslen; ++i)
691                 buf += abspath[i];
692         // Remove trailing /
693         if (suffixIs(buf, '/'))
694                 buf.erase(buf.length() - 1);
695         // Substitute empty with .
696         if (buf.empty())
697                 buf = '.';
698         return buf;
699 }
700
701
702 // Append sub-directory(ies) to a path in an intelligent way
703 string const addPath(string const & path, string const & path_2)
704 {
705         string buf;
706         string const path2 = os::internal_path(path_2);
707
708         if (!path.empty() && path != "." && path != "./") {
709                 buf = os::internal_path(path);
710                 if (path[path.length() - 1] != '/')
711                         buf += '/';
712         }
713
714         if (!path2.empty()) {
715                 string::size_type const p2start = path2.find_first_not_of('/');
716                 string::size_type const p2end = path2.find_last_not_of('/');
717                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
718                 buf += tmp + '/';
719         }
720         return buf;
721 }
722
723
724 /*
725  Change extension of oldname to extension.
726  Strips path off if no_path == true.
727  If no extension on oldname, just appends.
728  */
729 string const changeExtension(string const & oldname, string const & extension)
730 {
731         string::size_type const last_slash = oldname.rfind('/');
732         string::size_type last_dot = oldname.rfind('.');
733         if (last_dot < last_slash && last_slash != string::npos)
734                 last_dot = string::npos;
735
736         string ext;
737         // Make sure the extension starts with a dot
738         if (!extension.empty() && extension[0] != '.')
739                 ext= '.' + extension;
740         else
741                 ext = extension;
742
743         return os::internal_path(oldname.substr(0, last_dot) + ext);
744 }
745
746
747 string const removeExtension(string const & name)
748 {
749         return changeExtension(name, string());
750 }
751
752
753 /// Return the extension of the file (not including the .)
754 string const getExtension(string const & name)
755 {
756         string::size_type const last_slash = name.rfind('/');
757         string::size_type const last_dot = name.rfind('.');
758         if (last_dot != string::npos &&
759             (last_slash == string::npos || last_dot > last_slash))
760                 return name.substr(last_dot + 1,
761                                    name.length() - (last_dot + 1));
762         else
763                 return string();
764 }
765
766
767 // the different filetypes and what they contain in one of the first lines
768 // (dots are any characters).           (Herbert 20020131)
769 // AGR  Grace...
770 // BMP  BM...
771 // EPS  %!PS-Adobe-3.0 EPSF...
772 // FIG  #FIG...
773 // FITS ...BITPIX...
774 // GIF  GIF...
775 // JPG  JFIF
776 // PDF  %PDF-...
777 // PNG  .PNG...
778 // PBM  P1... or P4     (B/W)
779 // PGM  P2... or P5     (Grayscale)
780 // PPM  P3... or P6     (color)
781 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
782 // SGI  \001\332...     (decimal 474)
783 // TGIF %TGIF...
784 // TIFF II... or MM...
785 // XBM  ..._bits[]...
786 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
787 //      ...static char *...
788 // XWD  \000\000\000\151        (0x00006900) decimal 105
789 //
790 // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
791 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
792 // Z    \037\235                UNIX compress
793
794 string const getFormatFromContents(FileName const & filename)
795 {
796         // paranoia check
797         if (filename.empty() || !isFileReadable(filename))
798                 return string();
799
800         ifstream ifs(filename.toFilesystemEncoding().c_str());
801         if (!ifs)
802                 // Couldn't open file...
803                 return string();
804
805         // gnuzip
806         static string const gzipStamp = "\037\213";
807
808         // PKZIP
809         static string const zipStamp = "PK";
810
811         // compress
812         static string const compressStamp = "\037\235";
813
814         // Maximum strings to read
815         int const max_count = 50;
816         int count = 0;
817
818         string str;
819         string format;
820         bool firstLine = true;
821         while ((count++ < max_count) && format.empty()) {
822                 if (ifs.eof()) {
823                         lyxerr[Debug::GRAPHICS]
824                                 << "filetools(getFormatFromContents)\n"
825                                 << "\tFile type not recognised before EOF!"
826                                 << endl;
827                         break;
828                 }
829
830                 getline(ifs, str);
831                 string const stamp = str.substr(0, 2);
832                 if (firstLine && str.size() >= 2) {
833                         // at first we check for a zipped file, because this
834                         // information is saved in the first bytes of the file!
835                         // also some graphic formats which save the information
836                         // in the first line, too.
837                         if (prefixIs(str, gzipStamp)) {
838                                 format =  "gzip";
839
840                         } else if (stamp == zipStamp) {
841                                 format =  "zip";
842
843                         } else if (stamp == compressStamp) {
844                                 format =  "compress";
845
846                         // the graphics part
847                         } else if (stamp == "BM") {
848                                 format =  "bmp";
849
850                         } else if (stamp == "\001\332") {
851                                 format =  "sgi";
852
853                         // PBM family
854                         // Don't need to use str.at(0), str.at(1) because
855                         // we already know that str.size() >= 2
856                         } else if (str[0] == 'P') {
857                                 switch (str[1]) {
858                                 case '1':
859                                 case '4':
860                                         format =  "pbm";
861                                     break;
862                                 case '2':
863                                 case '5':
864                                         format =  "pgm";
865                                     break;
866                                 case '3':
867                                 case '6':
868                                         format =  "ppm";
869                                 }
870                                 break;
871
872                         } else if ((stamp == "II") || (stamp == "MM")) {
873                                 format =  "tiff";
874
875                         } else if (prefixIs(str,"%TGIF")) {
876                                 format =  "tgif";
877
878                         } else if (prefixIs(str,"#FIG")) {
879                                 format =  "fig";
880
881                         } else if (prefixIs(str,"GIF")) {
882                                 format =  "gif";
883
884                         } else if (str.size() > 3) {
885                                 int const c = ((str[0] << 24) & (str[1] << 16) &
886                                                (str[2] << 8)  & str[3]);
887                                 if (c == 105) {
888                                         format =  "xwd";
889                                 }
890                         }
891
892                         firstLine = false;
893                 }
894
895                 if (!format.empty())
896                     break;
897                 else if (contains(str,"EPSF"))
898                         // dummy, if we have wrong file description like
899                         // %!PS-Adobe-2.0EPSF"
900                         format =  "eps";
901
902                 else if (contains(str,"Grace"))
903                         format =  "agr";
904
905                 else if (contains(str,"JFIF"))
906                         format =  "jpg";
907
908                 else if (contains(str,"%PDF"))
909                         format =  "pdf";
910
911                 else if (contains(str,"PNG"))
912                         format =  "png";
913
914                 else if (contains(str,"%!PS-Adobe")) {
915                         // eps or ps
916                         ifs >> str;
917                         if (contains(str,"EPSF"))
918                                 format = "eps";
919                         else
920                             format = "ps";
921                 }
922
923                 else if (contains(str,"_bits[]"))
924                         format = "xbm";
925
926                 else if (contains(str,"XPM") || contains(str, "static char *"))
927                         format = "xpm";
928
929                 else if (contains(str,"BITPIX"))
930                         format = "fits";
931         }
932
933         if (!format.empty()) {
934                 lyxerr[Debug::GRAPHICS]
935                         << "Recognised Fileformat: " << format << endl;
936                 return format;
937         }
938
939         lyxerr[Debug::GRAPHICS]
940                 << "filetools(getFormatFromContents)\n"
941                 << "\tCouldn't find a known format!\n";
942         return string();
943 }
944
945
946 /// check for zipped file
947 bool zippedFile(FileName const & name)
948 {
949         string const type = getFormatFromContents(name);
950         if (contains("gzip zip compress", type) && !type.empty())
951                 return true;
952         return false;
953 }
954
955
956 string const unzippedFileName(string const & zipped_file)
957 {
958         string const ext = getExtension(zipped_file);
959         if (ext == "gz" || ext == "z" || ext == "Z")
960                 return changeExtension(zipped_file, string());
961         return "unzipped_" + zipped_file;
962 }
963
964
965 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
966 {
967         FileName const tempfile = FileName(unzipped_file.empty() ?
968                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
969                 unzipped_file);
970         // Run gunzip
971         string const command = "gunzip -c " +
972                 zipped_file.toFilesystemEncoding() + " > " +
973                 tempfile.toFilesystemEncoding();
974         Systemcall one;
975         one.startscript(Systemcall::Wait, command);
976         // test that command was executed successfully (anon)
977         // yes, please do. (Lgb)
978         return tempfile;
979 }
980
981
982 docstring const makeDisplayPath(string const & path, unsigned int threshold)
983 {
984         string str = path;
985         string const home = package().home_dir();
986
987         // replace /home/blah with ~/
988         if (!home.empty() && prefixIs(str, home))
989                 str = subst(str, home, "~");
990
991         if (str.length() <= threshold)
992                 return lyx::from_utf8(os::external_path(str));
993
994         string const prefix = ".../";
995         string temp;
996
997         while (str.length() > threshold)
998                 str = split(str, temp, '/');
999
1000         // Did we shorten everything away?
1001         if (str.empty()) {
1002                 // Yes, filename itself is too long.
1003                 // Pick the start and the end of the filename.
1004                 str = onlyFilename(path);
1005                 string const head = str.substr(0, threshold / 2 - 3);
1006
1007                 string::size_type len = str.length();
1008                 string const tail =
1009                         str.substr(len - threshold / 2 - 2, len - 1);
1010                 str = head + "..." + tail;
1011         }
1012
1013         return lyx::from_utf8(os::external_path(prefix + str));
1014 }
1015
1016
1017 bool readLink(string const & file, string & link, bool resolve)
1018 {
1019 #ifdef HAVE_READLINK
1020         char linkbuffer[512];
1021         // Should be PATH_MAX but that needs autconf support
1022         int const nRead = ::readlink(file.c_str(),
1023                                      linkbuffer, sizeof(linkbuffer) - 1);
1024         if (nRead <= 0)
1025                 return false;
1026         linkbuffer[nRead] = '\0'; // terminator
1027         if (resolve)
1028                 link = makeAbsPath(linkbuffer, onlyPath(file));
1029         else
1030                 link = linkbuffer;
1031         return true;
1032 #else
1033         return false;
1034 #endif
1035 }
1036
1037
1038 cmd_ret const runCommand(string const & cmd)
1039 {
1040         // FIXME: replace all calls to RunCommand with ForkedCall
1041         // (if the output is not needed) or the code in ispell.C
1042         // (if the output is needed).
1043
1044         // One question is if we should use popen or
1045         // create our own popen based on fork, exec, pipe
1046         // of course the best would be to have a
1047         // pstream (process stream), with the
1048         // variants ipstream, opstream
1049
1050 #if defined (HAVE_POPEN)
1051         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1052 #elif defined (HAVE__POPEN)
1053         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1054 #else
1055 #error No popen() function.
1056 #endif
1057
1058         // (Claus Hentschel) Check if popen was succesful ;-)
1059         if (!inf) {
1060                 lyxerr << "RunCommand:: could not start child process" << endl;
1061                 return make_pair(-1, string());
1062         }
1063
1064         string ret;
1065         int c = fgetc(inf);
1066         while (c != EOF) {
1067                 ret += static_cast<char>(c);
1068                 c = fgetc(inf);
1069         }
1070
1071 #if defined (HAVE_PCLOSE)
1072         int const pret = pclose(inf);
1073 #elif defined (HAVE__PCLOSE)
1074         int const pret = _pclose(inf);
1075 #else
1076 #error No pclose() function.
1077 #endif
1078
1079         if (pret == -1)
1080                 perror("RunCommand:: could not terminate child process");
1081
1082         return make_pair(pret, ret);
1083 }
1084
1085
1086 FileName const findtexfile(string const & fil, string const & /*format*/)
1087 {
1088         /* There is no problem to extend this function too use other
1089            methods to look for files. It could be setup to look
1090            in environment paths and also if wanted as a last resort
1091            to a recursive find. One of the easier extensions would
1092            perhaps be to use the LyX file lookup methods. But! I am
1093            going to implement this until I see some demand for it.
1094            Lgb
1095         */
1096
1097         // If the file can be found directly, we just return a
1098         // absolute path version of it.
1099         FileName const absfile(makeAbsPath(fil));
1100         if (fs::exists(absfile.toFilesystemEncoding()))
1101                 return absfile;
1102
1103         // No we try to find it using kpsewhich.
1104         // It seems from the kpsewhich manual page that it is safe to use
1105         // kpsewhich without --format: "When the --format option is not
1106         // given, the search path used when looking for a file is inferred
1107         // from the name given, by looking for a known extension. If no
1108         // known extension is found, the search path for TeX source files
1109         // is used."
1110         // However, we want to take advantage of the format sine almost all
1111         // the different formats has environment variables that can be used
1112         // to controll which paths to search. f.ex. bib looks in
1113         // BIBINPUTS and TEXBIB. Small list follows:
1114         // bib - BIBINPUTS, TEXBIB
1115         // bst - BSTINPUTS
1116         // graphic/figure - TEXPICTS, TEXINPUTS
1117         // ist - TEXINDEXSTYLE, INDEXSTYLE
1118         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1119         // tex - TEXINPUTS
1120         // tfm - TFMFONTS, TEXFONTS
1121         // This means that to use kpsewhich in the best possible way we
1122         // should help it by setting additional path in the approp. envir.var.
1123         string const kpsecmd = "kpsewhich " + fil;
1124
1125         cmd_ret const c = runCommand(kpsecmd);
1126
1127         lyxerr[Debug::LATEX] << "kpse status = " << c.first << '\n'
1128                  << "kpse result = `" << rtrim(c.second, "\n")
1129                  << '\'' << endl;
1130         if (c.first != -1)
1131                 return FileName(os::internal_path(rtrim(c.second, "\n\r")));
1132         else
1133                 return FileName();
1134 }
1135
1136
1137 void removeAutosaveFile(string const & filename)
1138 {
1139         string a = onlyPath(filename);
1140         a += '#';
1141         a += onlyFilename(filename);
1142         a += '#';
1143         FileName const autosave(a);
1144         if (fs::exists(autosave.toFilesystemEncoding()))
1145                 unlink(autosave);
1146 }
1147
1148
1149 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
1150         string const & message)
1151 {
1152         lyxerr[Debug::GRAPHICS] << "[readBB_from_PSFile] "
1153                 << message << std::endl;
1154 #ifdef WITH_WARNINGS
1155 #warning Why is this func deleting a file? (Lgb)
1156 #endif
1157         if (zipped)
1158                 unlink(file);
1159 }
1160
1161
1162 string const readBB_from_PSFile(FileName const & file)
1163 {
1164         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1165         // It seems that every command in the header has an own line,
1166         // getline() should work for all files.
1167         // On the other hand some plot programs write the bb at the
1168         // end of the file. Than we have in the header:
1169         // %%BoundingBox: (atend)
1170         // In this case we must check the end.
1171         bool zipped = zippedFile(file);
1172         FileName const file_ = zipped ? unzipFile(file) : file;
1173         string const format = getFormatFromContents(file_);
1174
1175         if (format != "eps" && format != "ps") {
1176                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1177                 return string();
1178         }
1179
1180         static boost::regex bbox_re(
1181                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1182         std::ifstream is(file_.toFilesystemEncoding().c_str());
1183         while (is) {
1184                 string s;
1185                 getline(is,s);
1186                 boost::smatch what;
1187                 if (regex_match(s, what, bbox_re)) {
1188                         // Our callers expect the tokens in the string
1189                         // separated by single spaces.
1190                         // FIXME: change return type from string to something
1191                         // sensible
1192                         ostringstream os;
1193                         os << what.str(1) << ' ' << what.str(2) << ' '
1194                            << what.str(3) << ' ' << what.str(4);
1195                         string const bb = os.str();
1196                         readBB_lyxerrMessage(file_, zipped, bb);
1197                         return bb;
1198                 }
1199         }
1200         readBB_lyxerrMessage(file_, zipped, "no bb found");
1201         return string();
1202 }
1203
1204
1205 int compare_timestamps(FileName const & filename1, FileName const & filename2)
1206 {
1207         // If the original is newer than the copy, then copy the original
1208         // to the new directory.
1209
1210         string const file1 = filename1.toFilesystemEncoding();
1211         string const file2 = filename2.toFilesystemEncoding();
1212         int cmp = 0;
1213         if (fs::exists(file1) && fs::exists(file2)) {
1214                 double const tmp = difftime(fs::last_write_time(file1),
1215                                             fs::last_write_time(file2));
1216                 if (tmp != 0)
1217                         cmp = tmp > 0 ? 1 : -1;
1218
1219         } else if (fs::exists(file1)) {
1220                 cmp = 1;
1221         } else if (fs::exists(file2)) {
1222                 cmp = -1;
1223         }
1224
1225         return cmp;
1226 }
1227
1228 } //namespace support
1229 } // namespace lyx