1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
8 * @file FileTransform.cpp
10 * @brief Implementation of file transformations
14 #include "FileTransform.h"
16 #include <Poco/Exception.h>
18 #include "multiformatText.h"
19 #include "UniMarkdownFile.h"
20 #include "Environment.h"
23 using Poco::Exception;
25 namespace FileTransform
28 PLUGIN_MODE g_UnpackerMode = PLUGIN_MANUAL;
29 PLUGIN_MODE g_PredifferMode = PLUGIN_MANUAL;
35 ////////////////////////////////////////////////////////////////////////////////
36 // transformations : packing unpacking
38 bool getPackUnpackPlugin(const String& pluginName, PluginInfo*& plugin, bool& bWithFile)
41 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_PACK_UNPACK", pluginName);
42 if (plugin == nullptr)
44 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_FOLDER_PACK_UNPACK", pluginName);
45 if (plugin == nullptr)
47 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"BUFFER_PACK_UNPACK", pluginName);
48 if (plugin == nullptr)
50 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(nullptr, pluginName);
51 if (plugin == nullptr)
53 AppErrorMessageBox(strutils::format_string1(_("Plugin not found or invalid: %1"), pluginName));
58 AppErrorMessageBox(strutils::format(_T("'%s' is not PACK_UNPACK plugin"), pluginName));
69 bool Packing(String & filepath, PackingInfo handler)
71 // no handler : return true
72 if (handler.m_PluginName.empty())
75 storageForPlugins bufferData;
76 bufferData.SetDataFileAnsi(filepath);
79 bool bHandled = false;
80 bool bWithFile = true;
81 PluginInfo* plugin = nullptr;
82 if (!getPackUnpackPlugin(handler.m_PluginName, plugin, bWithFile))
85 LPDISPATCH piScript = plugin->m_lpDispatch;
88 // use a temporary dest name
89 String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
90 String dstFileName = bufferData.GetDestFileName(); // <-Call order is important
91 bHandled = plugin::InvokePackFile(srcFileName,
93 bufferData.GetNChanged(),
94 piScript, handler.m_subcode);
96 bufferData.ValidateNewFile();
100 bHandled = plugin::InvokePackBuffer(*bufferData.GetDataBufferAnsi(),
101 bufferData.GetNChanged(),
102 piScript, handler.m_subcode);
104 bufferData.ValidateNewBuffer();
107 // if this packer does not work, that is an error
111 // if the buffer changed, write it before leaving
112 bool bSuccess = true;
113 if (bufferData.GetNChangedValid() > 0)
115 bSuccess = bufferData.SaveAsFile(filepath);
122 bool Unpacking(String & filepath, const PackingInfo * handler, int * handlerSubcode)
124 // no handler : return true
125 if (handler == nullptr || handler->m_PluginName.empty())
128 storageForPlugins bufferData;
129 bufferData.SetDataFileAnsi(filepath);
135 bool bHandled = false;
136 bool bWithFile = true;
137 PluginInfo* plugin = nullptr;
138 if (!getPackUnpackPlugin(handler->m_PluginName, plugin, bWithFile))
141 LPDISPATCH piScript = plugin->m_lpDispatch;
144 // use a temporary dest name
145 bufferData.SetDestFileExtension(plugin->m_ext);
146 String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
147 String dstFileName = bufferData.GetDestFileName(); // <-Call order is important
148 bHandled = plugin::InvokeUnpackFile(srcFileName,
150 bufferData.GetNChanged(),
153 bufferData.ValidateNewFile();
157 bHandled = plugin::InvokeUnpackBuffer(*bufferData.GetDataBufferAnsi(),
158 bufferData.GetNChanged(),
161 bufferData.ValidateNewBuffer();
164 // if this unpacker does not work, that is an error
169 *handlerSubcode = subcode;
171 // if the buffer changed, write it before leaving
172 bool bSuccess = true;
173 if (bufferData.GetNChangedValid() > 0)
175 bSuccess = bufferData.SaveAsFile(filepath);
182 // scan plugins for the first handler
183 bool Unpacking(String & filepath, const String& filteredText, PackingInfo * handler, int * handlerSubcode)
185 // PLUGIN_BUILTIN_XML : read source file through custom UniFile
186 if (handler->m_PluginOrPredifferMode == PLUGIN_BUILTIN_XML)
188 handler->m_pufile = new UniMarkdownFile;
189 handler->m_textType = _T("xml");
190 handler->m_bDisallowMixedEOL = true;
191 handler->m_PluginName.erase(); // Make FileTransform_Packing() a NOP
192 // Leave eToBeScanned alone so above lines will continue to execute on
193 // subsequent calls to this function
197 storageForPlugins bufferData;
198 bufferData.SetDataFileAnsi(filepath);
201 bool bHandled = false;
203 PluginInfo * plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"FILE_PACK_UNPACK", filteredText);
204 if (plugin == nullptr)
205 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"FILE_FOLDER_PACK_UNPACK", filteredText);
206 if (plugin != nullptr)
208 handler->m_PluginName = plugin->m_name;
209 // use a temporary dest name
210 bufferData.SetDestFileExtension(plugin->m_ext);
211 String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
212 String dstFileName = bufferData.GetDestFileName(); // <-Call order is important
213 bHandled = plugin::InvokeUnpackFile(srcFileName,
215 bufferData.GetNChanged(),
216 plugin->m_lpDispatch, handler->m_subcode);
218 bufferData.ValidateNewFile();
221 // We can not assume that the file is text, so use a safearray and not a BSTR
222 // TODO : delete this event ? Is anyone going to use this ?
226 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"BUFFER_PACK_UNPACK", filteredText);
227 if (plugin != nullptr)
229 handler->m_PluginName = plugin->m_name;
230 bHandled = plugin::InvokeUnpackBuffer(*bufferData.GetDataBufferAnsi(),
231 bufferData.GetNChanged(),
232 plugin->m_lpDispatch, handler->m_subcode);
234 bufferData.ValidateNewBuffer();
240 // we didn't find any unpacker, just hope it is normal Ansi/Unicode
241 handler->m_PluginName = _T("");
242 handler->m_subcode = 0;
246 // the handler is now defined
247 handler->m_PluginOrPredifferMode = PLUGIN_MANUAL;
250 *handlerSubcode = handler->m_subcode;
252 // if the buffer changed, write it before leaving
253 bool bSuccess = true;
254 if (bufferData.GetNChangedValid() > 0)
256 bSuccess = bufferData.SaveAsFile(filepath);
262 bool Unpacking(PackingInfo *handler, String& filepath, const String& filteredText)
264 if (handler->m_PluginOrPredifferMode != PLUGIN_MANUAL)
265 return Unpacking(filepath, filteredText, handler, &handler->m_subcode);
267 return Unpacking(filepath, handler, &handler->m_subcode);
270 ////////////////////////////////////////////////////////////////////////////////
271 // transformation prediffing
273 bool getPrediffPlugin(const String& pluginName, PluginInfo*& plugin, bool& bWithFile)
276 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_PREDIFF", pluginName);
277 if (plugin == nullptr)
279 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"BUFFER_PREDIFF", pluginName);
280 if (plugin == nullptr)
282 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(nullptr, pluginName);
283 if (plugin == nullptr)
285 AppErrorMessageBox(strutils::format_string1(_("Plugin not found or invalid: %1"), pluginName));
290 AppErrorMessageBox(strutils::format(_T("'%s' is not PREDIFF plugin"), pluginName));
300 bool Prediffing(String & filepath, PrediffingInfo handler, bool bMayOverwrite)
302 // no handler : return true
303 if (handler.m_PluginName.empty())
306 storageForPlugins bufferData;
307 // detect Ansi or Unicode file
308 bufferData.SetDataFileUnknown(filepath, bMayOverwrite);
309 // TODO : set the codepage
310 // bufferData.SetCodepage();
313 bool bHandled = false;
314 bool bWithFile = true;
315 PluginInfo* plugin = nullptr;
316 if (!getPrediffPlugin(handler.m_PluginName, plugin, bWithFile))
319 LPDISPATCH piScript = plugin->m_lpDispatch;
322 // use a temporary dest name
323 String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
324 String dstFileName = bufferData.GetDestFileName(); // <-Call order is important
325 bHandled = plugin::InvokePrediffFile(srcFileName,
327 bufferData.GetNChanged(),
330 bufferData.ValidateNewFile();
334 // probably it is for VB/VBscript so use a BSTR as argument
335 bHandled = plugin::InvokePrediffBuffer(*bufferData.GetDataBufferUnicode(),
336 bufferData.GetNChanged(),
339 bufferData.ValidateNewBuffer();
342 // if this unpacker does not work, that is an error
346 // if the buffer changed, write it before leaving
347 bool bSuccess = true;
348 if (bufferData.GetNChangedValid() > 0)
350 // bufferData changes filepath here to temp filepath
351 bSuccess = bufferData.SaveAsFile(filepath);
358 // scan plugins for the first handler
359 bool Prediffing(String & filepath, const String& filteredText, PrediffingInfo * handler, bool bMayOverwrite)
361 storageForPlugins bufferData;
362 // detect Ansi or Unicode file
363 bufferData.SetDataFileUnknown(filepath, bMayOverwrite);
364 // TODO : set the codepage
365 // bufferData.SetCodepage();
368 bool bHandled = false;
370 PluginInfo * plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"FILE_PREDIFF", filteredText);
371 if (plugin != nullptr)
373 handler->m_PluginName = plugin->m_name;
374 // use a temporary dest name
375 String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
376 String dstFileName = bufferData.GetDestFileName(); // <-Call order is important
377 bHandled = plugin::InvokePrediffFile(srcFileName,
379 bufferData.GetNChanged(),
380 plugin->m_lpDispatch);
382 bufferData.ValidateNewFile();
387 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"BUFFER_PREDIFF", filteredText);
388 if (plugin != nullptr)
390 handler->m_PluginName = plugin->m_name;
391 // probably it is for VB/VBscript so use a BSTR as argument
392 bHandled = plugin::InvokePrediffBuffer(*bufferData.GetDataBufferUnicode(),
393 bufferData.GetNChanged(),
394 plugin->m_lpDispatch);
396 bufferData.ValidateNewBuffer();
402 // we didn't find any prediffer, that is OK anyway
403 handler->m_PluginName = _T("");
407 // the handler is now defined
408 handler->m_PluginOrPredifferMode = PLUGIN_MANUAL;
410 // if the buffer changed, write it before leaving
411 bool bSuccess = true;
412 if (bufferData.GetNChangedValid() > 0)
414 bSuccess = bufferData.SaveAsFile(filepath);
420 bool Prediffing(PrediffingInfo * handler, String & filepath, const String& filteredText, bool bMayOverwrite)
422 if (handler->m_PluginOrPredifferMode != PLUGIN_MANUAL)
423 return Prediffing(filepath, filteredText, handler, bMayOverwrite);
425 return Prediffing(filepath, *handler, bMayOverwrite);
429 ////////////////////////////////////////////////////////////////////////////////
431 bool AnyCodepageToUTF8(int codepage, String & filepath, bool bMayOverwrite)
433 String tempDir = env::GetTemporaryPath();
436 String tempFilepath = env::GetTemporaryFileName(tempDir, _T("_W3"));
437 if (tempFilepath.empty())
439 // TODO : is it better with the BOM or without (just change the last argument)
440 int nFileChanged = 0;
441 bool bSuccess = ::AnyCodepageToUTF8(codepage, filepath, tempFilepath, nFileChanged, false);
442 if (bSuccess && nFileChanged!=0)
444 // we do not overwrite so we delete the old file
449 TFile(filepath).remove();
453 LogErrorStringUTF8(e.displayText());
456 // and change the filepath if everything works
457 filepath = tempFilepath;
463 TFile(tempFilepath).remove();
467 LogErrorStringUTF8(e.displayText());
475 ////////////////////////////////////////////////////////////////////////////////
476 // transformation : TextTransform_Interactive (editor scripts)
478 std::vector<String> GetFreeFunctionsInScripts(const wchar_t *TransformationEvent)
480 std::vector<String> sNamesArray;
482 // get an array with the available scripts
483 PluginArray * piScriptArray =
484 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(TransformationEvent);
486 // fill in these structures
489 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++)
491 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
492 if (plugin->m_disabled)
494 LPDISPATCH piScript = plugin->m_lpDispatch;
495 std::vector<String> scriptNamesArray;
496 std::vector<int> scriptIdsArray;
497 int nScriptFnc = plugin::GetMethodsFromScript(piScript, scriptNamesArray, scriptIdsArray);
498 sNamesArray.resize(nFnc+nScriptFnc);
501 for (iFnc = 0 ; iFnc < nScriptFnc ; iFnc++)
502 sNamesArray[nFnc+iFnc] = scriptNamesArray[iFnc];
509 bool Interactive(String & text, const wchar_t *TransformationEvent, int iFncChosen)
514 // get an array with the available scripts
515 PluginArray * piScriptArray =
516 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(TransformationEvent);
519 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++)
521 if (iFncChosen < piScriptArray->at(iScript)->m_nFreeFunctions)
522 // we have found the script file
524 iFncChosen -= piScriptArray->at(iScript)->m_nFreeFunctions;
527 if (iScript >= piScriptArray->size())
530 // iFncChosen is the index of the function in the script file
531 // we must convert it to the function ID
532 int fncID = plugin::GetMethodIDInScript(piScriptArray->at(iScript)->m_lpDispatch, iFncChosen);
534 // execute the transform operation
536 plugin::InvokeTransformText(text, nChanged, piScriptArray->at(iScript)->m_lpDispatch, fncID);
538 return (nChanged != 0);
543 ////////////////////////////////////////////////////////////////////////////////