X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FFormat.cpp;h=485715b6e3b23911d622c31897856467f748bd0e;hb=26910d5ec49395d1372dd5b9259f1bf6ed23de0a;hp=1ae3960163de695bcb9457bf14bec40842ecf342;hpb=dfd8a97bffa96ab4b12abf07f24c371d4b4e3adb;p=lyx.git diff --git a/src/Format.cpp b/src/Format.cpp index 1ae3960163..485715b6e3 100644 --- a/src/Format.cpp +++ b/src/Format.cpp @@ -23,7 +23,7 @@ #include "support/gettext.h" #include "support/lstrings.h" #include "support/os.h" -#include "support/Path.h" +#include "support/PathChanger.h" #include "support/Systemcall.h" #include "support/textutils.h" #include "support/Translator.h" @@ -37,6 +37,10 @@ #include "support/linkback/LinkBackProxy.h" #endif +#ifdef HAVE_MAGIC_H +#include +#endif + using namespace std; using namespace lyx::support; @@ -55,7 +59,8 @@ string const token_socket_format("$$a"); class FormatNamesEqual : public unary_function { public: FormatNamesEqual(string const & name) - : name_(name) {} + : name_(name) + {} bool operator()(Format const & f) const { return f.name() == name_; @@ -68,7 +73,8 @@ private: class FormatExtensionsEqual : public unary_function { public: FormatExtensionsEqual(string const & extension) - : extension_(extension) {} + : extension_(extension) + {} bool operator()(Format const & f) const { return f.hasExtension(extension_); @@ -78,10 +84,27 @@ private: }; +class FormatMimeEqual : public unary_function { +public: + FormatMimeEqual(string const & mime) + : mime_(mime) + {} + bool operator()(Format const & f) const + { + // The test for empty mime strings is needed since we allow + // formats with empty mime types. + return f.mime() == mime_ && !mime_.empty(); + } +private: + string mime_; +}; + + class FormatPrettyNameEqual : public unary_function { public: FormatPrettyNameEqual(string const & prettyname) - : prettyname_(prettyname) {} + : prettyname_(prettyname) + {} bool operator()(Format const & f) const { return f.prettyname() == prettyname_; @@ -92,6 +115,10 @@ private: } //namespace anon +bool Format::formatSorter(Format const * lhs, Format const * rhs) +{ + return _(lhs->prettyname()) < _(rhs->prettyname()); +} bool operator<(Format const & a, Format const & b) { @@ -101,9 +128,9 @@ bool operator<(Format const & a, Format const & b) Format::Format(string const & n, string const & e, string const & p, string const & s, string const & v, string const & ed, - int flags) + string const & m, int flags) : name_(n), prettyname_(p), shortcut_(s), viewer_(v), - editor_(ed), flags_(flags) + editor_(ed), mime_(m), flags_(flags) { extension_list_ = getVectorFromString(e, ","); LYXERR(Debug::GRAPHICS, "New Format: n=" << n << ", flags=" << flags); @@ -163,15 +190,256 @@ Format const * Formats::getFormat(string const & name) const } +namespace { + +/** Guess the file format name (as in Format::name()) from contents. + * Normally you don't want to use this directly, but rather + * Formats::getFormatFromFile(). + */ +string guessFormatFromContents(FileName const & fn) +{ + // the different filetypes and what they contain in one of the first lines + // (dots are any characters). (Herbert 20020131) + // AGR Grace... + // BMP BM... + // EPS %!PS-Adobe-3.0 EPSF... + // FIG #FIG... + // FITS ...BITPIX... + // GIF GIF... + // JPG JFIF + // PDF %PDF-... + // PNG .PNG... + // PBM P1... or P4 (B/W) + // PGM P2... or P5 (Grayscale) + // PPM P3... or P6 (color) + // PS %!PS-Adobe-2.0 or 1.0, no "EPSF"! + // SGI \001\332... (decimal 474) + // TGIF %TGIF... + // TIFF II... or MM... + // XBM ..._bits[]... + // XPM /* XPM */ sometimes missing (f.ex. tgif-export) + // ...static char *... + // XWD \000\000\000\151 (0x00006900) decimal 105 + // + // GZIP \037\213 http://www.ietf.org/rfc/rfc1952.txt + // ZIP PK... http://www.halyava.ru/document/ind_arch.htm + // Z \037\235 UNIX compress + + // paranoia check + if (fn.empty() || !fn.isReadableFile()) + return string(); + + ifstream ifs(fn.toFilesystemEncoding().c_str()); + if (!ifs) + // Couldn't open file... + return string(); + + // gnuzip + static string const gzipStamp = "\037\213"; + + // PKZIP + static string const zipStamp = "PK"; + + // ZIP containers (koffice, openoffice.org etc). + static string const nonzipStamp = "\008\0\0\0mimetypeapplication/"; + + // compress + static string const compressStamp = "\037\235"; + + // Maximum strings to read + int const max_count = 50; + int count = 0; + + string str; + string format; + bool firstLine = true; + bool backslash = false; + int dollars = 0; + while ((count++ < max_count) && format.empty()) { + if (ifs.eof()) + break; + + getline(ifs, str); + string const stamp = str.substr(0, 2); + if (firstLine && str.size() >= 2) { + // at first we check for a zipped file, because this + // information is saved in the first bytes of the file! + // also some graphic formats which save the information + // in the first line, too. + if (prefixIs(str, gzipStamp)) { + format = "gzip"; + + } else if (stamp == zipStamp && + !contains(str, nonzipStamp)) { + format = "zip"; + + } else if (stamp == compressStamp) { + format = "compress"; + + // the graphics part + } else if (stamp == "BM") { + format = "bmp"; + + } else if (stamp == "\001\332") { + format = "sgi"; + + // PBM family + // Don't need to use str.at(0), str.at(1) because + // we already know that str.size() >= 2 + } else if (str[0] == 'P') { + switch (str[1]) { + case '1': + case '4': + format = "pbm"; + break; + case '2': + case '5': + format = "pgm"; + break; + case '3': + case '6': + format = "ppm"; + } + break; + + } else if ((stamp == "II") || (stamp == "MM")) { + format = "tiff"; + + } else if (prefixIs(str,"%TGIF")) { + format = "tgif"; + + } else if (prefixIs(str,"#FIG")) { + format = "fig"; + + } else if (prefixIs(str,"GIF")) { + format = "gif"; + + } else if (str.size() > 3) { + int const c = ((str[0] << 24) & (str[1] << 16) & + (str[2] << 8) & str[3]); + if (c == 105) { + format = "xwd"; + } + } + + firstLine = false; + } + + if (!format.empty()) + break; + else if (contains(str,"EPSF")) + // dummy, if we have wrong file description like + // %!PS-Adobe-2.0EPSF" + format = "eps"; + + else if (contains(str, "Grace")) + format = "agr"; + + else if (contains(str, "JFIF")) + format = "jpg"; + + else if (contains(str, "%PDF")) + // autodetect pdf format for graphics inclusion + format = "pdf6"; + + else if (contains(str, "PNG")) + format = "png"; + + else if (contains(str, "%!PS-Adobe")) { + // eps or ps + ifs >> str; + if (contains(str,"EPSF")) + format = "eps"; + else + format = "ps"; + } + + else if (contains(str, "_bits[]")) + format = "xbm"; + + else if (contains(str, "XPM") || contains(str, "static char *")) + format = "xpm"; + + else if (contains(str, "BITPIX")) + format = "fits"; + + else if (contains(str, "\\documentclass") || + contains(str, "\\chapter") || + contains(str, "\\section") || + contains(str, "\\begin") || + contains(str, "\\end") || + contains(str, "$$") || + contains(str, "\\[") || + contains(str, "\\]")) + format = "latex"; + else { + if (contains(str, '\\')) + backslash = true; + dollars += count_char(str, '$'); + } + } + + if (format.empty() && backslash && dollars > 1) + // inline equation + format = "latex"; + + if (format.empty()) { + if (ifs.eof()) + LYXERR(Debug::GRAPHICS, "filetools(getFormatFromContents)\n" + "\tFile type not recognised before EOF!"); + } else { + LYXERR(Debug::GRAPHICS, "Recognised Fileformat: " << format); + return format; + } + + LYXERR(Debug::GRAPHICS, "filetools(getFormatFromContents)\n" + << "\tCouldn't find a known format!"); + return string(); +} + +} + + string Formats::getFormatFromFile(FileName const & filename) const { if (filename.empty()) return string(); - string const format = filename.guessFormatFromContents(); +#ifdef HAVE_MAGIC_H + if (filename.exists()) { + magic_t magic_cookie = magic_open(MAGIC_MIME); + if (magic_cookie) { + string format; + if (magic_load(magic_cookie, NULL) != 0) { + LYXERR(Debug::GRAPHICS, "Formats::getFormatFromFile\n" + << "\tCouldn't load magic database - " + << magic_error(magic_cookie)); + } else { + string mime = magic_file(magic_cookie, + filename.toFilesystemEncoding().c_str()); + mime = token(mime, ';', 0); + // we need our own ps/eps detection + if ((mime != "application/postscript") && (mime != "text/plain")) { + Formats::const_iterator cit = + find_if(formatlist.begin(), formatlist.end(), + FormatMimeEqual(mime)); + if (cit != formats.end()) { + LYXERR(Debug::GRAPHICS, "\tgot format from MIME type: " + << mime << " -> " << cit->name()); + format = cit->name(); + } + } + } + magic_close(magic_cookie); + if (!format.empty()) + return format; + } + } +#endif + + string const format = guessFormatFromContents(filename); string const ext = getExtension(filename.absFileName()); - if ((format == "gzip" || format == "zip" || format == "compress") - && !ext.empty()) { + if (isZippedFileFormat(format) && !ext.empty()) { string const & fmt_name = formats.getFormatFromExtension(ext); if (!fmt_name.empty()) { Format const * p_format = formats.getFormat(fmt_name); @@ -239,11 +507,22 @@ bool Formats::isZippedFile(support::FileName const & filename) const { return it->second.zipped; string const & format = getFormatFromFile(filename); bool zipped = (format == "gzip" || format == "zip"); - zipped_.insert(pair(fname, ZippedInfo(zipped, timestamp))); + zipped_.insert(make_pair(fname, ZippedInfo(zipped, timestamp))); return zipped; } +bool Formats::isZippedFileFormat(string const & format) +{ + return contains("gzip zip compress", format) && !format.empty(); +} + + +bool Formats::isPostScriptFileFormat(string const & format) +{ + return format == "ps" || format == "eps"; +} + static string fixCommand(string const & cmd, string const & ext, os::auto_open_mode mode) { @@ -291,24 +570,24 @@ void Formats::add(string const & name) { if (!getFormat(name)) add(name, name, name, string(), string(), string(), - Format::document); + string(), Format::document); } void Formats::add(string const & name, string const & extensions, string const & prettyname, string const & shortcut, string const & viewer, string const & editor, - int flags) + string const & mime, int flags) { FormatList::iterator it = find_if(formatlist.begin(), formatlist.end(), FormatNamesEqual(name)); if (it == formatlist.end()) formatlist.push_back(Format(name, extensions, prettyname, - shortcut, viewer, editor, flags)); + shortcut, viewer, editor, mime, flags)); else *it = Format(name, extensions, prettyname, shortcut, viewer, - editor, flags); + editor, mime, flags); } @@ -429,7 +708,7 @@ bool Formats::edit(Buffer const & buffer, FileName const & filename, // LinkBack files look like PDF, but have the .linkback extension string const ext = getExtension(filename.absFileName()); - if (format_name == "pdf" && ext == "linkback") { + if (format_name == "pdf6" && ext == "linkback") { #ifdef USE_MACOSX_PACKAGING return editLinkBackFile(filename.absFileName().c_str()); #else @@ -515,8 +794,10 @@ string const Formats::extensions(string const & name) const namespace { + typedef Translator FlavorTranslator; + FlavorTranslator initFlavorTranslator() { FlavorTranslator f(OutputParams::LATEX, "latex"); @@ -536,6 +817,7 @@ FlavorTranslator const & flavorTranslator() static FlavorTranslator translator = initFlavorTranslator(); return translator; } + }