Enrico (and anyone else who is interested), Can you try one and let me know if it works properly on Windows? I believe it should, even with the broken htlatex. The idea is to copy whatever files we need to the temporary conversion directory. Then we can do everything without paths.
Richard -- ================================================================== Richard G Heck, Jr Professor of Philosophy Brown University http://frege.brown.edu/heck/ ================================================================== Get my public key from http://sks.keyserver.penguin.de Hash: 0x1DE91F1E66FFBDEC Learn how to sign your email using Thunderbird and GnuPG at: http://dudu.dyn.2-h.org/nist/gpg-enigmail-howto
Index: lib/configure.py =================================================================== --- lib/configure.py (revision 18454) +++ lib/configure.py (working copy) @@ -348,7 +348,7 @@ rc_entry = [ r'\converter word latex "%%" ""' ]) # checkProg('a LaTeX -> MS Word converter', ["htlatex $$i 'html,word' 'symbol/!' '-cvalidate'"], - rc_entry = [ r'\converter latex wordhtml "%%" "originaldir,needaux"' ]) + rc_entry = [ r'\converter latex wordhtml "%%" "usetempdir,needaux"' ]) # checkProg('an OpenOffice.org -> LaTeX converter', ['w2l -clean $$i'], rc_entry = [ r'\converter sxw latex "%%" ""' ]) @@ -359,10 +359,6 @@ checkProg('a LaTeX -> Open Document converter', ['oolatex $$i', 'oolatex.sh $$i'], rc_entry = [ r'\converter latex odt "%%" "latex"' ]) # - #FIXME Looking for the commands needed to make oolatex output sxw instad of odt... - #checkProg('a LaTeX -> OpenOffice.org (sxw) converter', ['oolatex $$i', 'oolatex.sh $$i'], - # rc_entry = [ r'\converter latex odt "%%" "latex"' ]) - # On windows it is called latex2rt.exe checkProg('a LaTeX -> RTF converter', ['latex2rtf -p -S -o $$o $$i', 'latex2rt -p -S -o $$o $$i'], rc_entry = [ r'\converter latex rtf "%%" "needaux"' ]) # @@ -431,7 +427,7 @@ # checkProg('a LaTeX -> HTML converter', ['htlatex $$i', 'tth -t -e2 -L$$b < $$i > $$o', \ 'latex2html -no_subdir -split 0 -show_section_numbers $$i', 'hevea -s $$i'], - rc_entry = [ r'\converter latex html "%%" "originaldir,needaux"' ]) + rc_entry = [ r'\converter latex html "%%" "usetempdir,needaux"' ]) # path, lilypond = checkProg('a LilyPond -> EPS/PDF/PNG converter', ['lilypond']) if (lilypond != ''): Index: src/Converter.h =================================================================== --- src/Converter.h (revision 18461) +++ src/Converter.h (working copy) @@ -55,10 +55,11 @@ bool latex; /// The converter is xml bool xml; - /// Do we need to run the converter in the original directory? - bool original_dir; /// This converter needs the .aux files bool need_aux; + /// This converter should put its files in a separate temporary + /// directory + bool use_temp_dir; /// If the converter put the result in a directory, then result_dir /// is the name of the directory std::string result_dir; @@ -123,6 +124,14 @@ support::FileName const & orig_from, std::string const & from_format, std::string const & to_format, ErrorList & errorList, int conversionflags = none); + /// used_tmp_dir is a flag that signals whether our output files were put into + /// a temporary directory. Note also that to_file will be changed so that it points + /// at the converted file in this case. + bool convert(Buffer const * buffer, + support::FileName const & from_file, support::FileName & to_file, + support::FileName const & orig_from, bool & used_temp_dir, + std::string const & from_format, std::string const & to_format, + ErrorList & errorList, int conversionflags = none); /// void update(Formats const & formats); /// Index: src/Converter.cpp =================================================================== --- src/Converter.cpp (revision 18461) +++ src/Converter.cpp (working copy) @@ -31,6 +31,7 @@ #include "support/Path.h" #include "support/Systemcall.h" +#include <boost/filesystem/operations.hpp> namespace lyx { @@ -63,8 +64,8 @@ using std::distance; namespace Alert = lyx::frontend::Alert; +namespace fs = boost::filesystem; - namespace { string const token_from("$$i"); @@ -118,7 +119,8 @@ string const & c, string const & l) : from(f), to(t), command(c), flags(l), From(0), To(0), latex(false), xml(false), - original_dir(false), need_aux(false) + need_aux(false), + use_temp_dir(false) {} @@ -133,8 +135,8 @@ latex = true; else if (flag_name == "xml") xml = true; - else if (flag_name == "originaldir") - original_dir = true; + else if (flag_name == "usetempdir") + use_temp_dir = true; else if (flag_name == "needaux") need_aux = true; else if (flag_name == "resultdir") @@ -145,6 +147,10 @@ else if (flag_name == "parselog") parselog = flag_value; } + if (!result_dir.empty() && use_temp_dir) { + lyxerr << "Cannot set resultdir and usetempdir together." << endl; + use_temp_dir = false; + } if (!result_dir.empty() && result_file.empty()) result_file = "index." + formats.extension(to); //if (!contains(command, token_from)) @@ -287,21 +293,57 @@ } +namespace { + // A little helper for what follows + // Throws fs::filesystem_error + void forceSymlink(string const & target, string const & link) + { + if (fs::exists(link)) + fs::remove(link); +#ifdef _WIN32 + fs::copy_file(target, link); +#else + fs::create_symlink(target, link); +#endif + } +} + + bool Converters::convert(Buffer const * buffer, FileName const & from_file, FileName const & to_file, FileName const & orig_from, string const & from_format, string const & to_format, ErrorList & errorList, int conversionflags) { + FileName tmp_to_file = to_file; + bool trash; + return convert(buffer, from_file, tmp_to_file, orig_from, trash, + from_format, to_format, errorList, conversionflags); +} + + +//FIXME Georg suggested that the converter should ALWAYS determine the +//name of the converted file. So this should return a FileName, and it +//can return an empty one on failure. The used_temp_dir flag may still +//need to be set, though, unless we return a pair<FileName, bool> or +//something of that sort. +bool Converters::convert(Buffer const * buffer, + FileName const & from_file, FileName & to_file, + FileName const & orig_from, bool & used_temp_dir, + string const & from_format, string const & to_format, + ErrorList & errorList, int conversionflags) +{ if (from_format == to_format) return move(from_format, from_file, to_file, false); - if ((conversionflags & try_cache) && - ConverterCache::get().inCache(orig_from, to_format)) - return ConverterCache::get().copy(orig_from, to_format, to_file); + used_temp_dir = false; Graph::EdgePath edgepath = getPath(from_format, to_format); if (edgepath.empty()) { + if ((conversionflags & try_cache) && + ConverterCache::get().inCache(orig_from, to_format) + ) + return ConverterCache::get().copy(orig_from, to_format, to_file); if (conversionflags & try_default) { // if no special converter defined, then we take the // default one from ImageMagic. @@ -336,17 +378,36 @@ from_ascii(from_format), from_ascii(to_format))); return false; } + + + ////////////////////////////////////////////////////////////// + // At this point, we have a non-empty EdgePath (i.e., sequence + // of converters) to run. - // buffer is only invalid for importing, and then runparams is not + // set lastConv to the last converter in the chain + Converter const & lastConv = converterlist_[edgepath.back()]; + + //FIXME As things presently are, the cache routines aren't set up to + //cache more than one file. So we have to skip this when we're using + //the flags that indicate we're probably dealing with more than one + //file. For then only the main result file and none of the supporting + //files (eg, the CSS file in the case of htlatex) would be cached. + if ((conversionflags & try_cache) && + ConverterCache::get().inCache(orig_from, to_format) && + !lastConv.use_temp_dir + ) + return ConverterCache::get().copy(orig_from, to_format, to_file); + + // buffer is invalid if we're importing, and then runparams is not // used anyway. OutputParams runparams(buffer ? &buffer->params().encoding() : 0); runparams.flavor = getFlavor(edgepath); // Some converters (e.g. lilypond) can only output files to the - // current directory, so we need to change the current directory. - // This has the added benefit that all other files that may be - // generated by the converter are deleted when LyX closes and do not - // clutter the real working directory. + // current directory, so we need to change the current directory + // to LyX' temporary directory. This has the added benefit that all + // other files that may be generated by the converter are deleted + // when LyX closes and do not clutter the real working directory. string const path(onlyPath(from_file.absFilename())); // Prevent the compiler from optimizing away p FileName pp(path); @@ -355,11 +416,12 @@ // empty the error list before any new conversion takes place. errorList.clear(); - bool run_latex = false; + bool ran_latex = false; string from_base = changeExtension(from_file.absFilename(), ""); string to_base = changeExtension(to_file.absFilename(), ""); FileName infile; FileName outfile = from_file; + for (Graph::EdgePath::const_iterator cit = edgepath.begin(); cit != edgepath.end(); ++cit) { Converter const & conv = converterlist_[*cit]; @@ -367,134 +429,198 @@ if (!dummy) LYXERR(Debug::FILES) << "Converting from " << conv.from << " to " << conv.to << endl; + + //input file for this converter is output file from last one infile = outfile; - outfile = FileName(conv.result_dir.empty() - ? changeExtension(from_file.absFilename(), conv.To->extension()) - : addName(subst(conv.result_dir, - token_base, from_base), - subst(conv.result_file, - token_base, onlyFilename(from_base)))); + //set output filename + string temp_dir; + if (conv.result_dir.empty()) { + if (conv.use_temp_dir) { + //outfile = LYXTMPDIR/basename.format.conversion/basename.format + string const basename = + changeExtension(onlyFilename(outfile.toFilesystemEncoding()), ""); + temp_dir = addName(buffer->temppath(), + basename + "." + to_format + ".conversion"); + outfile = FileName(addName(temp_dir, + basename + "." + to_format)); + } else + outfile = FileName(changeExtension(from_file.absFilename(), + conv.To->extension())); + } + else + outfile = + FileName(addName(subst(conv.result_dir, token_base, from_base), + subst(conv.result_file, token_base, + onlyFilename(from_base)))); // if input and output files are equal, we use a // temporary file as intermediary (JMarc) + bool const needTempFile = (outfile == infile); FileName real_outfile; - if (outfile == infile) { + if (needTempFile) { real_outfile = infile; outfile = FileName(addName(buffer->temppath(), "tmpfile.out")); - } - + } + + //if this is a latex converter, run it if (conv.latex) { - run_latex = true; + ran_latex = true; string const command = subst(conv.command, token_from, ""); LYXERR(Debug::FILES) << "Running " << command << endl; if (!runLaTeX(*buffer, command, runparams, errorList)) return false; - } else { - if (conv.need_aux && !run_latex - && !latex_command_.empty()) { - LYXERR(Debug::FILES) - << "Running " << latex_command_ - << " to update aux file"<< endl; - runLaTeX(*buffer, latex_command_, runparams, errorList); - } + continue; + } + + //run latex if we need to generate the aux file and latex hasn't + //been run yet + if (conv.need_aux && !ran_latex + && !latex_command_.empty()) { + LYXERR(Debug::FILES) + << "Running " << latex_command_ + << " to update aux file"<< endl; + runLaTeX(*buffer, latex_command_, runparams, errorList); + } - // FIXME UNICODE - string const infile2 = (conv.original_dir) - ? infile.absFilename() : to_utf8(makeRelPath(from_utf8(infile.absFilename()), - from_utf8(path))); - string const outfile2 = (conv.original_dir) - ? outfile.absFilename() : to_utf8(makeRelPath(from_utf8(outfile.absFilename()), - from_utf8(path))); + // FIXME UNICODE + string const infile2 = (conv.use_temp_dir) + ? onlyFilename(infile.absFilename()) : + to_utf8(makeRelPath(from_utf8(infile.absFilename()), + from_utf8(path))); + string const outfile2 = (conv.use_temp_dir) + ? onlyFilename(outfile.absFilename()) : + to_utf8(makeRelPath(from_utf8(outfile.absFilename()), + from_utf8(path))); - string command = conv.command; - command = subst(command, token_from, quoteName(infile2)); - command = subst(command, token_base, quoteName(from_base)); - command = subst(command, token_to, quoteName(outfile2)); - command = libScriptSearch(command); + // do the substitutions into the command given in the converter + // definition + string command = conv.command; + command = subst(command, token_from, quoteName(infile2)); + command = subst(command, token_base, quoteName(from_base)); + command = subst(command, token_to, quoteName(outfile2)); + command = libScriptSearch (command); - if (!conv.parselog.empty()) - command += " 2> " + quoteName(infile2 + ".out"); + if (!conv.parselog.empty()) + command += " 2> " + quoteName(infile2 + ".out"); - if (conv.from == "dvi" && conv.to == "ps") - command = add_options(command, - buffer->params().dvips_options()); - else if (conv.from == "dvi" && prefixIs(conv.to, "pdf")) - command = add_options(command, - dvipdfm_options(buffer->params())); + if (conv.from == "dvi" && conv.to == "ps") + command = add_options(command, + buffer->params().dvips_options()); + else if (conv.from == "dvi" && prefixIs(conv.to, "pdf")) + command = add_options(command, + dvipdfm_options(buffer->params())); - LYXERR(Debug::FILES) << "Calling " << command << endl; - if (buffer) - buffer->message(_("Executing command: ") - + from_utf8(command)); + LYXERR(Debug::FILES) << "Calling " << command << endl; + if (buffer) + buffer->message(_("Executing command: ") + from_utf8(command)); - Systemcall::Starttype const type = (dummy) - ? Systemcall::DontWait : Systemcall::Wait; - Systemcall one; - int res; - if (conv.original_dir) { - FileName path(buffer->filePath()); - support::Path p(path); - res = one.startscript(type, - to_filesystem8bit(from_utf8(command))); - } else - res = one.startscript(type, - to_filesystem8bit(from_utf8(command))); + Systemcall::Starttype const type = (dummy) + ? Systemcall::DontWait : Systemcall::Wait; + Systemcall one; - if (!real_outfile.empty()) { - Mover const & mover = getMover(conv.to); - if (!mover.rename(outfile, real_outfile)) - res = -1; - else - LYXERR(Debug::FILES) - << "renaming file " << outfile - << " to " << real_outfile - << endl; - // Finally, don't forget to tell any future - // converters to use the renamed file... - outfile = real_outfile; + int res; + if (conv.use_temp_dir) { + FileName path(temp_dir); + //create the temporary directory if necessary + if (!isDirWriteable(path) && !createDirectory(path, 0777)) { + Alert::error(_("Directory error"), + _("Unable to create temporary directory.")); + return false; } - - if (!conv.parselog.empty()) { - string const logfile = infile2 + ".log"; - string const script = libScriptSearch(conv.parselog); - string const command2 = script + - " < " + quoteName(infile2 + ".out") + - " > " + quoteName(logfile); - one.startscript(Systemcall::Wait, - to_filesystem8bit(from_utf8(command2))); - if (!scanLog(*buffer, command, makeAbsPath(logfile, path), errorList)) - return false; + //switch into it + support::Path p(path); + //create a symlink to the input file... + try { + forceSymlink(infile.absFilename(), infile2); + string const auxname = + changeExtension(buffer->getLatexName(true), "aux"); + string const bblname = + changeExtension(buffer->getLatexName(true), "bbl"); + if (conv.need_aux) { + //and to the aux and bbl files if needed + forceSymlink(addName(buffer->temppath(), auxname), auxname); + if (fs::exists(addName(buffer->temppath(), bblname))) + forceSymlink(addName(buffer->temppath(), bblname), bblname); + } + } catch (fs::filesystem_error & e) { + Alert::error(_("File system error"), + _("Unable to create temporary file.")); + return false; } + res = one.startscript(type, + to_filesystem8bit(from_utf8(command))); + // try to clean up the symlinks...not a disaster if there's + // an error. + try { + fs::remove(infile2); + if (conv.need_aux) { + fs::remove(auxname); + if (fs::exists(bblname)) + fs::remove(bblname); + } catch (fs::filesystem_error & e) {} + } else + res = one.startscript(type, + to_filesystem8bit(from_utf8(command))); - if (res) { - if (conv.to == "program") { - Alert::error(_("Build errors"), - _("There were errors during the build process.")); - } else { + if (needTempFile) { + // move temporary file to requested location + Mover const & mover = getMover(conv.to); + if (!mover.rename(outfile, real_outfile)) + res = -1; + else + LYXERR(Debug::FILES) + << "renaming file " << outfile + << " to " << real_outfile + << endl; + // make sure the next converter knows what file to use + outfile = real_outfile; + } else { + //check if the output file exists + if(!isFileReadable(outfile)) + res = -1; + } + + if (!conv.parselog.empty()) { + string const logfile = infile2 + ".log"; + string const script = libScriptSearch(conv.parselog); + string const command2 = script + + " < " + quoteName(infile2 + ".out") + + " > " + quoteName(logfile); + one.startscript(Systemcall::Wait, + to_filesystem8bit(from_utf8(command2))); + if (!scanLog(*buffer, command, makeAbsPath(logfile, path), errorList)) + return false; + } + + if (res) { + if (conv.to == "program") { + Alert::error(_("Build errors"), + _("There were errors during the build process.")); + } else { // FIXME: this should go out of here. For example, here we cannot say if // it is a document (.lyx) or something else. Same goes for elsewhere. - Alert::error(_("Cannot convert file"), - bformat(_("An error occurred whilst running %1$s"), - from_utf8(command.substr(0, 50)))); - } - return false; + Alert::error(_("Cannot convert file"), + bformat(_("An error occurred whilst running %1$s"), + from_utf8(command.substr(0, 50)))); } + return false; } + //this converter is now complete } + // all the converters have now been run - Converter const & conv = converterlist_[edgepath.back()]; - if (conv.To->dummy()) + if (lastConv.To->dummy()) return true; - if (!conv.result_dir.empty()) { + if (!lastConv.result_dir.empty()) { // The converter has put the file(s) in a directory. // In this case we ignore the given to_file. if (from_base != to_base) { - string const from = subst(conv.result_dir, + string const from = subst(lastConv.result_dir, token_base, from_base); - string const to = subst(conv.result_dir, + string const to = subst(lastConv.result_dir, token_base, to_base); - Mover const & mover = getMover(conv.from); + Mover const & mover = getMover(lastConv.from); if (!mover.rename(FileName(from), FileName(to))) { Alert::error(_("Cannot convert file"), bformat(_("Could not move a temporary directory from %1$s to %2$s."), @@ -503,11 +629,19 @@ } } return true; - } else { - if (conversionflags & try_cache) - ConverterCache::get().add(orig_from, to_format, outfile); - return move(conv.to, outfile, to_file, conv.latex); } + // else... + // if the last converter created its own temporary directory, then + // the files can stay there. + if (lastConv.use_temp_dir) { + to_file = outfile; + used_temp_dir = true; + return true; + } // note that we skip the cache in this case + // else... + if (conversionflags & try_cache) + ConverterCache::get().add(orig_from, to_format, outfile); + return move(lastConv.to, outfile, to_file, lastConv.latex); } Index: src/Exporter.cpp =================================================================== --- src/Exporter.cpp (revision 18461) +++ src/Exporter.cpp (working copy) @@ -91,6 +91,21 @@ } +/// ask for permission to over-write files in an export directory +int checkDirOverwrite(FileName const & filename) +{ + if (fs::exists(filename.toFilesystemEncoding())) { + docstring text = bformat(_("The export directory %1$s already exists.\n\n" + "Do you want to over-write files in it?"), + makeDisplayPath(filename.absFilename())); + return Alert::prompt(_("Over-write?"), + text, 0, 1, + _("&Over-write"), _("&Cancel export")); + } + return 0; +} + + enum CopyStatus { SUCCESS, FORCE, @@ -203,7 +218,8 @@ } else if (!lyxrc.tex_allows_spaces && contains(buffer->filePath(), ' ')) { Alert::error(_("File name error"), - _("The directory path to the document cannot contain spaces.")); + _("The directory path to the document cannot contain spaces, " + "since your TeX installation does not allow them.")); return false; } else { runparams.nice = false; @@ -213,19 +229,58 @@ string const error_type = (format == "program")? "Build" : bufferFormat(*buffer); string const ext = formats.extension(format); - FileName const tmp_result_file(changeExtension(filename, ext)); + bool used_temp_dir = false; + FileName tmp_result_file(changeExtension(filename, ext)); bool const success = theConverters().convert(buffer, FileName(filename), - tmp_result_file, FileName(buffer->fileName()), backend_format, format, - buffer->errorList(error_type)); + tmp_result_file, FileName(buffer->fileName()), used_temp_dir, backend_format, + format, buffer->errorList(error_type)); // Emit the signal to show the error list. if (format != backend_format) buffer->errors(error_type); if (!success) return false; - if (put_in_tempdir) + if (put_in_tempdir) { result_file = tmp_result_file.absFilename(); - else { + } else if (used_temp_dir) { + FileName const temp_dir = + FileName(onlyPath(tmp_result_file.toFilesystemEncoding())); + string const new_dir = + addName(onlyPath(buffer->fileName()), + changeExtension(buffer->getLatexName(true), "") + + "." + format + ".conversion"); + FileName newDir = FileName(new_dir); + + //FIXME Should be put in a copyDirectory() routine. + //That could go in support/fs_extras.cpp. + if (isDirWriteable(newDir) && checkDirOverwrite(newDir)) { + buffer->message(_("Document export cancelled.")); + return false; + } + if (!isDirWriteable(newDir) && !createDirectory(newDir, 0777)) { + Alert::error(_("Directory error"), + _("Unable to create destination directory." + "Output files are in LyX temporary directory.")); + return false; + } + vector<FileName> const files = dirList(temp_dir); + for (vector<FileName>::const_iterator it = files.begin(); + it != files.end(); ++it) + { + string const fromFile = it->absFilename(); + string const toFile = addName(new_dir, onlyFilename(fromFile)); + try { + if (fs::exists(toFile)) + fs::remove(toFile); + fs::copy_file(fromFile, toFile); + } catch (fs::filesystem_error & e) { + Alert::error(_("File copy error"), + _("Unable to copy files. " + "Output files are in LyX temporary directory.")); + return false; + } + } + } else { result_file = changeExtension(buffer->fileName(), ext); // We need to copy referenced files (e. g. included graphics // if format == "dvi") to the result dir. Index: src/insets/InsetGraphics.cpp =================================================================== --- src/insets/InsetGraphics.cpp (revision 18461) +++ src/insets/InsetGraphics.cpp (working copy) @@ -724,6 +724,8 @@ // FIXME (Abdel 12/08/06): Is there a need to show these errors? ErrorList el; + + //FIXME Converter::convert call if (theConverters().convert(&buf, temp_file, to_file, params().filename, from, to, el, Converters::try_default | Converters::try_cache)) { Index: src/insets/ExternalSupport.cpp =================================================================== --- src/insets/ExternalSupport.cpp (revision 18461) +++ src/insets/ExternalSupport.cpp (working copy) @@ -270,6 +270,9 @@ } // the generated file (always in the temp dir) + //FIXME This needs to be changed, since the foregoing is no longer true + //if the usetempdir flag is set. But if we're going to have the converter + //always set the to_file name, then this doesn't even need doing.... string const to_file = doSubstitution(params, buffer, outputFormat.updateResult, false, true); @@ -315,6 +318,7 @@ // FIXME (Abdel 12/08/06): Is there a need to show these errors? ErrorList el; + //FIXME Converter::convert call bool const success = theConverters().convert(&buffer, temp_file, abs_to_file, params.filename, from_format, to_format, el,