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