mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-11-28 12:26:59 +00:00
15e1f5eb39
This was not necessary when LyX was generously outputting newlines. As it may happen that the output produced by writeExternal (the result of an external inset) starts right at the beginning of a line, if otexstream does not know that something is already on the line, the iomanip-like variable 'breakln' fails to actually break the line.
543 lines
16 KiB
C++
543 lines
16 KiB
C++
/**
|
|
* \file ExternalSupport.cpp
|
|
* This file is part of LyX, the document processor.
|
|
* Licence details can be found in the file COPYING.
|
|
*
|
|
* \author Asger Alstrup Nielsen
|
|
* \author Angus Leeming
|
|
*
|
|
* Full author contact details are available in file CREDITS.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "ExternalSupport.h"
|
|
#include "ExternalTemplate.h"
|
|
#include "ExternalTransforms.h"
|
|
#include "InsetExternal.h"
|
|
|
|
#include "Buffer.h"
|
|
#include "Converter.h"
|
|
#include "ErrorList.h"
|
|
#include "Exporter.h"
|
|
#include "Format.h"
|
|
#include "Mover.h"
|
|
|
|
#include "frontends/alert.h"
|
|
|
|
#include "support/debug.h"
|
|
#include "support/filetools.h"
|
|
#include "support/gettext.h"
|
|
#include "support/lstrings.h"
|
|
#include "support/lyxalgo.h"
|
|
#include "support/os.h"
|
|
#include "support/Package.h"
|
|
|
|
#include <algorithm>
|
|
|
|
using namespace std;
|
|
using namespace lyx::support;
|
|
|
|
namespace lyx {
|
|
namespace external {
|
|
|
|
Template const * getTemplatePtr(InsetExternalParams const & params)
|
|
{
|
|
TemplateManager const & etm = TemplateManager::get();
|
|
return etm.getTemplateByName(params.templatename());
|
|
}
|
|
|
|
|
|
void editExternal(InsetExternalParams const & params, Buffer const & buffer)
|
|
{
|
|
formats.edit(buffer, params.filename,
|
|
formats.getFormatFromFile(params.filename));
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
string const subst_path(string const & input,
|
|
string const & placeholder,
|
|
string const & path,
|
|
bool use_latex_path,
|
|
latex_path_extension ext = PROTECT_EXTENSION,
|
|
latex_path_dots dots = LEAVE_DOTS)
|
|
{
|
|
if (input.find(placeholder) == string::npos)
|
|
return input;
|
|
// Don't use external_path here when use_latex_path is false, as the
|
|
// path will be compared with another one in internal style later
|
|
// in Converters::move.
|
|
string const path2 = use_latex_path ?
|
|
latex_path(path, ext, dots) : path;
|
|
return subst(input, placeholder, path2);
|
|
}
|
|
|
|
} // namespace anon
|
|
|
|
|
|
string const doSubstitution(InsetExternalParams const & params,
|
|
Buffer const & buffer, string const & s,
|
|
bool use_latex_path,
|
|
bool external_in_tmpdir,
|
|
Substitute what)
|
|
{
|
|
Buffer const * masterBuffer = buffer.masterBuffer();
|
|
string const parentpath = external_in_tmpdir ?
|
|
masterBuffer->temppath() :
|
|
buffer.filePath();
|
|
string const filename = external_in_tmpdir ?
|
|
params.filename.mangledFileName() :
|
|
params.filename.outputFileName(parentpath);
|
|
string const basename = changeExtension(
|
|
onlyFileName(filename), string());
|
|
string const absname = makeAbsPath(filename, parentpath).absFileName();
|
|
|
|
string result = s;
|
|
if (what != ALL_BUT_PATHS) {
|
|
string const filepath = onlyPath(filename);
|
|
string const abspath = onlyPath(absname);
|
|
string const masterpath = external_in_tmpdir ?
|
|
masterBuffer->temppath() :
|
|
masterBuffer->filePath();
|
|
// FIXME UNICODE
|
|
string relToMasterPath = onlyPath(
|
|
to_utf8(makeRelPath(from_utf8(absname),
|
|
from_utf8(masterpath))));
|
|
if (relToMasterPath == "./")
|
|
relToMasterPath.clear();
|
|
// FIXME UNICODE
|
|
string relToParentPath = onlyPath(
|
|
to_utf8(makeRelPath(from_utf8(absname),
|
|
from_utf8(parentpath))));
|
|
if (relToParentPath == "./")
|
|
relToParentPath.clear();
|
|
|
|
result = subst_path(result, "$$FPath", filepath,
|
|
use_latex_path,
|
|
PROTECT_EXTENSION,
|
|
ESCAPE_DOTS);
|
|
result = subst_path(result, "$$AbsPath", abspath,
|
|
use_latex_path,
|
|
PROTECT_EXTENSION,
|
|
ESCAPE_DOTS);
|
|
result = subst_path(result, "$$RelPathMaster",
|
|
relToMasterPath, use_latex_path,
|
|
PROTECT_EXTENSION,
|
|
ESCAPE_DOTS);
|
|
result = subst_path(result, "$$RelPathParent",
|
|
relToParentPath, use_latex_path,
|
|
PROTECT_EXTENSION,
|
|
ESCAPE_DOTS);
|
|
if (FileName::isAbsolute(filename)) {
|
|
result = subst_path(result, "$$AbsOrRelPathMaster",
|
|
abspath, use_latex_path,
|
|
PROTECT_EXTENSION,
|
|
ESCAPE_DOTS);
|
|
result = subst_path(result, "$$AbsOrRelPathParent",
|
|
abspath, use_latex_path,
|
|
PROTECT_EXTENSION,
|
|
ESCAPE_DOTS);
|
|
} else {
|
|
result = subst_path(result, "$$AbsOrRelPathMaster",
|
|
relToMasterPath, use_latex_path,
|
|
PROTECT_EXTENSION,
|
|
ESCAPE_DOTS);
|
|
result = subst_path(result, "$$AbsOrRelPathParent",
|
|
relToParentPath, use_latex_path,
|
|
PROTECT_EXTENSION,
|
|
ESCAPE_DOTS);
|
|
}
|
|
}
|
|
|
|
if (what == PATHS)
|
|
return result;
|
|
|
|
result = subst_path(result, "$$FName", filename, use_latex_path,
|
|
EXCLUDE_EXTENSION);
|
|
result = subst_path(result, "$$Basename", basename, use_latex_path,
|
|
PROTECT_EXTENSION, ESCAPE_DOTS);
|
|
result = subst_path(result, "$$Extension",
|
|
'.' + getExtension(filename), use_latex_path);
|
|
result = subst_path(result, "$$Tempname", params.tempname().absFileName(), use_latex_path);
|
|
result = subst_path(result, "$$Sysdir",
|
|
package().system_support().absFileName(), use_latex_path);
|
|
|
|
// Handle the $$Contents(filename) syntax
|
|
if (contains(result, "$$Contents(\"")) {
|
|
// Since use_latex_path may be true we must extract the file
|
|
// name from s instead of result and do the substitutions
|
|
// again, this time with use_latex_path false.
|
|
size_t const spos = s.find("$$Contents(\"");
|
|
size_t const send = s.find("\")", spos);
|
|
string const file_template = s.substr(spos + 12, send - (spos + 12));
|
|
string const file = doSubstitution(params, buffer,
|
|
file_template, false,
|
|
external_in_tmpdir, what);
|
|
string contents;
|
|
|
|
FileName const absfile(
|
|
makeAbsPath(file, masterBuffer->temppath()));
|
|
if (absfile.isReadableFile())
|
|
// FIXME UNICODE
|
|
contents = to_utf8(absfile.fileContents("UTF-8"));
|
|
|
|
size_t const pos = result.find("$$Contents(\"");
|
|
size_t const end = result.find("\")", pos);
|
|
result.replace(pos, end + 2, contents);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
/** update the file represented by the template.
|
|
If \p external_in_tmpdir == true, then the generated file is
|
|
placed in the buffer's temporary directory.
|
|
*/
|
|
void updateExternal(InsetExternalParams const & params,
|
|
string const & format,
|
|
Buffer const & buffer,
|
|
ExportData & exportdata,
|
|
bool external_in_tmpdir,
|
|
bool dryrun)
|
|
{
|
|
Template const * const et_ptr = getTemplatePtr(params);
|
|
if (!et_ptr)
|
|
return; // FAILURE
|
|
Template const & et = *et_ptr;
|
|
|
|
if (!et.automaticProduction)
|
|
return; // NOT_NEEDED
|
|
|
|
Template::Formats::const_iterator cit = et.formats.find(format);
|
|
if (cit == et.formats.end())
|
|
return; // FAILURE
|
|
|
|
Template::Format const & outputFormat = cit->second;
|
|
if (outputFormat.updateResult.empty())
|
|
return; // NOT_NEEDED
|
|
|
|
string from_format = et.inputFormat;
|
|
if (from_format.empty())
|
|
return; // NOT_NEEDED
|
|
|
|
if (from_format == "*") {
|
|
if (params.filename.empty())
|
|
return; // NOT_NEEDED
|
|
|
|
// Try and ascertain the file format from its contents.
|
|
from_format = formats.getFormatFromFile(params.filename);
|
|
if (from_format.empty())
|
|
return; // FAILURE
|
|
}
|
|
|
|
string const to_format = outputFormat.updateFormat;
|
|
if (to_format.empty())
|
|
return; // NOT_NEEDED
|
|
|
|
// The master buffer. This is useful when there are multiple levels
|
|
// of include files
|
|
Buffer const * masterBuffer = buffer.masterBuffer();
|
|
|
|
// We copy the source file to the temp dir and do the conversion
|
|
// there if necessary
|
|
FileName const temp_file(
|
|
makeAbsPath(params.filename.mangledFileName(),
|
|
masterBuffer->temppath()));
|
|
if (!params.filename.empty() && !params.filename.isDirectory()) {
|
|
unsigned long const from_checksum = params.filename.checksum();
|
|
unsigned long const temp_checksum = temp_file.checksum();
|
|
|
|
if (from_checksum != temp_checksum) {
|
|
Mover const & mover = getMover(from_format);
|
|
if (!mover.copy(params.filename, temp_file)) {
|
|
LYXERR(Debug::EXTERNAL, "external::updateExternal. "
|
|
<< "Unable to copy " << params.filename << " to " << temp_file);
|
|
return; // FAILURE
|
|
}
|
|
}
|
|
}
|
|
|
|
// the generated file (always in the temp dir)
|
|
string const to_file = doSubstitution(params, buffer,
|
|
outputFormat.updateResult,
|
|
false, true);
|
|
FileName const abs_to_file(
|
|
makeAbsPath(to_file, masterBuffer->temppath()));
|
|
|
|
if (!dryrun) {
|
|
// Record the referenced files for the exporter.
|
|
// The exporter will copy them to the export dir.
|
|
typedef Template::Format::FileMap FileMap;
|
|
FileMap::const_iterator rit = outputFormat.referencedFiles.begin();
|
|
FileMap::const_iterator rend = outputFormat.referencedFiles.end();
|
|
for (; rit != rend; ++rit) {
|
|
vector<string>::const_iterator fit = rit->second.begin();
|
|
vector<string>::const_iterator fend = rit->second.end();
|
|
for (; fit != fend; ++fit) {
|
|
FileName const source(makeAbsPath(
|
|
doSubstitution(params, buffer, *fit,
|
|
false, true),
|
|
masterBuffer->temppath()));
|
|
// The path of the referenced file is never the
|
|
// temp path, but the filename may be the mangled
|
|
// or the real name. Therefore we substitute the
|
|
// paths and names separately.
|
|
string file = subst(*fit, "$$FName",
|
|
"$$FPath$$Basename$$Extension");
|
|
file = doSubstitution(params, buffer, file, false, false,
|
|
PATHS);
|
|
file = doSubstitution(params, buffer, file,
|
|
false, external_in_tmpdir,
|
|
ALL_BUT_PATHS);
|
|
// if file is a relative name, it is interpreted
|
|
// relative to the master document.
|
|
if (makeAbsPath(file, masterBuffer->filePath()) !=
|
|
params.filename.absFileName())
|
|
exportdata.addExternalFile(rit->first, source, file);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do we need to perform the conversion?
|
|
// Yes if to_file does not exist or if from_file is newer than to_file
|
|
if (compare_timestamps(temp_file, abs_to_file) < 0)
|
|
return; // SUCCESS
|
|
|
|
// FIXME (Abdel 12/08/06): Is there a need to show these errors?
|
|
ErrorList el;
|
|
bool const success =
|
|
theConverters().convert(&buffer, temp_file, abs_to_file,
|
|
params.filename, from_format, to_format, el,
|
|
Converters::try_default | Converters::try_cache);
|
|
|
|
if (!success) {
|
|
LYXERR(Debug::EXTERNAL, "external::updateExternal. "
|
|
<< "Unable to convert from " << from_format << " to " << to_format);
|
|
}
|
|
|
|
// return success
|
|
}
|
|
|
|
|
|
string const substituteCommands(InsetExternalParams const & params,
|
|
string const & input, string const & format);
|
|
|
|
string const substituteOptions(InsetExternalParams const & params,
|
|
string const & input, string const & format);
|
|
|
|
} // namespace anon
|
|
|
|
|
|
void writeExternal(InsetExternalParams const & params,
|
|
string const & format,
|
|
Buffer const & buffer, otexstream & os,
|
|
ExportData & exportdata,
|
|
bool external_in_tmpdir,
|
|
bool dryrun)
|
|
{
|
|
Template const * const et_ptr = getTemplatePtr(params);
|
|
if (!et_ptr)
|
|
return;
|
|
Template const & et = *et_ptr;
|
|
|
|
Template::Formats::const_iterator cit = et.formats.find(format);
|
|
if (cit == et.formats.end()) {
|
|
LYXERR(Debug::EXTERNAL, "External template format '" << format
|
|
<< "' not specified in template " << params.templatename());
|
|
return;
|
|
}
|
|
|
|
if (!dryrun || contains(cit->second.product, "$$Contents"))
|
|
updateExternal(params, format, buffer, exportdata,
|
|
external_in_tmpdir, dryrun);
|
|
|
|
bool const use_latex_path = format == "LaTeX";
|
|
string str = doSubstitution(params, buffer, cit->second.product,
|
|
use_latex_path, external_in_tmpdir);
|
|
|
|
string const absname = makeAbsPath(
|
|
params.filename.outputFileName(buffer.filePath()), buffer.filePath()).absFileName();
|
|
|
|
if (!dryrun && !external_in_tmpdir) {
|
|
if (!isValidLaTeXFileName(absname)) {
|
|
lyx::frontend::Alert::warning(_("Invalid filename"),
|
|
_("The following filename will cause troubles "
|
|
"when running the exported file through LaTeX: ") +
|
|
from_utf8(absname));
|
|
}
|
|
if (!isValidDVIFileName(absname)) {
|
|
lyx::frontend::Alert::warning(_("Problematic filename for DVI"),
|
|
_("The following filename can cause troubles "
|
|
"when running the exported file through LaTeX "
|
|
"and opening the resulting DVI: ") +
|
|
from_utf8(absname), true);
|
|
}
|
|
}
|
|
|
|
str = substituteCommands(params, str, format);
|
|
str = substituteOptions(params, str, format);
|
|
// FIXME UNICODE
|
|
os << from_utf8(str);
|
|
return;
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Empty template, specialised below.
|
|
template <typename TransformType>
|
|
string const substituteIt(string const &,
|
|
TransformID,
|
|
string const &,
|
|
Template::Format const &,
|
|
InsetExternalParams const &);
|
|
|
|
|
|
template <>
|
|
string const substituteIt<TransformCommand>(string const & input,
|
|
TransformID id,
|
|
string const & /* formatname */,
|
|
Template::Format const & format,
|
|
InsetExternalParams const & params)
|
|
{
|
|
typedef map<TransformID, TransformStore> Transformers;
|
|
Transformers::const_iterator it = format.command_transformers.find(id);
|
|
if (it == format.command_transformers.end())
|
|
return input;
|
|
|
|
TransformStore const & store = it->second;
|
|
|
|
TransformCommand::ptr_type ptr;
|
|
if (id == Rotate)
|
|
ptr = store.getCommandTransformer(params.rotationdata);
|
|
else if (id == Resize)
|
|
ptr = store.getCommandTransformer(params.resizedata);
|
|
|
|
if (!ptr.get())
|
|
return input;
|
|
|
|
string result =
|
|
subst(input, ptr->front_placeholder(), ptr->front());
|
|
return subst(result, ptr->back_placeholder(), ptr->back());
|
|
}
|
|
|
|
|
|
template <>
|
|
string const substituteIt<TransformOption>(string const & input,
|
|
TransformID id,
|
|
string const & fname,
|
|
Template::Format const & format,
|
|
InsetExternalParams const & params)
|
|
{
|
|
typedef map<TransformID, TransformStore> Transformers;
|
|
Transformers::const_iterator it = format.option_transformers.find(id);
|
|
if (it == format.option_transformers.end())
|
|
return input;
|
|
|
|
TransformStore const & store = it->second;
|
|
|
|
TransformOption::ptr_type ptr;
|
|
switch (id) {
|
|
case Clip:
|
|
ptr = store.getOptionTransformer(params.clipdata);
|
|
break;
|
|
case Extra:
|
|
ptr = store.getOptionTransformer(params.extradata.get(fname));
|
|
break;
|
|
case Rotate:
|
|
ptr = store.getOptionTransformer(params.rotationdata);
|
|
break;
|
|
case Resize:
|
|
ptr = store.getOptionTransformer(params.resizedata);
|
|
break;
|
|
}
|
|
|
|
if (!ptr.get())
|
|
return input;
|
|
|
|
return subst(input, ptr->placeholder(), ptr->option());
|
|
}
|
|
|
|
|
|
template <typename TransformerType>
|
|
string const transformIt(InsetExternalParams const & params,
|
|
string const & s, string const & formatname)
|
|
{
|
|
Template const * const et = getTemplatePtr(params);
|
|
if (!et || et->transformIds.empty())
|
|
return s;
|
|
|
|
Template::Formats::const_iterator fit = et->formats.find(formatname);
|
|
if (fit == et->formats.end())
|
|
return s;
|
|
|
|
string result = s;
|
|
Template::Format const & format = fit->second;
|
|
|
|
typedef vector<TransformID> TransformsIDs;
|
|
TransformsIDs::const_iterator it = et->transformIds.begin();
|
|
TransformsIDs::const_iterator end = et->transformIds.end();
|
|
for (; it != end; ++it) {
|
|
result = substituteIt<TransformerType>(result, *it, formatname,
|
|
format, params);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
string const substituteCommands(InsetExternalParams const & params,
|
|
string const & input, string const & format)
|
|
{
|
|
return transformIt<TransformCommand>(params, input, format);
|
|
}
|
|
|
|
|
|
string const substituteOption(InsetExternalParams const & params,
|
|
string const & input, string const & format)
|
|
{
|
|
string opt = transformIt<TransformOption>(params, input, format);
|
|
|
|
if (format == "LaTeX" || format == "PDFLaTeX")
|
|
return sanitizeLatexOption(opt);
|
|
if (format == "DocBook")
|
|
return sanitizeDocBookOption(opt);
|
|
return opt;
|
|
}
|
|
|
|
|
|
string const substituteOptions(InsetExternalParams const & params,
|
|
string const & input, string const & format)
|
|
{
|
|
string output = input;
|
|
|
|
Template const * const et = getTemplatePtr(params);
|
|
if (!et || et->transformIds.empty())
|
|
return output;
|
|
|
|
Template::Formats::const_iterator fit = et->formats.find(format);
|
|
if (fit == et->formats.end() || fit->second.options.empty())
|
|
return output;
|
|
|
|
typedef vector<Template::Option> Options;
|
|
Options const & options = fit->second.options;
|
|
Options::const_iterator it = options.begin();
|
|
Options::const_iterator end = options.end();
|
|
for (; it != end; ++it) {
|
|
string const opt = substituteOption(params, it->option, format);
|
|
string const placeholder = "$$" + it->name;
|
|
output = subst(output, placeholder, opt);
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
} // namespace anon
|
|
|
|
} // namespace external
|
|
|
|
} // namespace lyx
|