Code: Select all
///Validates selected XML fragment or whole document, pointing potential error place
// http://akelpad.sourceforge.net/forum/viewtopic.php?p=15256#p15256
// https://github.com/FeyFre/akelpad-customisations/blob/scripts/XMLValidator.js
// Version: ???
// Version: 2.0 (2016.02.28) by VladSh
// Version: 1.9 (2016.02.24) by Skif_off
// Version: 1.8 (2015.04.10) by VladSh
// Version: 1.7 (2014.12.01) by VladSh
// Version: 1.6 (2012.10.24) by VladSh
// Version: 1.0 (2011.10.25) (c) Panych Y.W. aka FeyFre
//
// Arguments:
//   validate:
//      • [true | без параметра] - производить проверку синтаксиса xml и валидацию согласно схеме;
//      • false - только проверку правильности структуры xml
//   parse2gt:
//      • [false | без параметра] - не производить проверку удвоенного символа '>'
//      • true - производить
//   keep:
//      • [0 | без параметра] - не сохранять исходное выделение, устанавливая курсор в место ошибки
//      • 1 - сохранять исходное выделение; если выделения нет, то курсор также будет установлен в место ошибки
//   msgOpts - параметры вывода сообщений:
//      • [0 | без параметра] - в диалоговое окно;
//      • перечень кодов сообщений через запятую, которые выводить в панели вывода Log-плагина:
//         1 - все сообщения/ошибки;
//         16 - критическая ошибка xml-парсера;
//         48 - ошибка xml-синтаксиса (можно выводить только её, т.к. можно переходить на место ошибки);
//         64 - сообщение об успешно пройденной валидации
//   append - параметры вывода в панель Log-плагина:
//      • [0 | без параметра] - очищать содержимое панели перед записью
//      • 1, 2 - не очищать (см. Log-Rus.txt -> вызов с кодом 4 -> параметр "APPEND")
//   lClose - параметры закрытия панели Log-плагина (при msgOpts=1 игнорируется):
//      • [0 | без параметра] - не закрывать (в панели может остаться нужный текст, в т.ч. и от других вкладок программы);
//      • 1 - закрывать только при успешно пройденной валидации;
//      • 2 - закрывать всегда (только при msgOpts=0)
//
// Examples:
//    Call("Scripts::Main", 1, "XMLValidator.js")               - вывод в обычное диалоговое сообщение
//    Call("Scripts::Main", 1, "XMLValidator.js", `-msgOpts=1`) - вывод всех сообщений в панель Log-плагина
//    Call("Scripts::Main", 1, "XMLValidator.js", `-msgOpts=48 -lClose=1`) - вывод в панель Log-плагина только сообщений об ошибках xml-синтаксиса с закрытием панели при успешно пройденной валидации
//    Call("Scripts::Main", 1, "XMLValidator.js", `-msgOpts=48 -append=1`) - вывод в панель Log-плагина только сообщений об ошибках xml-синтаксиса с добавлением их к уже существующим в панели сообщениям
var hWndEdit = AkelPad.GetEditWnd();
var parserName = "msxml2.DOMDocument";
var xml = new ActiveXObject(parserName);
try {
   xml.async = false;
   xml.validateOnParse = AkelPad.GetArgValue("validate", true);
   xml.resolveExternals = false;
} catch (e) {
   output("Internal parser " + parserName + " error: " + e.description, 16 /*MB_ICONSTOP*/);
}
var text = AkelPad.GetSelText();
var selection;
var bKeep = false;
if (!text) {
   text = AkelPad.GetTextRange(0, -1);
   selection = false;
} else {
   selection = true;
   var npStart = AkelPad.GetSelStart();
   var npEnd = AkelPad.GetSelEnd();
   bKeep = AkelPad.GetArgValue("keep", 0);
   var npMin = Math.min(AkelPad.GetSelStart(), AkelPad.GetSelEnd());
}
var filepos, linepos, oLine;
var linenum = 0;
var t = new Date();
var extInfo = t.toLocaleTimeString() + " " + (AkelPad.GetEditFile(0) || "*") + " -> ";
var aFind = [];
xml.loadXML(text);
if (xml.parseError.errorCode !== 0) {
   var err = xml.parseError;
   linepos = err.linepos;
   if (selection) {
      filepos = npMin + err.filepos;
      oLine = getLogLine(filepos);
      linenum = oLine.linenum;
   }
   else {
      linenum = err.line;
      filepos = err.filepos;
   }
   if (!bKeep)
      AkelPad.SetSel(filepos, filepos);
   output(extInfo + "XML validation error:\r(" + linenum + "," + linepos + ")\t\t" + err.reason, 48 /*MB_ICONEXCLAMATION*/);
} else {
   if (AkelPad.GetArgValue("parse2gt", false)) {
		if (FindText(0, "<([^>\\s]++)\\s*\\K>>++(?![^<]*+</\\1)", selection ? 0x00480001 : 0x00280001 /*FRF_SELECTION|FRF_BEGINNING|FRF_REGEXP|FRF_DOWN*/, aFind)) {
			filepos = aFind[0];
		} else {
		if (aFind[0])
			WScript.Echo("Error in regular expression at position: " + (aFind[0] - 1));
//		else
//			WScript.Echo("Not found!");
		}
      if (filepos >= 0) {
         filepos += 1;
//         if (selection)
//            filepos = npMin + filepos;
         oLine = getLogLine(filepos);
         linenum = oLine.linenum;
         linepos = filepos - oLine.startpos;
         if (!bKeep)
            AkelPad.SetSel(filepos, filepos);
         output(extInfo + "XML parsing error:\r(" + linenum + "," + linepos + ")\t\t'>>' was found.", 48 /*MB_ICONEXCLAMATION*/);
      }
   }
   output(extInfo + "XML fragment is valid.\r", 64 /*MB_ICONINFORMATION*/);
}
function output(msg, nIcon /*MB_ICON...*/) {
   var slog = AkelPad.GetArgValue("msgOpts", "0");
   if (slog == "1" || (slog != "0" && slog.indexOf(nIcon.toString()) != -1)) {
      var fLogOutput = "Log::Output";
      if (AkelPad.Call(fLogOutput, 1, "", "", "^\\((\\d+),(\\d+)\\)", "/GOTOLINE=\\1:\\2") != -1) {
         var nAppend = AkelPad.GetArgValue("append", 0);
         AkelPad.Call(fLogOutput, 4, msg, -1, nAppend);
      }
   } else {
      var lClose = AkelPad.GetArgValue("lClose", 0);
      if ((lClose == 1 && nIcon == 64) || lClose == 2)
         AkelPad.Call("Log::Output", 6);
      AkelPad.MessageBox(hWndEdit, msg, WScript.ScriptName, nIcon);
   }
   WScript.Quit();
}
// Function returns a LineObject for Log-panel
function getLogLine(filepos) {
   var wrpLine = AkelPad.SendMessage(hWndEdit, 1078 /*EM_EXLINEFROMCHAR*/, 0, filepos);
   var start = AkelPad.SendMessage(hWndEdit, 187 /*EM_LINEINDEX*/, wrpLine, 0);
   var unwrpLine = AkelPad.SendMessage(hWndEdit, 3143 /*AEM_GETUNWRAPLINE*/, wrpLine, 0);
   return {
      linenum: unwrpLine + 1,
      startpos: start - 1
   };
}
function FindText(hEditWnd, sFindIt, nFlags, aFind)
{
  var nAEFR  = 0x00200000 /*AEFR_REGEXPMINMATCH*/;
  var lpText = AkelPad.MemAlloc((sFindIt.length + 1) * 2);
  var lpFind = AkelPad.MemAlloc(_X64 ? 136 : 68 /*sizeof(AEFINDTEXTW)*/);
  var nAEGI1;
  var nAEGI2;
  var bFound;
  if (! hEditWnd)
    hEditWnd = AkelPad.GetEditWnd();
  aFind.length = 0;
  if ((nFlags & 0x00000001 /*FRF_DOWN*/) || (nFlags & 0x00200000 /*FRF_BEGINNING*/) || (nFlags & 0x00400000 /*FRF_SELECTION*/))
    nAEFR |= 0x00000001 /*AEFR_DOWN*/;
  if (nFlags & 0x00000002 /*FRF_WHOLEWORD*/)
    nAEFR |= 0x00000002 /*AEFR_WHOLEWORD*/;
  if (nFlags & 0x00000004 /*FRF_MATCHCASE*/)
    nAEFR |= 0x00000004 /*AEFR_MATCHCASE*/;
  if (nFlags & 0x00040000 /*FRF_REGEXPNONEWLINEDOT*/)
    nAEFR |= 0x00100000 /*AEFR_REGEXPNONEWLINEDOT*/;
  if (nFlags & 0x00080000 /*FRF_REGEXP*/)
    nAEFR |= 0x00080000 /*AEFR_REGEXP*/;
  if (nFlags & 0x00200000 /*FRF_BEGINNING*/)
  {
    nAEGI1 = 1 /*AEGI_FIRSTCHAR*/;
    nAEGI2 = 2 /*AEGI_LASTCHAR*/;
  }
  else if (nFlags & 0x00400000 /*FRF_SELECTION*/)
  {
    nAEGI1 = 3 /*AEGI_FIRSTSELCHAR*/;
    nAEGI2 = 4 /*AEGI_LASTSELCHAR*/;
  }
  else if (nFlags & 0x00000001 /*FRF_DOWN*/)
  {
    nAEGI1 = 5 /*AEGI_CARETCHAR*/;
    nAEGI2 = 2 /*AEGI_LASTCHAR*/;
  }
  else //(nFlags & FRF_UP)
  {
    nAEGI1 = 1 /*AEGI_FIRSTCHAR*/;
    nAEGI2 = 5 /*AEGI_CARETCHAR*/;
  }
  AkelPad.MemCopy(lpText, sFindIt, 1 /*DT_UNICODE*/);
  AkelPad.MemCopy(lpFind /*dwFlags*/, nAEFR, 3 /*DT_DWORD*/);
  AkelPad.MemCopy(_PtrAdd(lpFind, _X64 ?  8 :  4) /**pText*/, lpText, 2 /*DT_QWORD*/);
  AkelPad.MemCopy(_PtrAdd(lpFind, _X64 ? 16 :  8) /*dwTextLen*/, sFindIt.length, 2 /*DT_QWORD*/);
  AkelPad.MemCopy(_PtrAdd(lpFind, _X64 ? 24 : 12) /*nNewLine*/, 3 /*AELB_ASIS*/, 3 /*DT_DWORD*/);
  AkelPad.SendMessage(hEditWnd, 3130 /*AEM_GETINDEX*/, nAEGI1, _PtrAdd(lpFind, _X64 ?  32 : 16) /*crSearch.ciMin*/);
  AkelPad.SendMessage(hEditWnd, 3130 /*AEM_GETINDEX*/, nAEGI2, _PtrAdd(lpFind, _X64 ?  56 : 28) /*crSearch.ciMax*/);
  if (bFound = AkelPad.SendMessage(hEditWnd, 3041 /*AEM_FINDTEXTW*/, 0, lpFind))
  {
    aFind[0] = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, _PtrAdd(lpFind, _X64 ?  80 : 40) /*crFound.ciMin*/);
    aFind[1] = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, _PtrAdd(lpFind, _X64 ? 104 : 52) /*crFound.ciMax*/);
  }
  else
    aFind[0] = AkelPad.MemRead(_PtrAdd(lpFind, _X64 ? 128 : 64) /*nCompileErrorOffset*/, 2 /*DT_QWORD*/);
  AkelPad.MemFree(lpText);
  AkelPad.MemFree(lpFind);
  return bFound;
}