]> git.lyx.org Git - lyx.git/blob - src/insets/ExternalSupport.cpp
Saves some compile time by diminishing Buffer.h header dependencies:
[lyx.git] / src / insets / ExternalSupport.cpp
1 /**
2  * \file ExternalSupport.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup Nielsen
7  * \author Angus Leeming
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "ExternalSupport.h"
15 #include "ExternalTemplate.h"
16 #include "ExternalTransforms.h"
17 #include "InsetExternal.h"
18
19 #include "Buffer.h"
20 #include "Converter.h"
21 #include "debug.h"
22 #include "ErrorList.h"
23 #include "Exporter.h"
24 #include "Format.h"
25 #include "Mover.h"
26
27 #include "support/filetools.h"
28 #include "support/Forkedcall.h"
29 #include "support/lstrings.h"
30 #include "support/lyxalgo.h"
31 #include "support/lyxlib.h"
32 #include "support/os.h"
33 #include "support/Package.h"
34
35 #include <boost/filesystem/operations.hpp>
36
37 using std::endl;
38 using std::string;
39 using std::vector;
40
41 using boost::filesystem::is_directory;
42
43
44 namespace lyx {
45
46 using support::FileName;
47
48 namespace external {
49
50 Template const * getTemplatePtr(InsetExternalParams const & params)
51 {
52         TemplateManager const & etm = TemplateManager::get();
53         return etm.getTemplateByName(params.templatename());
54 }
55
56
57 void editExternal(InsetExternalParams const & params, Buffer const & buffer)
58 {
59         formats.edit(buffer, params.filename,
60                      formats.getFormatFromFile(params.filename));
61 }
62
63
64 namespace {
65
66 string const subst_path(string const & input,
67                         string const & placeholder,
68                         string const & path,
69                         bool use_latex_path,
70                         support::latex_path_extension ext = support::PROTECT_EXTENSION,
71                         support::latex_path_dots dots = support::LEAVE_DOTS)
72 {
73         if (input.find(placeholder) == string::npos)
74                 return input;
75         // Don't use external_path here when use_latex_path is false, as the
76         // path will be compared with another one in internal style later
77         // in Converters::move.
78         string const path2 = use_latex_path ?
79                 support::latex_path(path, ext, dots) : path;
80         return support::subst(input, placeholder, path2);
81 }
82
83 } // namespace anon
84
85
86 string const doSubstitution(InsetExternalParams const & params,
87                             Buffer const & buffer, string const & s,
88                             bool use_latex_path,
89                             bool external_in_tmpdir,
90                             Substitute what)
91 {
92         Buffer const * m_buffer = buffer.getMasterBuffer();
93         string const parentpath = external_in_tmpdir ?
94                 m_buffer->temppath() :
95                 buffer.filePath();
96         string const filename = external_in_tmpdir ?
97                 params.filename.mangledFilename() :
98                 params.filename.outputFilename(parentpath);
99         string const basename = support::changeExtension(
100                         support::onlyFilename(filename), string());
101         string const absname = support::makeAbsPath(filename, parentpath).absFilename();
102
103         string result = s;
104         if (what != ALL_BUT_PATHS) {
105                 string const filepath = support::onlyPath(filename);
106                 string const abspath = support::onlyPath(absname);
107                 string const masterpath = external_in_tmpdir ?
108                         m_buffer->temppath() :
109                         m_buffer->filePath();
110                 // FIXME UNICODE
111                 string relToMasterPath = support::onlyPath(
112                                 to_utf8(support::makeRelPath(from_utf8(absname),
113                                                              from_utf8(masterpath))));
114                 if (relToMasterPath == "./")
115                         relToMasterPath.clear();
116                 // FIXME UNICODE
117                 string relToParentPath = support::onlyPath(
118                                 to_utf8(support::makeRelPath(from_utf8(absname),
119                                                              from_utf8(parentpath))));
120                 if (relToParentPath == "./")
121                         relToParentPath.clear();
122
123                 result = subst_path(result, "$$FPath", filepath,
124                                     use_latex_path,
125                                     support::PROTECT_EXTENSION,
126                                     support::ESCAPE_DOTS);
127                 result = subst_path(result, "$$AbsPath", abspath,
128                                     use_latex_path,
129                                     support::PROTECT_EXTENSION,
130                                     support::ESCAPE_DOTS);
131                 result = subst_path(result, "$$RelPathMaster",
132                                     relToMasterPath, use_latex_path,
133                                     support::PROTECT_EXTENSION,
134                                     support::ESCAPE_DOTS);
135                 result = subst_path(result, "$$RelPathParent",
136                                     relToParentPath, use_latex_path,
137                                     support::PROTECT_EXTENSION,
138                                     support::ESCAPE_DOTS);
139                 if (support::absolutePath(filename)) {
140                         result = subst_path(result, "$$AbsOrRelPathMaster",
141                                             abspath, use_latex_path,
142                                             support::PROTECT_EXTENSION,
143                                             support::ESCAPE_DOTS);
144                         result = subst_path(result, "$$AbsOrRelPathParent",
145                                             abspath, use_latex_path,
146                                             support::PROTECT_EXTENSION,
147                                             support::ESCAPE_DOTS);
148                 } else {
149                         result = subst_path(result, "$$AbsOrRelPathMaster",
150                                             relToMasterPath, use_latex_path,
151                                             support::PROTECT_EXTENSION,
152                                             support::ESCAPE_DOTS);
153                         result = subst_path(result, "$$AbsOrRelPathParent",
154                                             relToParentPath, use_latex_path,
155                                             support::PROTECT_EXTENSION,
156                                             support::ESCAPE_DOTS);
157                 }
158         }
159
160         if (what == PATHS)
161                 return result;
162
163         result = subst_path(result, "$$FName", filename, use_latex_path,
164                             support::EXCLUDE_EXTENSION);
165         result = subst_path(result, "$$Basename", basename, use_latex_path,
166                             support::PROTECT_EXTENSION, support::ESCAPE_DOTS);
167         result = subst_path(result, "$$Extension",
168                         '.' + support::getExtension(filename), use_latex_path);
169         result = subst_path(result, "$$Tempname", params.tempname().absFilename(), use_latex_path);
170         result = subst_path(result, "$$Sysdir",
171                                 support::package().system_support().absFilename(), use_latex_path);
172
173         // Handle the $$Contents(filename) syntax
174         if (support::contains(result, "$$Contents(\"")) {
175                 // Since use_latex_path may be true we must extract the file
176                 // name from s instead of result and do the substitutions
177                 // again, this time with use_latex_path false.
178                 string::size_type const spos = s.find("$$Contents(\"");
179                 string::size_type const send = s.find("\")", spos);
180                 string const file_template = s.substr(spos + 12, send - (spos + 12));
181                 string const file = doSubstitution(params, buffer,
182                                                    file_template, false,
183                                                    external_in_tmpdir, what);
184                 string contents;
185
186                 FileName const absfile(
187                         support::makeAbsPath(file, m_buffer->temppath()));
188                 if (support::isFileReadable(absfile))
189                         contents = support::getFileContents(absfile);
190
191                 string::size_type const pos = result.find("$$Contents(\"");
192                 string::size_type const end = result.find("\")", pos);
193                 result.replace(pos, end + 2, contents);
194         }
195
196         return result;
197 }
198
199
200 namespace {
201
202 /** update the file represented by the template.
203     If \p external_in_tmpdir == true, then the generated file is
204     placed in the buffer's temporary directory.
205 */
206 void updateExternal(InsetExternalParams const & params,
207                     string const & format,
208                     Buffer const & buffer,
209                     ExportData & exportdata,
210                     bool external_in_tmpdir,
211                     bool dryrun)
212 {
213         Template const * const et_ptr = getTemplatePtr(params);
214         if (!et_ptr)
215                 return; // FAILURE
216         Template const & et = *et_ptr;
217
218         if (!et.automaticProduction)
219                 return; // NOT_NEEDED
220
221         Template::Formats::const_iterator cit = et.formats.find(format);
222         if (cit == et.formats.end())
223                 return; // FAILURE
224
225         Template::Format const & outputFormat = cit->second;
226         if (outputFormat.updateResult.empty())
227                 return; // NOT_NEEDED
228
229         string from_format = et.inputFormat;
230         if (from_format.empty())
231                 return; // NOT_NEEDED
232
233         if (from_format == "*") {
234                 if (params.filename.empty())
235                         return; // NOT_NEEDED
236
237                 // Try and ascertain the file format from its contents.
238                 from_format = formats.getFormatFromFile(params.filename);
239                 if (from_format.empty())
240                         return; // FAILURE
241
242         }
243
244         string const to_format = outputFormat.updateFormat;
245         if (to_format.empty())
246                 return; // NOT_NEEDED
247
248         // The master buffer. This is useful when there are multiple levels
249         // of include files
250         Buffer const * m_buffer = buffer.getMasterBuffer();
251
252         // We copy the source file to the temp dir and do the conversion
253         // there if necessary
254         FileName const temp_file(
255                 support::makeAbsPath(params.filename.mangledFilename(),
256                                      m_buffer->temppath()));
257         if (!params.filename.empty() && !is_directory(params.filename.toFilesystemEncoding())) {
258                 unsigned long const from_checksum = support::sum(params.filename);
259                 unsigned long const temp_checksum = support::sum(temp_file);
260
261                 if (from_checksum != temp_checksum) {
262                         Mover const & mover = getMover(from_format);
263                         if (!mover.copy(params.filename, temp_file)) {
264                                 LYXERR(Debug::EXTERNAL)
265                                         << "external::updateExternal. "
266                                         << "Unable to copy "
267                                         << params.filename << " to " << temp_file << endl;
268                                 return; // FAILURE
269                         }
270                 }
271         }
272
273         // the generated file (always in the temp dir)
274         string const to_file = doSubstitution(params, buffer,
275                                               outputFormat.updateResult,
276                                               false, true);
277         FileName const abs_to_file(
278                 support::makeAbsPath(to_file, m_buffer->temppath()));
279
280         if (!dryrun) {
281                 // Record the referenced files for the exporter.
282                 // The exporter will copy them to the export dir.
283                 typedef Template::Format::FileMap FileMap;
284                 FileMap::const_iterator rit  = outputFormat.referencedFiles.begin();
285                 FileMap::const_iterator rend = outputFormat.referencedFiles.end();
286                 for (; rit != rend; ++rit) {
287                         vector<string>::const_iterator fit  = rit->second.begin();
288                         vector<string>::const_iterator fend = rit->second.end();
289                         for (; fit != fend; ++fit) {
290                                 FileName const source(support::makeAbsPath(
291                                                 doSubstitution(params, buffer, *fit,
292                                                                false, true),
293                                                 m_buffer->temppath()));
294                                 // The path of the referenced file is never the
295                                 // temp path, but the filename may be the mangled
296                                 // or the real name. Therefore we substitute the
297                                 // paths and names separately.
298                                 string file = support::subst(*fit, "$$FName",
299                                                 "$$FPath$$Basename$$Extension");
300                                 file = doSubstitution(params, buffer, file, false, false,
301                                                       PATHS);
302                                 file = doSubstitution(params, buffer, file,
303                                                       false, external_in_tmpdir,
304                                                       ALL_BUT_PATHS);
305                                 // if file is a relative name, it is interpreted
306                                 // relative to the master document.
307                                 exportdata.addExternalFile(rit->first, source, file);
308                         }
309                 }
310         }
311
312         // Do we need to perform the conversion?
313         // Yes if to_file does not exist or if from_file is newer than to_file
314         if (support::compare_timestamps(temp_file, abs_to_file) < 0)
315                 return; // SUCCESS
316
317         // FIXME (Abdel 12/08/06): Is there a need to show these errors?
318         ErrorList el;
319         bool const success =
320                 theConverters().convert(&buffer, temp_file, abs_to_file,
321                                    params.filename, from_format, to_format, el,
322                                    Converters::try_default | Converters::try_cache);
323
324         if (!success)
325                 LYXERR(Debug::EXTERNAL)
326                         << "external::updateExternal. "
327                         << "Unable to convert from "
328                         << from_format << " to " << to_format << endl;
329
330         // return success
331 }
332
333
334 string const substituteCommands(InsetExternalParams const & params,
335                                 string const & input, string const & format);
336
337 string const substituteOptions(InsetExternalParams const & params,
338                                string const & input, string const & format);
339
340 } // namespace anon
341
342
343 int writeExternal(InsetExternalParams const & params,
344                   string const & format,
345                   Buffer const & buffer, odocstream & os,
346                   ExportData & exportdata,
347                   bool external_in_tmpdir,
348                   bool dryrun)
349 {
350         Template const * const et_ptr = getTemplatePtr(params);
351         if (!et_ptr)
352                 return 0;
353         Template const & et = *et_ptr;
354
355         Template::Formats::const_iterator cit = et.formats.find(format);
356         if (cit == et.formats.end()) {
357                 LYXERR(Debug::EXTERNAL)
358                         << "External template format '" << format
359                         << "' not specified in template "
360                         << params.templatename() << endl;
361                 return 0;
362         }
363
364         if (!dryrun || support::contains(cit->second.product, "$$Contents"))
365                 updateExternal(params, format, buffer, exportdata,
366                                external_in_tmpdir, dryrun);
367
368         bool const use_latex_path = format == "LaTeX";
369         string str = doSubstitution(params, buffer, cit->second.product,
370                                     use_latex_path, external_in_tmpdir);
371         str = substituteCommands(params, str, format);
372         str = substituteOptions(params, str, format);
373         // FIXME UNICODE
374         os << from_utf8(str);
375         return int(lyx::count(str.begin(), str.end(),'\n'));
376 }
377
378 namespace {
379
380 // Empty template, specialised below.
381 template <typename TransformType>
382 string const substituteIt(string const &,
383                           TransformID,
384                           string const &,
385                           Template::Format const &,
386                           InsetExternalParams const &);
387
388
389 template <>
390 string const substituteIt<TransformCommand>(string const & input,
391                                             TransformID id,
392                                             string const & /* formatname */,
393                                             Template::Format const & format,
394                                             InsetExternalParams const & params)
395 {
396         typedef std::map<TransformID, TransformStore> Transformers;
397         Transformers::const_iterator it = format.command_transformers.find(id);
398         if (it == format.command_transformers.end())
399                 return input;
400
401         TransformStore const & store = it->second;
402
403         TransformCommand::ptr_type ptr;
404         if (id == Rotate)
405                 ptr = store.getCommandTransformer(params.rotationdata);
406         else if (id == Resize)
407                 ptr = store.getCommandTransformer(params.resizedata);
408
409         if (!ptr.get())
410                 return input;
411
412         string result =
413                 support::subst(input, ptr->front_placeholder(), ptr->front());
414         return support::subst(result, ptr->back_placeholder(),  ptr->back());
415 }
416
417
418 template <>
419 string const substituteIt<TransformOption>(string const & input,
420                                            TransformID id,
421                                            string const & fname,
422                                            Template::Format const & format,
423                                            InsetExternalParams const & params)
424 {
425         typedef std::map<TransformID, TransformStore> Transformers;
426         Transformers::const_iterator it = format.option_transformers.find(id);
427         if (it == format.option_transformers.end())
428                 return input;
429
430         TransformStore const & store = it->second;
431
432         TransformOption::ptr_type ptr;
433         switch (id) {
434         case Clip:
435                 ptr = store.getOptionTransformer(params.clipdata);
436                 break;
437         case Extra:
438                 ptr = store.getOptionTransformer(params.extradata.get(fname));
439                 break;
440         case Rotate:
441                 ptr = store.getOptionTransformer(params.rotationdata);
442                 break;
443         case Resize:
444                 ptr = store.getOptionTransformer(params.resizedata);
445                 break;
446         }
447
448         if (!ptr.get())
449                 return input;
450
451         return support::subst(input, ptr->placeholder(), ptr->option());
452 }
453
454
455 template <typename TransformerType>
456 string const transformIt(InsetExternalParams const & params,
457                          string const & s, string const & formatname)
458 {
459         Template const * const et = getTemplatePtr(params);
460         if (!et || et->transformIds.empty())
461                 return s;
462
463         Template::Formats::const_iterator fit = et->formats.find(formatname);
464         if (fit == et->formats.end())
465                 return s;
466
467         string result = s;
468         Template::Format const & format =  fit->second;
469
470         typedef vector<TransformID> TransformsIDs;
471         TransformsIDs::const_iterator it  = et->transformIds.begin();
472         TransformsIDs::const_iterator end = et->transformIds.end();
473         for (; it != end; ++it) {
474                 result = substituteIt<TransformerType>(result, *it, formatname,
475                                                        format, params);
476         }
477         return result;
478 }
479
480
481 string const substituteCommands(InsetExternalParams const & params,
482                                 string const & input, string const & format)
483 {
484         return transformIt<TransformCommand>(params, input, format);
485 }
486
487
488 string const substituteOption(InsetExternalParams const & params,
489                               string const & input, string const & format)
490 {
491         string opt = transformIt<TransformOption>(params, input, format);
492
493         if (format == "LaTeX" || format == "PDFLaTeX")
494                 return sanitizeLatexOption(opt);
495         if (format == "DocBook")
496                 return sanitizeDocBookOption(opt);
497         return opt;
498 }
499
500
501 string const substituteOptions(InsetExternalParams const & params,
502                                string const & input, string const & format)
503 {
504         string output = input;
505
506         Template const * const et = getTemplatePtr(params);
507         if (!et || et->transformIds.empty())
508                 return output;
509
510         Template::Formats::const_iterator fit = et->formats.find(format);
511         if (fit == et->formats.end() || fit->second.options.empty())
512                 return output;
513
514         typedef vector<Template::Option> Options;
515         Options const & options = fit->second.options;
516         Options::const_iterator it  = options.begin();
517         Options::const_iterator end = options.end();
518         for (; it != end; ++it) {
519                 string const opt = substituteOption(params, it->option, format);
520                 string const placeholder = "$$" + it->name;
521                 output = support::subst(output, placeholder, opt);
522         }
523
524         return output;
525 }
526
527 } // namespace anon
528
529 } // namespace external
530
531 } // namespace lyx