|
| 1 | +#include "OverrideMapUpdaterClass.h" |
| 2 | + |
| 3 | +// Experiment with concepts needed, might eventually morph into actual behavior |
| 4 | +OverrideMapUpdater::OverrideMapUpdater(void) |
| 5 | +{ |
| 6 | + gNppMetaInfo.populate(); |
| 7 | + |
| 8 | + // 1. Pick correct file: "cfg" directory (Settings/Cloud/AppData) or "app" directory, whichever exists |
| 9 | + _wsOverMapPath = gNppMetaInfo.dir.cfgFunctionList + L"\\overrideMap.xml"; |
| 10 | + _wsAppOverMapPath = gNppMetaInfo.dir.app + L"\\functionList\\overrideMap.xml"; |
| 11 | + bool needWriteToAppDir = false; |
| 12 | + bool needToCreateFile = false; |
| 13 | + |
| 14 | + if (!PathFileExists(_wsOverMapPath.c_str())) { |
| 15 | + // since .cfgFunctionList\overrideMap.xml doesn't exist, try to copy from app dir instead |
| 16 | + if (PathFileExists(_wsAppOverMapPath.c_str())) { |
| 17 | + // check if writeable |
| 18 | + if (pcjHelper::is_dir_writable(gNppMetaInfo.dir.cfgFunctionList)) { |
| 19 | + // if writable, copy the app version to the cfg location |
| 20 | + CopyFile(_wsAppOverMapPath.c_str(), _wsOverMapPath.c_str(), TRUE); |
| 21 | + } |
| 22 | + else { |
| 23 | + // if not writable, then need to write into the app directory instead |
| 24 | + needWriteToAppDir = true; |
| 25 | + } |
| 26 | + } |
| 27 | + else { |
| 28 | + // if neither XML exists, need to create one of them |
| 29 | + needToCreateFile = true; |
| 30 | + // pick the right one based on writability of cfg directory |
| 31 | + needWriteToAppDir = !pcjHelper::is_dir_writable(gNppMetaInfo.dir.cfgFunctionList); |
| 32 | + } |
| 33 | + |
| 34 | + // change the destination path if it needs to be in the app directory |
| 35 | + if (needWriteToAppDir) _wsOverMapPath = _wsAppOverMapPath; |
| 36 | + } |
| 37 | + |
| 38 | + // 2: need XML object |
| 39 | + pOverrideMapXML = new tinyxml2::XMLDocument; // create new one (old is discarded, if it exists) |
| 40 | + if (needToCreateFile) { |
| 41 | + // create the basic structure |
| 42 | + pRoot = pOverrideMapXML->NewElement("NotepadPlus"); |
| 43 | + pOverrideMapXML->InsertFirstChild(pRoot); |
| 44 | + |
| 45 | + pFunctionList = pRoot->InsertNewChildElement("functionList"); |
| 46 | + pAssociationMap = pFunctionList->InsertNewChildElement("associationMap"); |
| 47 | + |
| 48 | + pAssociationMap->InsertEndChild( |
| 49 | + pOverrideMapXML->NewComment( |
| 50 | + " \n" |
| 51 | + " See https://github.com/notepad-plus-plus/notepad-plus-plus/blob/master/PowerEditor/installer/functionList/overrideMap.xml for the \"official\" default for this file\n" |
| 52 | + " tincluding the default id-vs-langID values\n" |
| 53 | + " \n" |
| 54 | + " Each functionlist parse rule links to a language ID(\"langID\") or a UDL name.\n" |
| 55 | + " Examples:\n" |
| 56 | + " <association id=\"my_perl.xml\" langID=\"21\" />\n" |
| 57 | + " <association id=\"nppexec.xml\" userDefinedLangName=\"NppExec\" />\n" |
| 58 | + " " |
| 59 | + ) |
| 60 | + ); |
| 61 | + pAssociationMap->InsertEndChild( |
| 62 | + pOverrideMapXML->NewComment(" ==================== User Defined Languages ============================ ") |
| 63 | + ); |
| 64 | + |
| 65 | + |
| 66 | + /*tinyxml2::XMLElement* pAssoc =*/ add_udl_assoc("nppexec.xml", "NppExec"); |
| 67 | + } |
| 68 | + else { |
| 69 | + // load file and extract the main elements |
| 70 | + tinyxml2::XMLError eResult = pOverrideMapXML->LoadFile(sOverMapPath().c_str()); |
| 71 | + if(_xml_check_result(eResult, pOverrideMapXML, wsOverMapPath())) return; |
| 72 | + |
| 73 | + pRoot = pOverrideMapXML->FirstChildElement("NotepadPlus"); |
| 74 | + if (!pRoot) { |
| 75 | + _xml_check_result(tinyxml2::XML_ERROR_PARSING, pOverrideMapXML, wsOverMapPath()); |
| 76 | + return; |
| 77 | + } |
| 78 | + |
| 79 | + pFunctionList = pRoot->FirstChildElement("functionList"); |
| 80 | + if (!pFunctionList) { |
| 81 | + _xml_check_result(tinyxml2::XML_ERROR_PARSING, pOverrideMapXML, wsOverMapPath()); |
| 82 | + return; |
| 83 | + } |
| 84 | + |
| 85 | + pAssociationMap = pFunctionList->FirstChildElement("associationMap"); |
| 86 | + if (!pAssociationMap) { |
| 87 | + _xml_check_result(tinyxml2::XML_ERROR_PARSING, pOverrideMapXML, wsOverMapPath()); |
| 88 | + return; |
| 89 | + } |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +// add an <association> tag for a given UDL |
| 94 | +// converts wstring to string first |
| 95 | +tinyxml2::XMLElement* OverrideMapUpdater::add_udl_assoc(std::wstring wsFilename, std::wstring wsUDLname) |
| 96 | +{ |
| 97 | + return add_udl_assoc( |
| 98 | + pcjHelper::wstring_to_utf8(wsFilename), |
| 99 | + pcjHelper::wstring_to_utf8(wsUDLname) |
| 100 | + ); |
| 101 | +} |
| 102 | + |
| 103 | +// add an <association> tag for a given UDL |
| 104 | +// (skip if already exists) |
| 105 | +tinyxml2::XMLElement* OverrideMapUpdater::add_udl_assoc(std::string sFilename, std::string sUDLname) |
| 106 | +{ |
| 107 | + // first check if the UDL already exists in the <association> structure |
| 108 | + tinyxml2::XMLElement* pExist = _find_element_with_attribute_value(pAssociationMap, nullptr, "association", "userDefinedLangName", sUDLname, true); |
| 109 | + if (pExist) return pExist; |
| 110 | + |
| 111 | + // if it doesn't, then add it |
| 112 | + tinyxml2::XMLElement* pAssoc = pAssociationMap->InsertNewChildElement("association"); |
| 113 | + if (pAssoc) { |
| 114 | + pAssoc->SetAttribute("id", sFilename.c_str()); |
| 115 | + pAssoc->SetAttribute("userDefinedLangName", sUDLname.c_str()); |
| 116 | + } |
| 117 | + return pAssoc; |
| 118 | +} |
| 119 | + |
| 120 | +// add <association> tags for each filename,udl pair in the map |
| 121 | +std::vector<tinyxml2::XMLElement*> OverrideMapUpdater::add_udl_assoc(std::map<std::string, std::string> msUdls) |
| 122 | +{ |
| 123 | + std::vector<tinyxml2::XMLElement*> vpAssoc; |
| 124 | + for (const auto& pair : msUdls) { |
| 125 | + vpAssoc.push_back(add_udl_assoc(pair.first, pair.second)); |
| 126 | + } |
| 127 | + return vpAssoc; |
| 128 | +} |
| 129 | + |
| 130 | +// add <association> tags for each filename,udl pair in the map |
| 131 | +std::vector<tinyxml2::XMLElement*> OverrideMapUpdater::add_udl_assoc(std::map<std::wstring, std::wstring> mwsUdls) |
| 132 | +{ |
| 133 | + std::vector<tinyxml2::XMLElement*> vpAssoc; |
| 134 | + for (const auto& pair : mwsUdls) { |
| 135 | + vpAssoc.push_back(add_udl_assoc(pair.first, pair.second)); |
| 136 | + } |
| 137 | + return vpAssoc; |
| 138 | +} |
| 139 | + |
| 140 | +// private: case-insensitive std::string equality check |
| 141 | +bool _string_insensitive_eq(std::string a, std::string b) |
| 142 | +{ |
| 143 | + std::string a_copy = ""; |
| 144 | + std::string b_copy = ""; |
| 145 | + |
| 146 | + // ignore conversion of int to char implicit in the <algorithm>std::transform, which I have no control over |
| 147 | +#pragma warning(push) |
| 148 | +#pragma warning(disable: 4244) |
| 149 | + for (size_t i = 0; i < a.size(); i++) { a_copy += std::tolower(a[i]); } |
| 150 | + for (size_t i = 0; i < b.size(); i++) { b_copy += std::tolower(b[i]); } |
| 151 | +#pragma warning(pop) |
| 152 | + return a_copy == b_copy; |
| 153 | +} |
| 154 | + |
| 155 | +// private: case-insensitive std::string less-than check |
| 156 | +bool _string_insensitive_lt(std::string a, std::string b) |
| 157 | +{ |
| 158 | + std::string a_copy = ""; |
| 159 | + std::string b_copy = ""; |
| 160 | + |
| 161 | + // ignore conversion of int to char implicit in the <algorithm>std::transform, which I have no control over |
| 162 | +#pragma warning(push) |
| 163 | +#pragma warning(disable: 4244) |
| 164 | + for (size_t i = 0; i < a.size(); i++) { a_copy += std::tolower(a[i]); } |
| 165 | + for (size_t i = 0; i < b.size(); i++) { b_copy += std::tolower(b[i]); } |
| 166 | +#pragma warning(pop) |
| 167 | + return a_copy < b_copy; |
| 168 | +} |
| 169 | + |
| 170 | + |
| 171 | +// look for an element, based on {Parent, FirstChild, or both} which is of a specific ElementType, having a specific AttributeName with specific AttributeValue |
| 172 | +tinyxml2::XMLElement* OverrideMapUpdater::_find_element_with_attribute_value(tinyxml2::XMLElement* pParent, tinyxml2::XMLElement* pFirst, std::string sElementType, std::string sAttributeName, std::string sAttributeValue, bool caseSensitive) |
| 173 | +{ |
| 174 | + if (!pParent && !pFirst) return nullptr; |
| 175 | + tinyxml2::XMLElement* pMyParent = pParent ? pParent->ToElement() : pFirst->Parent()->ToElement(); |
| 176 | + tinyxml2::XMLElement* pFCE = pMyParent->FirstChildElement(sElementType.c_str()); |
| 177 | + if (!pFirst && !pFCE) return nullptr; |
| 178 | + tinyxml2::XMLElement* pFoundElement = pFirst ? pFirst->ToElement() : pFCE->ToElement(); |
| 179 | + while (pFoundElement) { |
| 180 | + // if this node has the right attribute pair, great! |
| 181 | + if (caseSensitive) { |
| 182 | + if (pFoundElement->Attribute(sAttributeName.c_str(), sAttributeValue.c_str())) |
| 183 | + return pFoundElement; |
| 184 | + } |
| 185 | + else { |
| 186 | + const char* cAttrValue = pFoundElement->Attribute(sAttributeName.c_str()); |
| 187 | + if (cAttrValue) { |
| 188 | + if (_string_insensitive_eq(sAttributeValue, cAttrValue)) |
| 189 | + return pFoundElement; |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + // otherwise, move on to next |
| 194 | + pFoundElement = pFoundElement->NextSiblingElement(sElementType.c_str()); |
| 195 | + } |
| 196 | + return pFoundElement; |
| 197 | +} |
| 198 | + |
| 199 | + |
| 200 | +// compares the XMLError result to XML_SUCCESS, and returns a TRUE boolean to indicate failure |
| 201 | +// p_doc defaults to nullptr, wsFilePath to L"" |
| 202 | +bool OverrideMapUpdater::_xml_check_result(tinyxml2::XMLError a_eResult, tinyxml2::XMLDocument* p_doc, std::wstring wsFilePath) |
| 203 | +{ |
| 204 | + if (a_eResult != tinyxml2::XML_SUCCESS) { |
| 205 | + std::string sMsg = std::string("XML Error #") + std::to_string(static_cast<int>(a_eResult)); |
| 206 | + if (p_doc != NULL) { |
| 207 | + sMsg += std::string(": ") + std::string(p_doc->ErrorStr()); |
| 208 | + if (p_doc->ErrorLineNum()) { |
| 209 | + sMsg += "\n\nI will try to open the file near that error."; |
| 210 | + } |
| 211 | + } |
| 212 | + ::MessageBoxA(NULL, sMsg.c_str(), "XML Error", MB_ICONWARNING | MB_OK); |
| 213 | + if (p_doc != NULL && p_doc->ErrorLineNum() && wsFilePath.size()) { |
| 214 | + if (::SendMessage(gNppMetaInfo.hwnd._nppHandle, NPPM_DOOPEN, 0, reinterpret_cast<LPARAM>(wsFilePath.c_str()))) { |
| 215 | + extern NppData nppData; // not in PluginDefinition.h |
| 216 | + |
| 217 | + // Get the current scintilla |
| 218 | + int which = -1; |
| 219 | + ::SendMessage(nppData._nppHandle, NPPM_GETCURRENTSCINTILLA, 0, (LPARAM)&which); |
| 220 | + HWND curScintilla = (which < 1) ? nppData._scintillaMainHandle : nppData._scintillaSecondHandle; |
| 221 | + |
| 222 | + // SCI_GOTOLINE in the current scintilla instance |
| 223 | + WPARAM zeroLine = static_cast<WPARAM>(p_doc->ErrorLineNum() - 1); |
| 224 | + ::SendMessage(curScintilla, SCI_GOTOLINE, zeroLine, 0); |
| 225 | + |
| 226 | + // do annotation |
| 227 | + ::SendMessage(curScintilla, SCI_ANNOTATIONCLEARALL, 0, 0); |
| 228 | + ::SendMessage(curScintilla, SCI_ANNOTATIONSETVISIBLE, ANNOTATION_BOXED, 0); |
| 229 | + ::SendMessageA(curScintilla, SCI_ANNOTATIONSETTEXT, zeroLine, reinterpret_cast<LPARAM>(p_doc->ErrorStr())); |
| 230 | + |
| 231 | + // need to stop |
| 232 | + // In ConfigUpdater, I needed to set `_doAbort = true;` to exit out of a loop, but that's needed in CollectionInterface |
| 233 | + }; |
| 234 | + } |
| 235 | + return true; |
| 236 | + } |
| 237 | + return false; |
| 238 | +}; |
0 commit comments