OSDN Git Service

Fix failed test
[winmerge-jp/winmerge-jp.git] / Src / FileTransform.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //    SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
7 /**
8  *  @file FileTransform.cpp
9  *
10  *  @brief Implementation of file transformations
11  */ 
12
13 #include "pch.h"
14 #include "FileTransform.h"
15 #include <vector>
16 #include <Poco/Exception.h>
17 #include "Plugins.h"
18 #include "multiformatText.h"
19 #include "UniMarkdownFile.h"
20 #include "Environment.h"
21 #include "TFile.h"
22
23 using Poco::Exception;
24
25 namespace FileTransform
26 {
27
28 PLUGIN_MODE g_UnpackerMode = PLUGIN_MODE::PLUGIN_MANUAL;
29 PLUGIN_MODE g_PredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
30
31
32
33
34
35 ////////////////////////////////////////////////////////////////////////////////
36 // transformations : packing unpacking
37
38 bool getPackUnpackPlugin(const String& pluginName, PluginInfo*& plugin, bool& bWithFile)
39 {
40         bWithFile = true;
41         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_PACK_UNPACK", pluginName);
42         if (plugin == nullptr)
43         {
44                 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_FOLDER_PACK_UNPACK", pluginName);
45                 if (plugin == nullptr)
46                 {
47                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"BUFFER_PACK_UNPACK", pluginName);
48                         if (plugin == nullptr)
49                         {
50                                 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(nullptr, pluginName);
51                                 if (plugin == nullptr)
52                                 {
53                                         AppErrorMessageBox(strutils::format_string1(_("Plugin not found or invalid: %1"), pluginName));
54                                 }
55                                 else
56                                 {
57                                         plugin = nullptr;
58                                         AppErrorMessageBox(strutils::format(_T("'%s' is not PACK_UNPACK plugin"), pluginName));
59                                 }
60                                 return false;
61                         }
62                         bWithFile = false;
63                 }
64         }
65         return true;
66 }
67
68 // known handler
69 bool Packing(String & filepath, PackingInfo handler)
70 {
71         // no handler : return true
72         if (handler.m_PluginName.empty())
73                 return true;
74
75         storageForPlugins bufferData;
76         bufferData.SetDataFileAnsi(filepath);
77
78         // control value
79         bool bHandled = false;
80         bool bWithFile = true;
81         PluginInfo* plugin = nullptr;
82         if (!getPackUnpackPlugin(handler.m_PluginName, plugin, bWithFile))
83                 return false;
84
85         LPDISPATCH piScript = plugin->m_lpDispatch;
86         if (bWithFile)
87         {
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,
92                         dstFileName,
93                         bufferData.GetNChanged(),
94                         piScript, handler.m_subcode);
95                 if (bHandled)
96                         bufferData.ValidateNewFile();
97         }
98         else
99         {
100                 bHandled = plugin::InvokePackBuffer(*bufferData.GetDataBufferAnsi(),
101                         bufferData.GetNChanged(),
102                         piScript, handler.m_subcode);
103                 if (bHandled)
104                         bufferData.ValidateNewBuffer();
105         }
106
107         // if this packer does not work, that is an error
108         if (!bHandled)
109                 return false;
110
111         // if the buffer changed, write it before leaving
112         bool bSuccess = true;
113         if (bufferData.GetNChangedValid() > 0)
114         {
115                 bSuccess = bufferData.SaveAsFile(filepath);
116         }
117
118         return bSuccess;
119 }
120
121 // known handler
122 bool Unpacking(String & filepath, const PackingInfo * handler, int * handlerSubcode)
123 {
124         // no handler : return true
125         if (handler == nullptr || handler->m_PluginName.empty())
126                 return true;
127
128         storageForPlugins bufferData;
129         bufferData.SetDataFileAnsi(filepath);
130
131         // temporary subcode 
132         int subcode;
133
134         // control value
135         bool bHandled = false;
136         bool bWithFile = true;
137         PluginInfo* plugin = nullptr;
138         if (!getPackUnpackPlugin(handler->m_PluginName, plugin, bWithFile))
139                 return false;
140
141         LPDISPATCH piScript = plugin->m_lpDispatch;
142         if (bWithFile)
143         {
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,
149                         dstFileName,
150                         bufferData.GetNChanged(),
151                         piScript, subcode);
152                 if (bHandled)
153                         bufferData.ValidateNewFile();
154         }
155         else
156         {
157                 bHandled = plugin::InvokeUnpackBuffer(*bufferData.GetDataBufferAnsi(),
158                         bufferData.GetNChanged(),
159                         piScript, subcode);
160                 if (bHandled)
161                         bufferData.ValidateNewBuffer();
162         }
163
164         // if this unpacker does not work, that is an error
165         if (!bHandled)
166                 return false;
167
168         // valid the subcode
169         *handlerSubcode = subcode;
170
171         // if the buffer changed, write it before leaving
172         bool bSuccess = true;
173         if (bufferData.GetNChangedValid() > 0)
174         {
175                 bSuccess = bufferData.SaveAsFile(filepath);
176         }
177
178         return bSuccess;
179 }
180
181
182 // scan plugins for the first handler
183 bool Unpacking(String & filepath, const String& filteredText, PackingInfo * handler, int * handlerSubcode)
184 {
185         // PLUGIN_MODE::PLUGIN_BUILTIN_XML : read source file through custom UniFile
186         if (handler->m_PluginOrPredifferMode == PLUGIN_MODE::PLUGIN_BUILTIN_XML)
187         {
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
194                 return true;
195         }
196
197         storageForPlugins bufferData;
198         bufferData.SetDataFileAnsi(filepath);
199
200         // control value
201         bool bHandled = false;
202
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)
207         {
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,
214                         dstFileName,
215                         bufferData.GetNChanged(),
216                         plugin->m_lpDispatch, handler->m_subcode);
217                 if (bHandled)
218                         bufferData.ValidateNewFile();
219         }
220
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 ?
223
224         if (!bHandled)
225         {
226                 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"BUFFER_PACK_UNPACK", filteredText);
227                 if (plugin != nullptr)
228                 {
229                         handler->m_PluginName = plugin->m_name;
230                         bHandled = plugin::InvokeUnpackBuffer(*bufferData.GetDataBufferAnsi(),
231                                 bufferData.GetNChanged(),
232                                 plugin->m_lpDispatch, handler->m_subcode);
233                         if (bHandled)
234                                 bufferData.ValidateNewBuffer();
235                 }
236         }
237
238         if (!bHandled)
239         {
240                 // we didn't find any unpacker, just hope it is normal Ansi/Unicode
241                 handler->m_PluginName = _T("");
242                 handler->m_subcode = 0;
243                 bHandled = true;
244         }
245
246         // the handler is now defined
247         handler->m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
248
249         // assign the sucode
250         *handlerSubcode = handler->m_subcode;
251
252         // if the buffer changed, write it before leaving
253         bool bSuccess = true;
254         if (bufferData.GetNChangedValid() > 0)
255         {
256                 bSuccess = bufferData.SaveAsFile(filepath);
257         }
258
259         return bSuccess;
260 }
261
262 bool Unpacking(PackingInfo *handler, String& filepath, const String& filteredText)
263 {
264         if (handler->m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
265                 return Unpacking(filepath, filteredText, handler, &handler->m_subcode);
266         else
267                 return Unpacking(filepath, handler, &handler->m_subcode);
268 }
269
270 ////////////////////////////////////////////////////////////////////////////////
271 // transformation prediffing
272
273 bool getPrediffPlugin(const String& pluginName, PluginInfo*& plugin, bool& bWithFile)
274 {
275         bWithFile = true;
276         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_PREDIFF", pluginName);
277         if (plugin == nullptr)
278         {
279                 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"BUFFER_PREDIFF", pluginName);
280                 if (plugin == nullptr)
281                 {
282                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(nullptr, pluginName);
283                         if (plugin == nullptr)
284                         {
285                                 AppErrorMessageBox(strutils::format_string1(_("Plugin not found or invalid: %1"), pluginName));
286                         }
287                         else
288                         {
289                                 plugin = nullptr;
290                                 AppErrorMessageBox(strutils::format(_T("'%s' is not PREDIFF plugin"), pluginName));
291                         }
292                         return false;
293                 }
294                 bWithFile = false;
295         }
296         return true;
297 }
298
299 // known handler
300 bool Prediffing(String & filepath, PrediffingInfo handler, bool bMayOverwrite)
301 {
302         // no handler : return true
303         if (handler.m_PluginName.empty())
304                 return true;
305
306         storageForPlugins bufferData;
307         // detect Ansi or Unicode file
308         bufferData.SetDataFileUnknown(filepath, bMayOverwrite);
309         // TODO : set the codepage
310         // bufferData.SetCodepage();
311
312         // control value
313         bool bHandled = false;
314         bool bWithFile = true;
315         PluginInfo* plugin = nullptr;
316         if (!getPrediffPlugin(handler.m_PluginName, plugin, bWithFile))
317                 return false;
318
319         LPDISPATCH piScript = plugin->m_lpDispatch;
320         if (bWithFile)
321         {
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,
326                         dstFileName,
327                         bufferData.GetNChanged(),
328                         piScript);
329                 if (bHandled)
330                         bufferData.ValidateNewFile();
331         }
332         else
333         {
334                 // probably it is for VB/VBscript so use a BSTR as argument
335                 bHandled = plugin::InvokePrediffBuffer(*bufferData.GetDataBufferUnicode(),
336                         bufferData.GetNChanged(),
337                         piScript);
338                 if (bHandled)
339                         bufferData.ValidateNewBuffer();
340         }
341
342         // if this unpacker does not work, that is an error
343         if (!bHandled)
344                 return false;
345
346         // if the buffer changed, write it before leaving
347         bool bSuccess = true;
348         if (bufferData.GetNChangedValid() > 0)
349         {
350                 // bufferData changes filepath here to temp filepath
351                 bSuccess = bufferData.SaveAsFile(filepath);
352         }
353
354         return bSuccess;
355 }
356
357
358 // scan plugins for the first handler
359 bool Prediffing(String & filepath, const String& filteredText, PrediffingInfo * handler, bool bMayOverwrite)
360 {
361         storageForPlugins bufferData;
362         // detect Ansi or Unicode file
363         bufferData.SetDataFileUnknown(filepath, bMayOverwrite);
364         // TODO : set the codepage
365         // bufferData.SetCodepage();
366
367         // control value
368         bool bHandled = false;
369
370         PluginInfo * plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"FILE_PREDIFF", filteredText);
371         if (plugin != nullptr)
372         {
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,
378                         dstFileName,
379                         bufferData.GetNChanged(),
380                         plugin->m_lpDispatch);
381                 if (bHandled)
382                         bufferData.ValidateNewFile();
383         }
384
385         if (!bHandled)
386         {
387                 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"BUFFER_PREDIFF", filteredText);
388                 if (plugin != nullptr)
389                 {
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);
395                         if (bHandled)
396                                 bufferData.ValidateNewBuffer();
397                 }
398         }
399
400         if (!bHandled)
401         {
402                 // we didn't find any prediffer, that is OK anyway
403                 handler->m_PluginName = _T("");
404                 bHandled = true;
405         }
406
407         // the handler is now defined
408         handler->m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
409
410         // if the buffer changed, write it before leaving
411         bool bSuccess = true;
412         if (bufferData.GetNChangedValid() > 0)
413         {
414                 bSuccess = bufferData.SaveAsFile(filepath);
415         }
416
417         return bSuccess;
418 }
419
420 bool Prediffing(PrediffingInfo * handler, String & filepath, const String& filteredText, bool bMayOverwrite)
421 {
422         if (handler->m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
423                 return Prediffing(filepath, filteredText, handler, bMayOverwrite);
424         else
425                 return Prediffing(filepath, *handler, bMayOverwrite);
426 }
427
428
429 ////////////////////////////////////////////////////////////////////////////////
430
431 bool AnyCodepageToUTF8(int codepage, String & filepath, bool bMayOverwrite)
432 {
433         String tempDir = env::GetTemporaryPath();
434         if (tempDir.empty())
435                 return false;
436         String tempFilepath = env::GetTemporaryFileName(tempDir, _T("_W3"));
437         if (tempFilepath.empty())
438                 return false;
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)
443         {
444                 // we do not overwrite so we delete the old file
445                 if (bMayOverwrite)
446                 {
447                         try
448                         {
449                                 TFile(filepath).remove();
450                         }
451                         catch (Exception& e)
452                         {
453                                 LogErrorStringUTF8(e.displayText());
454                         }
455                 }
456                 // and change the filepath if everything works
457                 filepath = tempFilepath;
458         }
459         else
460         {
461                 try
462                 {
463                         TFile(tempFilepath).remove();
464                 }
465                 catch (Exception& e)
466                 {
467                         LogErrorStringUTF8(e.displayText());
468                 }
469         }
470
471         return bSuccess;
472 }
473
474
475 ////////////////////////////////////////////////////////////////////////////////
476 // transformation : TextTransform_Interactive (editor scripts)
477
478 std::vector<String> GetFreeFunctionsInScripts(const wchar_t *TransformationEvent)
479 {
480         std::vector<String> sNamesArray;
481
482         // get an array with the available scripts
483         PluginArray * piScriptArray = 
484                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(TransformationEvent);
485
486         // fill in these structures
487         int nFnc = 0;   
488         size_t iScript;
489         for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++)
490         {
491                 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
492                 if (plugin->m_disabled)
493                         continue;
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);
499
500                 int iFnc;
501                 for (iFnc = 0 ; iFnc < nScriptFnc ; iFnc++)
502                         sNamesArray[nFnc+iFnc] = scriptNamesArray[iFnc];
503
504                 nFnc += nScriptFnc;
505         }
506         return sNamesArray;
507 }
508
509 bool Interactive(String & text, const wchar_t *TransformationEvent, int iFncChosen)
510 {
511         if (iFncChosen < 0)
512                 return false;
513
514         // get an array with the available scripts
515         PluginArray * piScriptArray = 
516                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(TransformationEvent);
517
518         size_t iScript;
519         for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++)
520         {
521                 if (iFncChosen < piScriptArray->at(iScript)->m_nFreeFunctions)
522                         // we have found the script file
523                         break;
524                 iFncChosen -= piScriptArray->at(iScript)->m_nFreeFunctions;
525         }
526
527         if (iScript >= piScriptArray->size())
528                 return false;
529
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);
533
534         // execute the transform operation
535         int nChanged = 0;
536         plugin::InvokeTransformText(text, nChanged, piScriptArray->at(iScript)->m_lpDispatch, fncID);
537
538         return (nChanged != 0);
539 }
540
541 }
542
543 ////////////////////////////////////////////////////////////////////////////////