OSDN Git Service

Fix a crash problem when the Diff algorithm is set to something other than default...
[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 <Poco/Mutex.h>
18 #include "Plugins.h"
19 #include "multiformatText.h"
20 #include "Environment.h"
21 #include "TFile.h"
22 #include "paths.h"
23 #include "MergeApp.h"
24
25 using Poco::Exception;
26
27 static Poco::FastMutex g_mutex;
28
29 ////////////////////////////////////////////////////////////////////////////////
30 // transformations : packing unpacking
31
32
33 std::vector<PluginForFile::PipelineItem> PluginForFile::ParsePluginPipeline(String& errorMessage) const
34 {
35         return ParsePluginPipeline(m_PluginPipeline, errorMessage);
36 }
37
38 std::vector<PluginForFile::PipelineItem> PluginForFile::ParsePluginPipeline(const String& pluginPipeline, String& errorMessage)
39 {
40         std::vector<PluginForFile::PipelineItem> result;
41         bool inQuotes = false;
42         tchar_t quoteChar = 0;
43         std::vector<String> args;
44         String token, name;
45         errorMessage.clear();
46         const tchar_t* p = pluginPipeline.c_str();
47         while (tc::istspace(*p)) p++;
48         while (*p)
49         {
50                 tchar_t sep = 0;
51                 while (*p)
52                 {
53                         if (!inQuotes)
54                         {
55                                 if (*p == '"' || *p == '\'')
56                                 {
57                                         inQuotes = true;
58                                         quoteChar = *p;
59                                 }
60                                 else if (tc::istspace(*p))
61                                 {
62                                         sep = *p;
63                                         break;
64                                 }
65                                 else if (*p == '|')
66                                 {
67                                         sep = *p;
68                                         break;
69                                 }
70                                 else
71                                         token += *p;
72                         }
73                         else
74                         {
75                                 if (*p == quoteChar)
76                                 {
77                                         if (*(p + 1) == quoteChar)
78                                         {
79                                                 token += *p;
80                                                 ++p;
81                                         }
82                                         else
83                                         {
84                                                 inQuotes = false;
85                                         }
86                                 }
87                                 else
88                                 {
89                                         token += *p;
90                                 }
91                         }
92                         ++p;
93                 }
94                 if (name.empty())
95                 {
96                         name = token;
97                 }
98                 else
99                 {
100                         args.push_back(token);
101                 }
102                 while (tc::istspace(*p)) p++;
103                 if (*p == '|')
104                         sep = *p;
105                 if (sep == '|')
106                         p++;
107                 token.clear();
108                 if (sep == '|' || !*p)
109                 {
110                         if (name.empty() || (sep == '|' && !*p))
111                         {
112                                 errorMessage = strutils::format_string1(_("Missing plugin name in plugin pipeline: %1"), pluginPipeline);
113                                 break;
114                         }
115                         result.push_back({ name, args, quoteChar });
116                         name.clear();
117                         args.clear();
118                         quoteChar = 0;
119                 }
120         };
121         if (inQuotes)
122                 errorMessage = strutils::format_string1(_("Missing quotation mark in plugin pipeline: %1"), pluginPipeline);
123         return result;
124 }
125
126 String PluginForFile::MakePluginPipeline(const std::vector<PluginForFile::PipelineItem>& list)
127 {
128         int i = 0;
129         String pipeline;
130         for (const auto& [name, args, quoteChar] : list)
131         {
132                 if (quoteChar && name.find_first_of(_T(" '\"")) != String::npos)
133                 {
134                         String nameQuoted = name;
135                         strutils::replace(nameQuoted, String(1, quoteChar), String(2, quoteChar));
136                         pipeline += strutils::format(_T("%c%s%c"), quoteChar, nameQuoted, quoteChar);
137                 }
138                 else
139                 {
140                         pipeline += name;
141                 }
142                 if (!args.empty())
143                 {
144                         for (const auto& arg : args)
145                         {
146                                 if (quoteChar)
147                                 {
148                                         String argQuoted = arg;
149                                         strutils::replace(argQuoted, String(1, quoteChar), String(2, quoteChar));
150                                         pipeline += strutils::format(_T(" %c%s%c"), quoteChar, argQuoted, quoteChar);
151                                 }
152                                 else
153                                 {
154                                         pipeline += _T(" ") + arg;
155                                 }
156                         }
157                 }
158                 if (i < list.size() - 1)
159                         pipeline += _T("|");
160                 i++;
161         }
162         return pipeline;
163 }
164
165 String PluginForFile::MakeArguments(const std::vector<String>& args, const std::vector<StringView>& variables)
166 {
167         String newstr;
168         int i = 0;
169         for (const auto& arg : args)
170         {
171                 String newarg;
172                 for (const tchar_t* p = arg.c_str(); *p; ++p)
173                 {
174                         if (*p == '%' && *(p + 1) != 0)
175                         {
176                                 ++p;
177                                 tchar_t c = *p;
178                                 if (c == '%')
179                                 {
180                                         newarg += '%';
181                                 }
182                                 else if (c >= '1' && c <= '9')
183                                 {
184                                         if ((c - '1') < variables.size())
185                                                 newarg += strutils::to_str(variables[(c - '1')]);
186                                 }
187                                 else
188                                 {
189                                         newarg += *(p - 1);
190                                         newarg += c;
191                                 }
192                         }
193                         else
194                         {
195                                 newarg += *p;
196                         }
197                 }
198                 if (newarg.find_first_of(_T(" \"")) != String::npos)
199                 {
200                         strutils::replace(newarg, _T("\""), _T("\"\""));
201                         newstr += _T("\"") + newarg + _T("\"");
202                 }
203                 else
204                 {
205                         newstr += newarg;
206                 }
207                 if (i < args.size() - 1)
208                         newstr += ' ';
209                 i++;
210         }
211         return newstr;
212 }
213
214 bool PackingInfo::GetPackUnpackPlugin(const String& filteredFilenames, bool bUrl, bool bReverse,
215         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>>& plugins,
216         String *pPluginPipelineResolved, String *pURLHandlerResolved, String& errorMessage) const
217 {
218         auto result = ParsePluginPipeline(errorMessage);
219         if (!errorMessage.empty())
220                 return false;
221         if (bUrl)
222         {
223                 std::vector<String> args;
224                 bool bWithFile = true;
225                 PluginInfo* plugin = nullptr;
226                 if (m_URLHandler.empty())
227                         plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"URL_PACK_UNPACK", filteredFilenames);
228                 else
229                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"URL_PACK_UNPACK", m_URLHandler);
230                 if (plugin)
231                         plugins.push_back({ plugin, args, bWithFile });
232                 if (pURLHandlerResolved)
233                         *pURLHandlerResolved = plugin ? plugin->m_name : _T("");
234         }
235         std::vector<PluginForFile::PipelineItem> pipelineResolved;
236         for (auto& [pluginName, args, quoteChar] : result)
237         {
238                 PluginInfo* plugin = nullptr;
239                 bool bWithFile = true;
240                 if (pluginName == _T("<None>") || pluginName == _("<None>"))
241                         ;
242                 else if (pluginName == _T("<Automatic>") || pluginName == _("<Automatic>"))
243                 {
244                         plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"FILE_PACK_UNPACK", filteredFilenames);
245                         if (plugin == nullptr)
246                                 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"FILE_FOLDER_PACK_UNPACK", filteredFilenames);
247                         if (plugin == nullptr)
248                         {
249                                 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"BUFFER_PACK_UNPACK", filteredFilenames);
250                                 bWithFile = false;
251                         }
252                 }
253                 else
254                 {
255                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_PACK_UNPACK", pluginName);
256                         if (plugin == nullptr)
257                         {
258                                 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_FOLDER_PACK_UNPACK", pluginName);
259                                 if (plugin == nullptr)
260                                 {
261                                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"BUFFER_PACK_UNPACK", pluginName);
262                                         if (plugin == nullptr)
263                                         {
264                                                 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(nullptr, pluginName);
265                                                 if (plugin == nullptr)
266                                                 {
267                                                         errorMessage = strutils::format_string1(_("Plugin not found or invalid: %1"), pluginName);
268                                                 }
269                                                 else
270                                                 {
271                                                         plugin = nullptr;
272                                                         errorMessage = strutils::format_string1(_("'%1' is not unpacker plugin"), pluginName);
273                                                 }
274                                                 return false;
275                                         }
276                                         bWithFile = false;
277                                 }
278                         }
279                 }
280                 if (plugin)
281                 {
282                         pipelineResolved.push_back({plugin->m_name, args, quoteChar });
283                         if (bReverse)
284                                 plugins.insert(plugins.begin(), { plugin, args, bWithFile });
285                         else
286                                 plugins.push_back({ plugin, args, bWithFile });
287                 }
288         }
289         if (pPluginPipelineResolved)
290                 *pPluginPipelineResolved = MakePluginPipeline(pipelineResolved);
291         return true;
292 }
293
294 // known handler
295 bool PackingInfo::pack(String & filepath, const String& dstFilepath, const std::vector<int>& handlerSubcodes, const std::vector<StringView>& variables) const
296 {
297         // no handler : return true
298         bool bUrl = paths::IsURL(dstFilepath);
299         if (m_PluginPipeline.empty() && !bUrl)
300                 return true;
301
302         // control value
303         String errorMessage;
304         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>> plugins;
305         if (!GetPackUnpackPlugin(_T(""), bUrl, true, plugins, nullptr, nullptr, errorMessage))
306         {
307                 AppErrorMessageBox(errorMessage);
308                 return false;
309         }
310
311         if (m_bWebBrowser && m_PluginPipeline.empty())
312                 return true;
313
314         auto itSubcode = handlerSubcodes.rbegin();
315         for (auto& [plugin, args, bWithFile] : plugins)
316         {
317                 bool bHandled = false;
318                 storageForPlugins bufferData;
319                 bufferData.SetDataFileAnsi(filepath);
320
321                 LPDISPATCH piScript = plugin->m_lpDispatch;
322                 Poco::FastMutex::ScopedLock lock(g_mutex);
323
324                 if (plugin->m_hasVariablesProperty)
325                 {
326                         if (!plugin::InvokePutPluginVariables(strutils::to_str(variables[0]), piScript))
327                                 return false;
328                 }
329                 if (plugin->m_hasArgumentsProperty)
330                 {
331                         if (!plugin::InvokePutPluginArguments(args.empty() ? plugin->m_arguments : MakeArguments(args, variables), piScript))
332                                 return false;
333                 }
334
335                 if (bWithFile)
336                 {
337                         // use a temporary dest name
338                         String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
339                         String dstFileName = plugin->m_event == L"URL_PACK_UNPACK" ?
340                                 dstFilepath : bufferData.GetDestFileName(); // <-Call order is important
341                         bHandled = plugin::InvokePackFile(srcFileName,
342                                 dstFileName,
343                                 bufferData.GetNChanged(),
344                                 piScript, *itSubcode);
345                         if (bHandled)
346                                 bufferData.ValidateNewFile();
347                 }
348                 else
349                 {
350                         bHandled = plugin::InvokePackBuffer(*bufferData.GetDataBufferAnsi(),
351                                 bufferData.GetNChanged(),
352                                 piScript, *itSubcode);
353                         if (bHandled)
354                                 bufferData.ValidateNewBuffer();
355                 }
356
357                 // if this packer does not work, that is an error
358                 if (!bHandled)
359                         return false;
360
361                 // if the buffer changed, write it before leaving
362                 if (bufferData.GetNChangedValid() > 0)
363                 {
364                         bool bSuccess = bufferData.SaveAsFile(filepath);
365                         if (!bSuccess)
366                                 return false;
367                 }
368                 ++itSubcode;
369         }
370         return true;
371 }
372
373 bool PackingInfo::Packing(const String& srcFilepath, const String& dstFilepath, const std::vector<int>& handlerSubcodes, const std::vector<StringView>& variables) const
374 {
375         String csTempFileName = srcFilepath;
376         if (!pack(csTempFileName, dstFilepath, handlerSubcodes, variables))
377                 return false;
378         try
379         {
380                 if (!paths::IsURL(dstFilepath))
381                 {
382                         TFile file1(csTempFileName);
383                         file1.copyTo(dstFilepath);
384                         if (srcFilepath != csTempFileName)
385                                 file1.remove();
386                 }
387                 return true;
388         }
389         catch (Poco::Exception& e)
390         {
391                 DWORD dwErrCode = GetLastError();
392                 LogErrorStringUTF8(e.displayText());
393                 SetLastError(dwErrCode);
394                 return false;
395         }
396 }
397
398 bool PackingInfo::Unpacking(std::vector<int> * handlerSubcodes, String & filepath, const String& filteredText, const std::vector<StringView>& variables)
399 {
400         if (handlerSubcodes)
401                 handlerSubcodes->clear();
402
403         // no handler : return true
404         bool bUrl = paths::IsURL(filepath);
405         if (m_PluginPipeline.empty() && !bUrl)
406                 return true;
407
408         // control value
409         String errorMessage;
410         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>> plugins;
411         if (!GetPackUnpackPlugin(filteredText, bUrl, false, plugins, &m_PluginPipeline, &m_URLHandler, errorMessage))
412         {
413                 AppErrorMessageBox(errorMessage);
414                 return false;
415         }
416
417         if (m_bWebBrowser && m_PluginPipeline.empty())
418                 return true;
419
420         for (auto& [plugin, args, bWithFile] : plugins)
421         {
422                 bool bHandled = false;
423                 storageForPlugins bufferData;
424                 bufferData.SetDataFileAnsi(filepath);
425
426                 // temporary subcode 
427                 int subcode = 0;
428
429                 LPDISPATCH piScript = plugin->m_lpDispatch;
430                 Poco::FastMutex::ScopedLock lock(g_mutex);
431
432                 if (plugin->m_hasVariablesProperty)
433                 {
434                         if (!plugin::InvokePutPluginVariables(strutils::to_str(variables[0]), piScript))
435                                 return false;
436                 }
437                 if (plugin->m_hasArgumentsProperty)
438                 {
439                         if (!plugin::InvokePutPluginArguments(args.empty() ? plugin->m_arguments : MakeArguments(args, variables), piScript))
440                                 return false;
441                 }
442
443                 if (bWithFile)
444                 {
445                         // use a temporary dest name
446                         bufferData.SetDestFileExtension(!plugin->m_ext.empty() ? plugin->m_ext : paths::FindExtension(filepath));
447                         String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
448                         String dstFileName = bufferData.GetDestFileName(); // <-Call order is important
449                         bHandled = plugin::InvokeUnpackFile(srcFileName,
450                                 dstFileName,
451                                 bufferData.GetNChanged(),
452                                 piScript, subcode);
453                         if (bHandled)
454                                 bufferData.ValidateNewFile();
455                 }
456                 else
457                 {
458                         bHandled = plugin::InvokeUnpackBuffer(*bufferData.GetDataBufferAnsi(),
459                                 bufferData.GetNChanged(),
460                                 piScript, subcode);
461                         if (bHandled)
462                                 bufferData.ValidateNewBuffer();
463                 }
464
465                 // if this unpacker does not work, that is an error
466                 if (!bHandled)
467                         return false;
468
469                 // valid the subcode
470                 if (handlerSubcodes)
471                         handlerSubcodes->push_back(subcode);
472
473                 // if the buffer changed, write it before leaving
474                 if (bufferData.GetNChangedValid() > 0)
475                 {
476                         bool bSuccess = bufferData.SaveAsFile(filepath);
477                         if (!bSuccess)
478                                 return false;
479                 }
480         }
481         return true;
482 }
483
484 String PackingInfo::GetUnpackedFileExtension(const String& filteredFilenames, int& preferredWindowType) const
485 {
486         preferredWindowType = -1;
487         String ext;
488         String errorMessage;
489         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>> plugins;
490         if (GetPackUnpackPlugin(filteredFilenames, false, false, plugins, nullptr, nullptr, errorMessage))
491         {
492                 for (auto& [plugin, args, bWithFile] : plugins)
493                 {
494                         ext += plugin->m_ext;
495                         auto preferredWindowTypeStr = plugin->GetExtendedPropertyValue(_T("PreferredWindowType"));
496                         if (preferredWindowTypeStr.has_value())
497                         {
498                                 if (preferredWindowTypeStr == L"Text")
499                                         preferredWindowType = 0;
500                                 else if (preferredWindowTypeStr == L"Table")
501                                         preferredWindowType = 1;
502                                 else if (preferredWindowTypeStr == L"Binary")
503                                         preferredWindowType = 2;
504                                 else if (preferredWindowTypeStr == L"Image")
505                                         preferredWindowType = 3;
506                                 else if (preferredWindowTypeStr == L"Webpage")
507                                         preferredWindowType = 4;
508                         }
509                 }
510         }
511         return ext;
512 }
513
514 ////////////////////////////////////////////////////////////////////////////////
515 // transformation prediffing
516
517 bool PrediffingInfo::GetPrediffPlugin(const String& filteredFilenames, bool bReverse,
518         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>>& plugins,
519         String *pPluginPipelineResolved, String& errorMessage) const
520 {
521         auto result = ParsePluginPipeline(errorMessage);
522         if (!errorMessage.empty())
523                 return false;
524         std::vector<PluginForFile::PipelineItem> pipelineResolved;
525         for (auto& [pluginName, args, quoteChar] : result)
526         {
527                 PluginInfo* plugin = nullptr;
528                 bool bWithFile = true;
529                 if (pluginName == _T("<None>") || pluginName == _("<None>"))
530                         ;
531                 else if (pluginName == _T("<Automatic>") || pluginName == _("<Automatic>"))
532                 {
533                         plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"FILE_PREDIFF", filteredFilenames);
534                         if (plugin == nullptr)
535                         {
536                                 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"BUFFER_PREDIFF", filteredFilenames);
537                                 if (plugin)
538                                         bWithFile = false;
539                         }
540                 }
541                 else
542                 {
543                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_PREDIFF", pluginName);
544                         if (plugin == nullptr)
545                         {
546                                 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"BUFFER_PREDIFF", pluginName);
547                                 if (plugin == nullptr)
548                                 {
549                                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(nullptr, pluginName);
550                                         if (plugin == nullptr)
551                                         {
552                                                 errorMessage = strutils::format_string1(_("Plugin not found or invalid: %1"), pluginName);
553                                         }
554                                         else
555                                         {
556                                                 errorMessage = strutils::format_string1(_("'%1' is not prediffer plugin"), pluginName);
557                                         }
558                                         return false;
559                                 }
560                                 bWithFile = false;
561                         }
562                 }
563                 if (plugin)
564                 {
565                         pipelineResolved.push_back({ plugin->m_name, args, quoteChar });
566                         if (bReverse)
567                                 plugins.insert(plugins.begin(), { plugin, args, bWithFile });
568                         else
569                                 plugins.push_back({ plugin, args, bWithFile });
570                 }
571         }
572         if (pPluginPipelineResolved)
573                 *pPluginPipelineResolved = MakePluginPipeline(pipelineResolved);
574         return true;
575 }
576
577 bool PrediffingInfo::Prediffing(String & filepath, const String& filteredText, bool bMayOverwrite, const std::vector<StringView>& variables)
578 {
579         // no handler : return true
580         if (m_PluginPipeline.empty())
581                 return true;
582
583         // control value
584         bool bHandled = false;
585         String errorMessage;
586         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>> plugins;
587         if (!GetPrediffPlugin(filteredText, false, plugins, &m_PluginPipeline, errorMessage))
588         {
589                 AppErrorMessageBox(errorMessage);
590                 return false;
591         }
592
593         for (const auto& [plugin, args, bWithFile] : plugins)
594         {
595                 storageForPlugins bufferData;
596                 // detect Ansi or Unicode file
597                 bufferData.SetDataFileUnknown(filepath, bMayOverwrite);
598                 // TODO : set the codepage
599                 // bufferData.SetCodepage();
600
601                 LPDISPATCH piScript = plugin->m_lpDispatch;
602                 Poco::FastMutex::ScopedLock lock(g_mutex);
603
604                 if (plugin->m_hasVariablesProperty)
605                 {
606                         if (!plugin::InvokePutPluginVariables(strutils::to_str(variables[0]), piScript))
607                                 return false;
608                 }
609                 if (plugin->m_hasArgumentsProperty)
610                 {
611                         if (!plugin::InvokePutPluginArguments(args.empty() ? plugin->m_arguments : MakeArguments(args, variables), piScript))
612                                 return false;
613                 }
614
615                 if (bWithFile)
616                 {
617                         // use a temporary dest name
618                         String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
619                         String dstFileName = bufferData.GetDestFileName(); // <-Call order is important
620                         bHandled = plugin::InvokePrediffFile(srcFileName,
621                                 dstFileName,
622                                 bufferData.GetNChanged(),
623                                 piScript);
624                         if (bHandled)
625                                 bufferData.ValidateNewFile();
626                 }
627                 else
628                 {
629                         // probably it is for VB/VBscript so use a BSTR as argument
630                         bHandled = plugin::InvokePrediffBuffer(*bufferData.GetDataBufferUnicode(),
631                                 bufferData.GetNChanged(),
632                                 piScript);
633                         if (bHandled)
634                                 bufferData.ValidateNewBuffer();
635                 }
636
637                 // if this unpacker does not work, that is an error
638                 if (!bHandled)
639                         return false;
640
641                 // if the buffer changed, write it before leaving
642                 if (bufferData.GetNChangedValid() > 0)
643                 {
644                         // bufferData changes filepath here to temp filepath
645                         bool bSuccess = bufferData.SaveAsFile(filepath);
646                         if (!bSuccess)
647                                 return false;
648                 }
649         }
650         return true;
651 }
652
653 ////////////////////////////////////////////////////////////////////////////////
654 // transformation text
655
656 bool EditorScriptInfo::GetEditorScriptPlugin(std::vector<std::tuple<PluginInfo*, std::vector<String>, int>>& plugins,
657         String& errorMessage) const
658 {
659         auto result = ParsePluginPipeline(errorMessage);
660         if (!errorMessage.empty())
661                 return false;
662         for (auto& [pluginName, args, quoteChar] : result)
663         {
664                 bool found = false;
665                 PluginArray *pluginInfoArray = CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"EDITOR_SCRIPT");
666                 for (const auto& plugin : *pluginInfoArray)
667                 {
668                         std::vector<String> namesArray;
669                         std::vector<int> idArray;
670                         int nFunc = plugin::GetMethodsFromScript(plugin->m_lpDispatch, namesArray, idArray);
671                         for (int i = 0; i < nFunc; ++i)
672                         {
673                                 if (namesArray[i] == pluginName)
674                                 {
675                                         plugins.push_back({ plugin.get(), args, idArray[i] });
676                                         found = true;
677                                         break;
678                                 }
679                         }
680                         if (found)
681                                 break;
682                 }
683                 if (!found)
684                 {
685                         errorMessage = strutils::format_string1(_("Plugin not found or invalid: %1"), pluginName);
686                         return false;
687                 }
688         }
689         return true;
690 }
691
692 bool EditorScriptInfo::TransformText(String & text, const std::vector<StringView>& variables, bool& changed)
693 {
694         changed = false;
695         // no handler : return true
696         if (m_PluginPipeline.empty())
697                 return true;
698
699         // control value
700         String errorMessage;
701         std::vector<std::tuple<PluginInfo*, std::vector<String>, int>> plugins;
702         if (!GetEditorScriptPlugin(plugins, errorMessage))
703         {
704                 AppErrorMessageBox(errorMessage);
705                 return false;
706         }
707
708         for (const auto& [plugin, args, fncID] : plugins)
709         {
710                 LPDISPATCH piScript = plugin->m_lpDispatch;
711                 Poco::FastMutex::ScopedLock lock(g_mutex);
712
713                 if (plugin->m_hasVariablesProperty)
714                 {
715                         if (!plugin::InvokePutPluginVariables(strutils::to_str(variables[0]), piScript))
716                                 return false;
717                 }
718                 if (plugin->m_hasArgumentsProperty)
719                 {
720                         if (!plugin::InvokePutPluginArguments(args.empty() ? plugin->m_arguments : MakeArguments(args, variables), piScript))
721                                 return false;
722                 }
723
724                 // execute the transform operation
725                 int nChanged = 0;
726                 if (!plugin::InvokeTransformText(text, nChanged, plugin->m_lpDispatch, fncID))
727                         return false;
728                 if (!changed)
729                         changed = (nChanged != 0);
730         }
731         return true;
732 }
733
734 namespace FileTransform
735 {
736
737 bool AutoUnpacking = false;
738 bool AutoPrediffing = false;
739
740 ////////////////////////////////////////////////////////////////////////////////
741
742 bool AnyCodepageToUTF8(int codepage, String & filepath, bool bMayOverwrite)
743 {
744         String tempDir = env::GetTemporaryPath();
745         if (tempDir.empty())
746                 return false;
747         String tempFilepath = env::GetTemporaryFileName(tempDir, _T("_W3"));
748         if (tempFilepath.empty())
749                 return false;
750         // TODO : is it better with the BOM or without (just change the last argument)
751         int nFileChanged = 0;
752         bool bSuccess = ::AnyCodepageToUTF8(codepage, filepath, tempFilepath, nFileChanged, false); 
753         if (bSuccess && nFileChanged!=0)
754         {
755                 // we do not overwrite so we delete the old file
756                 if (bMayOverwrite)
757                 {
758                         try
759                         {
760                                 TFile(filepath).remove();
761                         }
762                         catch (Exception& e)
763                         {
764                                 LogErrorStringUTF8(e.displayText());
765                         }
766                 }
767                 // and change the filepath if everything works
768                 filepath = tempFilepath;
769         }
770         else
771         {
772                 try
773                 {
774                         TFile(tempFilepath).remove();
775                 }
776                 catch (Exception& e)
777                 {
778                         LogErrorStringUTF8(e.displayText());
779                 }
780         }
781
782         return bSuccess;
783 }
784
785 std::pair<
786         std::vector<std::tuple<String, String, unsigned, PluginInfo *>>,
787         std::map<String, std::vector<std::tuple<String, String, unsigned, PluginInfo *>>>
788 >
789 CreatePluginMenuInfos(const String& filteredFilenames, const std::vector<std::wstring>& events, unsigned baseId)
790 {
791         std::vector<std::tuple<String, String, unsigned, PluginInfo *>> suggestedPlugins;
792         std::map<String, std::vector<std::tuple<String, String, unsigned, PluginInfo *>>> allPlugins;
793         std::map<String, int> captions;
794         unsigned id = baseId;
795         bool addedNoneAutomatic = false;
796         static PluginInfo noPlugin;
797         static PluginInfo autoPlugin;
798         if (autoPlugin.m_name.empty())
799                 autoPlugin.m_name = _T("<Automatic>");
800         for (const auto& event: events)
801         {
802                 auto pScriptArray =
803                         CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(event.c_str());
804                 for (auto& plugin : *pScriptArray)
805                 {
806                         if (!plugin->m_disabled)
807                         {
808                                 if (event != L"EDITOR_SCRIPT")
809                                 {
810                                         if (!addedNoneAutomatic)
811                                         {
812                                                 String process = _T("");
813                                                 allPlugins.insert_or_assign(process, std::vector<std::tuple<String, String, unsigned, PluginInfo *>>());
814                                                 allPlugins[process].emplace_back(_("<None>"), _T(""), id++, &noPlugin);
815                                                 allPlugins[process].emplace_back(_("<Automatic>"), _T("<Automatic>"), id++, &autoPlugin);
816                                                 addedNoneAutomatic = true;
817                                         }
818                                         const auto menuCaption = plugin->GetExtendedPropertyValue(_T("MenuCaption"));
819                                         const auto processType = plugin->GetExtendedPropertyValue(_T("ProcessType"));
820                                         const String caption = tr(ucr::toUTF8(menuCaption.has_value() ?
821                                                 strutils::to_str(*menuCaption) : plugin->m_name));
822                                         const String process = tr(ucr::toUTF8(processType.has_value() ?
823                                                 strutils::to_str(*processType) : _T("&Others")));
824
825                                         if (plugin->TestAgainstRegList(filteredFilenames))
826                                                 suggestedPlugins.emplace_back(caption, plugin->m_name, id, plugin.get());
827
828                                         if (allPlugins.find(process) == allPlugins.end())
829                                                 allPlugins.insert_or_assign(process, std::vector<std::tuple<String, String, unsigned, PluginInfo *>>());
830                                         allPlugins[process].emplace_back(caption, plugin->m_name, id, plugin.get());
831                                         captions[caption]++;
832                                         id++;
833                                 }
834                                 else
835                                 {
836                                         LPDISPATCH piScript = plugin->m_lpDispatch;
837                                         std::vector<String> scriptNamesArray;
838                                         std::vector<int> scriptIdsArray;
839                                         int nScriptFnc = plugin::GetMethodsFromScript(piScript, scriptNamesArray, scriptIdsArray);
840                                         bool matched = plugin->TestAgainstRegList(filteredFilenames);
841                                         for (int i = 0; i < nScriptFnc; ++i, ++id)
842                                         {
843                                                 if (scriptNamesArray[i] == L"PluginOnEvent")
844                                                         continue;
845                                                 const auto menuCaption = plugin->GetExtendedPropertyValue(scriptNamesArray[i] + _T(".MenuCaption"));
846                                                 auto processType = plugin->GetExtendedPropertyValue(scriptNamesArray[i] + _T(".ProcessType"));
847                                                 if (!processType.has_value())
848                                                         processType = plugin->GetExtendedPropertyValue(_T("ProcessType"));
849                                                 const String caption = tr(ucr::toUTF8(menuCaption.has_value() ?
850                                                         strutils::to_str(*menuCaption) : scriptNamesArray[i]));
851                                                 const String process = tr(ucr::toUTF8(processType.has_value() ?
852                                                         strutils::to_str(*processType) : _T("&Others")));
853                                                 if (matched)
854                                                         suggestedPlugins.emplace_back(caption, scriptNamesArray[i], id, plugin.get());
855                                                 if (allPlugins.find(process) == allPlugins.end())
856                                                         allPlugins.insert_or_assign(process, std::vector<std::tuple<String, String, unsigned, PluginInfo *>>());
857                                                 allPlugins[process].emplace_back(caption, scriptNamesArray[i], id, plugin.get());
858                                         }
859                                 }
860                         }
861                 }
862         }
863         auto ResolveConflictMenuCaptions = [&captions](auto& plugins)
864         {
865                 for (auto& plugin : plugins)
866                 {
867                         const String& caption = std::get<0>(plugin);
868                         if (captions[caption] > 1)
869                                 std::get<0>(plugin) = caption + _T("(") + std::get<1>(plugin) + _T(")");
870                 }
871         };
872         ResolveConflictMenuCaptions(suggestedPlugins);
873         for (auto& [processType, plugins] : allPlugins)
874                 ResolveConflictMenuCaptions(plugins);
875         return { suggestedPlugins, allPlugins };
876 }
877
878 }
879
880 ////////////////////////////////////////////////////////////////////////////////