3 #define POCO_NO_UNWINDOWS 1
4 #include <Poco/FileStream.h>
5 #include <Poco/SAX/SAXParser.h>
6 #include <Poco/SAX/SAXException.h>
7 #include <Poco/SAX/ContentHandler.h>
8 #include <Poco/SAX/Attributes.h>
9 #include <Poco/Exception.h>
12 #include <unordered_set>
20 #include "Environment.h"
21 #include "OptionsMgr.h"
22 #include "OptionsDef.h"
23 #include "codepage_detect.h"
25 #include "WinMergePluginBase.h"
28 using Poco::XML::SAXParser;
29 using Poco::XML::ContentHandler;
30 using Poco::XML::Locator;
31 using Poco::XML::XMLChar;
32 using Poco::XML::XMLString;
33 using Poco::XML::Attributes;
34 using namespace std::literals::string_literals;
38 HRESULT ReadFile(const String& path, String& text)
41 if (!file.OpenReadOnly(path))
42 return HRESULT_FROM_WIN32(GetLastError());
46 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
47 int64_t fileSize = file.GetFileSize();
48 FileTextEncoding encoding = codepage_detect::Guess(
49 paths::FindExtension(path), file.GetBase(), static_cast<size_t>(
50 fileSize < static_cast<int64_t>(codepage_detect::BufSize) ?
51 fileSize : static_cast<int64_t>(codepage_detect::BufSize)),
53 file.SetCodepage(encoding.m_codepage);
55 file.ReadStringAll(text);
60 HRESULT WriteFile(const String& path, const String& text, bool bom = true)
63 if (!fileOut.Open(path, _T("wb")))
64 return HRESULT_FROM_WIN32(GetLastError());
65 fileOut.SetUnicoding(ucr::UNICODESET::UTF8);
66 fileOut.WriteString(text);
72 namespace internal_plugin
78 String m_fileExtension;
84 std::unique_ptr<Script> m_script;
89 Info(const String& name) : m_name(name) {}
90 Info(Info&& info) = default;
95 bool m_isAutomatic = false;
96 String m_unpackedFileExtension;
97 String m_extendedProperties;
99 std::unique_ptr<Method> m_prediffFile;
100 std::unique_ptr<Method> m_unpackFile;
101 std::unique_ptr<Method> m_packFile;
102 std::unique_ptr<Method> m_unpackFolder;
103 std::unique_ptr<Method> m_packFolder;
104 std::map<String, Method> m_editorScripts;
107 class XMLHandler : public Poco::XML::ContentHandler
110 inline static const std::string Empty = "";
111 inline static const std::string PluginsElement = "plugins";
112 inline static const std::string PluginElement = "plugin";
113 inline static const std::string EventElement = "event";
114 inline static const std::string DescriptionElement = "description";
115 inline static const std::string FileFiltersElement = "file-filters";
116 inline static const std::string IsAutomaticElement = "is-automatic";
117 inline static const std::string UnpackedFileExtensionElement = "unpacked-file-extension";
118 inline static const std::string ExtendedPropertiesElement = "extended-properties";
119 inline static const std::string ArgumentsElement = "arguments";
120 inline static const std::string PrediffFileElement = "prediff-file";
121 inline static const std::string UnpackFileElement = "unpack-file";
122 inline static const std::string PackFileElement = "pack-file";
123 inline static const std::string UnpackFolderElement = "unpack-folder";
124 inline static const std::string PackFolderElement = "pack-folder";
125 inline static const std::string CommandElement = "command";
126 inline static const std::string ScriptElement = "script";
127 inline static const std::string NameAttribute = "name";
128 inline static const std::string ValueAttribute = "value";
129 inline static const std::string FileExtensionAttribute = "fileExtension";
131 explicit XMLHandler(std::list<Info>* pPlugins) : m_pPlugins(pPlugins) {}
133 void setDocumentLocator(const Locator* loc) {}
134 void startDocument() {}
135 void endDocument() {}
136 void startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
138 if (!m_stack.empty())
140 if (m_stack.top() == PluginsElement)
142 if (localName == PluginElement)
145 int index = attributes.getIndex(Empty, NameAttribute);
147 name = ucr::toTString(attributes.getValue(index));
148 m_pPlugins->emplace_back(name);
152 else if (m_stack.top() == PluginElement)
154 Info& plugin = m_pPlugins->back();
156 int index = attributes.getIndex(Empty, ValueAttribute);
159 value = ucr::toTString(attributes.getValue(index));
160 if (localName == EventElement)
161 plugin.m_event = value;
162 else if (localName == DescriptionElement)
163 plugin.m_description = value;
164 else if (localName == FileFiltersElement)
165 plugin.m_fileFilters = value;
166 else if (localName == IsAutomaticElement)
168 TCHAR ch = value.c_str()[0];
169 plugin.m_isAutomatic = (ch == 't' || ch == 'T');
171 else if (localName == UnpackedFileExtensionElement)
172 plugin.m_unpackedFileExtension = value;
173 else if (localName == ExtendedPropertiesElement)
174 plugin.m_extendedProperties = value;
175 else if (localName == ArgumentsElement)
176 plugin.m_arguments = value;
178 else if (localName == PrediffFileElement)
180 plugin.m_prediffFile.reset(new Method());
181 m_pMethod = plugin.m_prediffFile.get();
183 else if (localName == UnpackFileElement)
185 plugin.m_unpackFile.reset(new Method());
186 m_pMethod = plugin.m_unpackFile.get();
188 else if (localName == PackFileElement)
190 plugin.m_packFile.reset(new Method());
191 m_pMethod = plugin.m_packFile.get();
193 else if (localName == UnpackFolderElement)
195 plugin.m_unpackFolder.reset(new Method());
196 m_pMethod = plugin.m_unpackFolder.get();
198 else if (localName == PackFolderElement)
200 plugin.m_packFolder.reset(new Method());
201 m_pMethod = plugin.m_packFolder.get();
206 if (localName == ScriptElement)
208 m_pMethod->m_script.reset(new Script);
209 int index = attributes.getIndex(Empty, FileExtensionAttribute);
211 m_pMethod->m_script->m_fileExtension = ucr::toTString(attributes.getValue(index));
215 m_stack.push(localName);
217 void endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
221 void characters(const XMLChar ch[], int start, int length)
225 if (m_stack.top() == CommandElement && m_pMethod)
227 m_pMethod->m_command += xmlch2tstr(ch, length);
229 else if (m_stack.top() == ScriptElement && m_pMethod && m_pMethod->m_script)
231 m_pMethod->m_script->m_body += xmlch2tstr(ch, length);
234 void ignorableWhitespace(const XMLChar ch[], int start, int length) {}
235 void processingInstruction(const XMLString& target, const XMLString& data) {}
236 void startPrefixMapping(const XMLString& prefix, const XMLString& uri) {}
237 void endPrefixMapping(const XMLString& prefix) {}
238 void skippedEntity(const XMLString& name) {}
241 static String xmlch2tstr(const XMLChar* ch, int length)
243 return ucr::toTString(std::string(ch, length));
246 std::list<Info>* m_pPlugins = nullptr;
247 std::stack<std::string> m_stack;
248 Method* m_pMethod = nullptr;
251 class UnpackerGeneratedFromEditorScript : public WinMergePluginBase
254 UnpackerGeneratedFromEditorScript(const PluginInfo& plugin, const std::wstring funcname, int id)
255 : WinMergePluginBase(
257 strutils::format_string1(_T("Unpacker to execute %1 script (automatically generated)"), funcname),
259 , m_pDispatch(plugin.m_lpDispatch)
261 , m_hasArgumentsProperty(plugin.m_hasArgumentsProperty)
262 , m_hasVariablesProperty(plugin.m_hasVariablesProperty)
264 auto desc = plugin.GetExtendedPropertyValue(funcname + _T(".Description"));
265 if (desc.has_value())
266 m_sDescription = *desc;
267 m_pDispatch->AddRef();
268 auto menuCaption = plugin.GetExtendedPropertyValue(funcname + _T(".MenuCaption"));
269 String caption = menuCaption.has_value() ? String{ menuCaption->data(), menuCaption->length() } : funcname;
270 m_sExtendedProperties = ucr::toUTF16(strutils::format(_T("ProcessType=Editor script;MenuCaption=%s"), caption))
271 + (plugin.GetExtendedPropertyValue(funcname + _T(".ArgumentsRequired")).has_value() ? L";ArgumentsRequired" : L"");
274 virtual ~UnpackerGeneratedFromEditorScript()
276 m_pDispatch->Release();
279 HRESULT STDMETHODCALLTYPE UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT* pSubcode, VARIANT_BOOL* pbSuccess) override
282 HRESULT hr = ReadFile(fileSrc, text);
285 if (m_hasVariablesProperty && !plugin::InvokePutPluginVariables(ucr::toTString(fileSrc), m_pDispatch))
287 if (m_hasArgumentsProperty && !plugin::InvokePutPluginArguments(m_sArguments, m_pDispatch))
290 if (!plugin::InvokeTransformText(text, changed, m_pDispatch, m_funcid))
292 hr = WriteFile(fileDst, text);
296 *pbChanged = VARIANT_TRUE;
297 *pbSuccess = VARIANT_TRUE;
301 HRESULT STDMETHODCALLTYPE PackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT subcode, VARIANT_BOOL* pbSuccess) override
303 *pbChanged = VARIANT_FALSE;
304 *pbSuccess = VARIANT_FALSE;
309 IDispatch* m_pDispatch;
311 bool m_hasArgumentsProperty;
312 bool m_hasVariablesProperty;
315 class InternalPlugin : public WinMergePluginBase
318 InternalPlugin(Info&& info)
319 : WinMergePluginBase(info.m_event, info.m_description, info.m_fileFilters, info.m_unpackedFileExtension, info.m_extendedProperties, info.m_arguments, info.m_isAutomatic)
320 , m_info(std::move(info))
324 virtual ~InternalPlugin()
328 HRESULT STDMETHODCALLTYPE PrediffFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, VARIANT_BOOL* pbSuccess) override
330 if (!m_info.m_prediffFile)
332 *pbChanged = VARIANT_FALSE;
333 *pbSuccess = VARIANT_FALSE;
337 String command = replaceMacros(m_info.m_prediffFile->m_command, fileSrc, fileDst);
338 if (m_info.m_prediffFile->m_script)
340 createScript(*m_info.m_prediffFile->m_script, scriptFile);
341 strutils::replace(command, _T("${SCRIPT_FILE}"), scriptFile.GetPath());
344 HRESULT hr = launchProgram(command, SW_HIDE, dwExitCode);
346 *pbChanged = SUCCEEDED(hr);
347 *pbSuccess = SUCCEEDED(hr);
351 HRESULT STDMETHODCALLTYPE UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT* pSubcode, VARIANT_BOOL* pbSuccess) override
353 if (!m_info.m_unpackFile)
356 *pbChanged = VARIANT_FALSE;
357 *pbSuccess = VARIANT_FALSE;
361 String command = replaceMacros(m_info.m_unpackFile->m_command, fileSrc, fileDst);
362 if (m_info.m_unpackFile->m_script)
364 createScript(*m_info.m_unpackFile->m_script, scriptFile);
365 strutils::replace(command, _T("${SCRIPT_FILE}"), scriptFile.GetPath());
368 HRESULT hr = launchProgram(command, SW_HIDE, dwExitCode);
371 *pbChanged = SUCCEEDED(hr);
372 *pbSuccess = SUCCEEDED(hr);
376 HRESULT STDMETHODCALLTYPE PackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT subcode, VARIANT_BOOL* pbSuccess) override
378 if (!m_info.m_packFile)
380 *pbChanged = VARIANT_FALSE;
381 *pbSuccess = VARIANT_FALSE;
385 String command = replaceMacros(m_info.m_packFile->m_command, fileSrc, fileDst);
386 if (m_info.m_packFile->m_script)
388 createScript(*m_info.m_packFile->m_script, scriptFile);
389 strutils::replace(command, _T("${SCRIPT_FILE}"), scriptFile.GetPath());
392 HRESULT hr = launchProgram(command, SW_HIDE, dwExitCode);
394 *pbChanged = SUCCEEDED(hr);
395 *pbSuccess = SUCCEEDED(hr);
401 String replaceMacros(const String& cmd, const String & fileSrc, const String& fileDst)
403 String command = cmd;
404 strutils::replace(command, _T("${SRC_FILE}"), fileSrc);
405 strutils::replace(command, _T("${DST_FILE}"), fileDst);
406 std::vector<StringView> vars = strutils::split(m_sVariables, '\0');
407 for (size_t i = 0; i < vars.size(); ++i)
408 strutils::replace(command, strutils::format(_T("${%d}"), i), { vars[i].data(), vars[i].length() });
409 strutils::replace(command, _T("${*}"), m_sArguments);
413 static HRESULT createScript(const Script& script, TempFile& tempFile)
415 String path = tempFile.Create(_T(""), script.m_fileExtension);
416 return WriteFile(path, script.m_body, false);
419 static HRESULT launchProgram(const String& sCmd, WORD wShowWindow, DWORD &dwExitCode)
422 String sOutputFile = stderrFile.Create();
423 if (_wgetenv(L"WINMERGE_HOME") == nullptr)
424 _wputenv_s(L"WINMERGE_HOME", env::GetProgPath().c_str());
425 String command = sCmd;
426 strutils::replace(command, _T("${WINMERGE_HOME}"), env::GetProgPath());
427 STARTUPINFO stInfo = { sizeof(STARTUPINFO) };
428 stInfo.dwFlags = STARTF_USESHOWWINDOW;
429 stInfo.wShowWindow = wShowWindow;
430 SECURITY_ATTRIBUTES sa{ sizeof(sa) };
431 sa.bInheritHandle = true;
432 stInfo.hStdError = CreateFile(sOutputFile.c_str(), GENERIC_READ | GENERIC_WRITE,
433 FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
434 stInfo.hStdOutput = stInfo.hStdError;
435 stInfo.dwFlags |= STARTF_USESTDHANDLES;
436 PROCESS_INFORMATION processInfo;
437 bool retVal = !!CreateProcess(nullptr, (LPTSTR)command.c_str(),
438 nullptr, nullptr, TRUE, CREATE_DEFAULT_ERROR_MODE, nullptr, nullptr,
439 &stInfo, &processInfo);
441 return HRESULT_FROM_WIN32(GetLastError());
442 WaitForSingleObject(processInfo.hProcess, INFINITE);
443 GetExitCodeProcess(processInfo.hProcess, &dwExitCode);
444 CloseHandle(processInfo.hThread);
445 CloseHandle(processInfo.hProcess);
446 DWORD dwStdErrorSize = 0;
447 if (stInfo.hStdError != nullptr && stInfo.hStdError != INVALID_HANDLE_VALUE)
449 DWORD dwStdErrorSizeHigh = 0;
450 dwStdErrorSize = GetFileSize(stInfo.hStdError, &dwStdErrorSizeHigh);
451 CloseHandle(stInfo.hStdError);
453 if (dwExitCode != 0 && dwStdErrorSize > 0)
456 ReadFile(sOutputFile, error);
457 ICreateErrorInfo* pCreateErrorInfo = nullptr;
458 if (SUCCEEDED(CreateErrorInfo(&pCreateErrorInfo)))
460 pCreateErrorInfo->SetSource(const_cast<OLECHAR*>(command.c_str()));
461 pCreateErrorInfo->SetDescription(const_cast<OLECHAR*>(ucr::toUTF16(error).c_str()));
462 IErrorInfo* pErrorInfo = nullptr;
463 pCreateErrorInfo->QueryInterface(&pErrorInfo);
464 SetErrorInfo(0, pErrorInfo);
465 pErrorInfo->Release();
466 pCreateErrorInfo->Release();
467 return DISP_E_EXCEPTION;
477 class EditorScriptGeneratedFromUnpacker: public WinMergePluginBase
480 EditorScriptGeneratedFromUnpacker(const PluginInfo& plugin, const String& funcname)
481 : WinMergePluginBase(
483 plugin.m_description,
484 plugin.m_filtersTextDefault, L"", plugin.m_extendedProperties, plugin.m_arguments)
485 , m_pDispatch(plugin.m_lpDispatch)
487 m_pDispatch->AddRef();
488 AddFunction(ucr::toUTF16(funcname), CallUnpackFile);
491 virtual ~EditorScriptGeneratedFromUnpacker()
493 m_pDispatch->Release();
496 static HRESULT STDMETHODCALLTYPE CallUnpackFile(IDispatch *pDispatch, BSTR text, BSTR* pbstrResult)
499 String fileSrc = src.Create();
500 String fileDst = dst.Create();
501 HRESULT hr = WriteFile(fileSrc, text);
506 auto* thisObj = static_cast<EditorScriptGeneratedFromUnpacker*>(pDispatch);
507 auto* pInternalPlugin = dynamic_cast<InternalPlugin*>(thisObj->m_pDispatch);
510 BSTR bstrFileSrc = SysAllocString(ucr::toUTF16(fileSrc).c_str());
511 BSTR bstrFileDst= SysAllocString(ucr::toUTF16(fileDst).c_str());
512 VARIANT_BOOL bChanged;
513 VARIANT_BOOL bSuccess;
514 hr = pInternalPlugin->UnpackFile(bstrFileSrc, bstrFileDst, &bChanged, &subcode, &bSuccess);
515 SysFreeString(bstrFileSrc);
516 SysFreeString(bstrFileDst);
522 if (!plugin::InvokeUnpackFile(fileSrc, fileDst, changed, thisObj->m_pDispatch, subcode))
526 hr = ReadFile(fileDst, unpackedText);
529 *pbstrResult = SysAllocStringLen(ucr::toUTF16(unpackedText).c_str(),
530 static_cast<unsigned>(unpackedText.length()));
535 IDispatch* m_pDispatch;
542 CAllThreadsScripts::RegisterInternalPluginsLoader(&Load);
545 static bool Load(std::map<String, PluginArrayPtr>& plugins, String& errmsg)
547 if (plugins.find(L"EDITOR_SCRIPT") != plugins.end())
549 for (auto plugin : *plugins[L"EDITOR_SCRIPT"])
551 if (!plugin->m_disabled && plugin->GetExtendedPropertyValue(_T("GenerateUnpacker")).has_value())
553 std::vector<String> namesArray;
554 std::vector<int> idArray;
555 int validFuncs = plugin::GetMethodsFromScript(plugin->m_lpDispatch, namesArray, idArray);
556 for (int i = 0; i < validFuncs; ++i)
558 if (plugins.find(L"FILE_PACK_UNPACK") == plugins.end())
559 plugins[L"FILE_PACK_UNPACK"].reset(new PluginArray);
560 PluginInfoPtr pluginNew(new PluginInfo());
561 IDispatch* pDispatch = new UnpackerGeneratedFromEditorScript(*plugin, namesArray[i], idArray[i]);
563 pluginNew->MakeInfo(namesArray[i], pDispatch);
564 plugins[L"FILE_PACK_UNPACK"]->push_back(pluginNew);
570 std::list<Info> internalPlugins;
571 XMLHandler handler(&internalPlugins);
573 parser.setContentHandler(&handler);
576 parser.parse(ucr::toUTF8(paths::ConcatPath(env::GetProgPath(), _T("MergePlugins\\Plugins.xml"))));
578 catch (Poco::XML::SAXParseException& e)
580 errmsg = ucr::toTString(e.message());
583 catch (Poco::FileNotFoundException&)
587 for (auto& info : internalPlugins)
589 String event = info.m_event;
590 String name = info.m_name;
591 if (plugins.find(event) == plugins.end())
592 plugins[event].reset(new PluginArray);
593 PluginInfoPtr pluginNew(new PluginInfo());
594 IDispatch* pDispatch = new InternalPlugin(std::move(info));
596 pluginNew->MakeInfo(name, pDispatch);
597 plugins[event]->push_back(pluginNew);
600 if (plugins.find(L"FILE_PACK_UNPACK") != plugins.end())
602 for (auto plugin : *plugins[L"FILE_PACK_UNPACK"])
604 if (!plugin->m_disabled && plugin->GetExtendedPropertyValue(_T("GenerateEditorScript")).has_value())
606 if (plugins.find(L"EDITOR_SCRIPT") == plugins.end())
607 plugins[L"EDITOR_SCRIPT"].reset(new PluginArray);
608 PluginInfoPtr pluginNew(new PluginInfo());
609 auto menuCaption = plugin->GetExtendedPropertyValue(_T("MenuCaption"));
610 String funcname = menuCaption.has_value() ? String{menuCaption->data(), menuCaption->length() } : plugin->m_name;
611 IDispatch* pDispatch = new EditorScriptGeneratedFromUnpacker(*plugin, funcname);
613 pluginNew->MakeInfo(plugin->m_name, pDispatch);
614 plugins[L"EDITOR_SCRIPT"]->push_back(pluginNew);