<scriptlet>
<implements type="Automation" id="dispatcher">
  <property name="PluginEvent">
    <get/>
  </property>
  <property name="PluginDescription">
    <get/>
  </property>
  <property name="PluginFileFilters">
    <get/>
  </property>
  <property name="PluginIsAutomatic">
    <get/>
  </property>
  <property name="PluginExtendedProperties">
    <get/>
  </property>
  <method name="PluginOnEvent"/>
  <method name="UnpackFile"/>
  <method name="PackFile"/>
  <method name="IsFolder"/>
  <method name="UnpackFolder"/>
  <method name="PackFolder"/>
  <method name="ShowSettingsDialog"/>
</implements>

<script language="JScript">

/*
    This is a plugin for WinMerge.
    It will display the text content of MS PowerPoint files.
    Copyright (C) 2016-2024 Takashi Sawanaka

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/


var REGKEY_PATH = "Plugins\\CompareMSPowerPointFiles.sct/";
var MsgCannotGetMacros = "${Cannot get Macros.\r\n" +
  "Enable 'Trust access to Visual Basic Project' in MS Office Macro Security.}";

var fso = new ActiveXObject("Scripting.FileSystemObject");
var wsh = new ActiveXObject("WScript.Shell");
var mergeApp;
var ppObjList = new Array();

function isAccessibleVBAProject(prs) {
  try {
    return (prs.VBProject.VBComponents.Count >= 0);
  } catch (e) {
    return false;
  }
}

function regRead(Key, DefaultValue) {
  return mergeApp.GetOption(Key, DefaultValue);
}

function regWrite(Key, Value, TypeNm) {
  mergeApp.SaveOption(Key, (TypeNm === "REG_DWORD") ? parseInt(Value, 10) : String(Value));
}

function toString(val) {
  switch (typeof val) {
  case "string":
    return val;
  case "number":
    return val.toString();
  case "date":
    var d = new Date(val);
    if (d.getHours() == 0 && d.getMinutes() == 0 && d.getSeconds() == 0 && d.getMilliseconds() == 0) {
        return d.toLocaleDateString();
    }
    return d.toLocaleString();
  case "undefined":
    return "";
  default:
    return String(val);
  }
}

function writeObjectProperties(fo, items) {
  try {
    for (var it = new Enumerator(items); !it.atEnd(); it.moveNext()) {
      try {
        var o = it.item();
        fo.WriteLine(o.Name + ": " + toString(o.Value));
      } catch (e) {}
    }
  } catch (e) {}
}

function writeShape(fo, shp) {
  var shpType = -1;
  try {
    shpType = shp.Type;
    fo.Write(shp.Name + ": ");
    if (shp.HasTable) {
      for (var r = 1; r <= shp.Table.Rows.Count; r++) {
        for (var c = 1; c <= shp.Table.Columns.Count; c++) {
          fo.Write("(" + r + ", " + c + "): ");
          writeShape(fo, shp.Table.Cell(r, c).Shape);
        }
      }
    } else if (shpType === 6) { //msoGroup = 6
      for (var it = new Enumerator(shp.GroupItems); !it.atEnd(); it.moveNext()) {
        var shp2 = it.item();
        writeShape(fo, shp2);
      }
    } else if (shp.HasTextFrame) {
      fo.WriteLine(shp.TextFrame.TextRange.Characters().Text);
    } else {
      fo.WriteLine("");
    }
  } catch (e) {}
}

function writeTextsInShapes(fo, p) {
  try {
    for (var it = new Enumerator(p.Shapes); !it.atEnd(); it.moveNext()) {
      var shp = it.item();
      writeShape(fo, shp);
      shp = null;
    }
  } catch (e) {}
}

function getModuleExtension(cmp) {
  switch (cmp.Type) {
  case 2:
    return ".cls";
  case 3:
    return ".frm";
  default:
    return ".bas";
  }
}

function saveSlideAsImage(sld, filename) {
  sld.Export(filename, "PNG");
}

function get_PluginEvent() {
  return "FILE_FOLDER_PACK_UNPACK";
}

function get_PluginDescription() {
  return "Display MS PowerPoint text content";
}

function get_PluginFileFilters() {
  return "\\.ppt(\\..*)?$;\\.pptx(\\..*)?$;\\.pptm(\\..*)?$;\\.ppa(\\..*)?$;\\.ppam(\\..*)?$;\\.potx(\\..*)?$;\\.potm(\\..*)?$";
}

function get_PluginIsAutomatic() {
  return true;
}

function get_PluginExtendedProperties() {
  return "ProcessType=Content Extraction;FileType=MS-PowerPoint;MenuCaption=MS-PowerPoint";
}

function PluginOnEvent(eventType, obj) {
  mergeApp = obj;
  if (eventType == 1) {
    for (var i = 0; i < ppObjList.length; i++) {
      ppObjList[i].Quit();
      ppObjList[i] = null;
    }
  }
}

function UnpackFile(fileSrc, fileDst, pbChanged, pSubcode) {
  var fo = fso.CreateTextFile(fileDst, true, true);
  var pp;
  try {
    pp = new ActiveXObject("PowerPoint.Application");
  } catch (e) {}
  if (!pp) {
    throw new Error(30001, translate("${%1 is not installed.}").replace("%1", "PowerPoint"));
  }
  pp.Visible = true;
  pp.DisplayAlerts = false;

  var fileSrc2 = fileSrc;
  if (fso.GetExtensionName(fileSrc2) === "lnk") {
    fileSrc2 = wsh.CreateShortcut(fileSrc2).TargetPath;
    if (!fso.FileExists(fileSrc2)) {
      throw new Error(30001, "CompareMSPowerPointFiles.sct: " + fileSrc + ": Target file '" + fileSrc2 + "' not found");
    } 
  }

  var prs = pp.Presentations.Open(fileSrc2, -1);

  if (regRead(REGKEY_PATH + "CompareDocumentProperties", false)) {
    fo.WriteLine("[Document Properties]");
    writeObjectProperties(fo, prs.BuiltinDocumentProperties);
    fo.WriteLine("");
  }
 
  for (var it = new Enumerator(prs.Slides); !it.atEnd(); it.moveNext()) {
    var sld = it.item();
    if (regRead(REGKEY_PATH + "CompareTextsInShapes", true)) {
      fo.WriteLine("[" + sld.Name + "]");
      writeTextsInShapes(fo, sld);
      fo.WriteLine("");
    }
    if (regRead(REGKEY_PATH + "CompareTextsInNotesPage", true)) {
      fo.WriteLine("[" + sld.Name + ".NotesPage]");
      writeTextsInShapes(fo, sld.NotesPage);
      fo.WriteLine("");
    }
    sld = null;
  }
 
  if (regRead(REGKEY_PATH + "CompareVBAMacros", true)) {
    if (!isAccessibleVBAProject(prs)) {
      fo.WriteLine(translate(MsgCannotGetMacros));
    } else {
      for (var it = new Enumerator(prs.VBProject.VBComponents); !it.atEnd(); it.moveNext()) {
        var cmp = it.item();
        fo.WriteLine("[CodeModule." + cmp.Name + "]");
        if (cmp.CodeModule.CountOfLines > 0) {
          fo.WriteLine(cmp.CodeModule.Lines(1, cmp.CodeModule.CountOfLines));
        }
        fo.WriteLine("");
        cmp = null;
      }
    }
  }

  prs.Saved = true;
  prs.Close();
  prs = null;
  pp.WindowState = 2; // ppWindowMinimized
  ppObjList.push(pp);
  fo.Close();
  fo = null;

  pbChanged = true;
  pSubcode = 0;

  CollectGarbage();

  var result = new ActiveXObject("Scripting.Dictionary");
  result.Add(0, true);
  result.Add(1, pbChanged);
  result.Add(2, pSubcode);
  return result.Items();
}

function PackFile(fileSrc, fileDst, pbChanged, pSubcode) {
  return false;
}

function IsFolder(file) {
  return regRead(REGKEY_PATH + "UnpackToFolder", false);
}

function UnpackFolder(fileSrc, folderDst, pbChanged, pSubcode) {
  if (!fso.FolderExists(folderDst)) { fso.CreateFolder(folderDst); }
  var pp;
  try {
    pp = new ActiveXObject("PowerPoint.Application");
  } catch (e) {}
  if (!pp) {
    throw new Error(30001, translate("${%1 is not installed.}").replace("%1", "PowerPoint"));
  }
  pp.Visible = true;
  pp.DisplayAlerts = false;

  var fileSrc2 = fileSrc;
  if (fso.GetExtensionName(fileSrc2) === "lnk") {
    fileSrc2 = wsh.CreateShortcut(fileSrc2).TargetPath;
    if (!fso.FileExists(fileSrc2)) {
      throw new Error(30001, "CompareMSPowerPointFiles.sct: " + fileSrc + ": Target file '" + fileSrc2 + "' not found");
    } 
  }
  var prs = pp.Presentations.Open(fileSrc2, -1);

  if (regRead(REGKEY_PATH + "CompareDocumentProperties", false)) {
    var fo = fso.CreateTextFile(fso.BuildPath(folderDst, "(0)DocumentProperties.txt"), true, true);
    writeObjectProperties(fo, prs.BuiltinDocumentProperties);
    fo.Close();
    fo = null;
  }
 
  for (var it = new Enumerator(prs.Slides); !it.atEnd(); it.moveNext()) {
    var sld = it.item();
    if (regRead(REGKEY_PATH + "CompareTextsInShapes", true)) {
      var fo = fso.CreateTextFile(fso.BuildPath(folderDst, sld.Name + ".txt"), true, true);
      writeTextsInShapes(fo, sld);
      fo.Close();
      fo = null;
    }
 
    if (regRead(REGKEY_PATH + "CompareTextsInNotesPage", true)) {
      var fo = fso.CreateTextFile(fso.BuildPath(folderDst, sld.Name + "_NotesPage.txt"), true, true);
      writeTextsInShapes(fo, sld.NotesPage);
      fo.Close();
      fo = null;
    }
 
    if (regRead(REGKEY_PATH + "CompareSlideAsImage", true)) {
      saveSlideAsImage(sld, fso.BuildPath(folderDst, sld.Name + ".png"));
    }
    sld = null;
  }
  if (regRead(REGKEY_PATH + "CompareVBAMacros", true)) {
    if (!isAccessibleVBAProject(prs)) {
      var fo = fso.CreateTextFile(fso.BuildPath(folderDst, "CannotGetMacros.bas"), true, true);
      fo.WriteLine(translate(MsgCannotGetMacros));
      fo.Close();
      fo = null;
    } else {
      for (var it = new Enumerator(prs.VBProject.VBComponents); !it.atEnd(); it.moveNext()) {
        var cmp = it.item();
        cmp.Export(fso.BuildPath(folderDst, cmp.Name + getModuleExtension(cmp)));
        cmp = nul;
      }
    }
  }

  prs.Saved = true;
  prs.Close();
  prs = null;
  pp.WindowState = 2; // ppWindowMinimized
  ppObjList.push(pp);

  pbChanged = true;
  pSubcode = 0;

  CollectGarbage();

  var result = new ActiveXObject("Scripting.Dictionary");
  result.Add(0, true);
  result.Add(1, pbChanged);
  result.Add(2, pSubcode);
  return result.Items();
}

function PackFolder(folderSrc, fileDst, pbChanged, pSubcode) {
  return false;
}

function translate(text) {
  var re = /\${([^}]+)}/g;
  var matches;
  while ((matches = re.exec(text)) != null) {
    text = text.replace(matches[0], mergeApp.Translate(matches[1]));
  }
  return text;
}

function ShowSettingsDialog() {
  var tname = fso.BuildPath(fso.GetSpecialFolder(2), fso.GetTempName() + ".hta");
  var xmlfile = fso.BuildPath(fso.GetSpecialFolder(2), fso.GetTempName() + ".xml");
  var tfile = fso.CreateTextFile(tname, true, true);
  tfile.Write(translate(getResource("dialog1")));
  tfile.Close();
  exportSettingsToXMLFile(xmlfile);
  var mshta = wsh.ExpandEnvironmentStrings("%SystemRoot%\\System32\\mshta.exe");
  if (!fso.FileExists(mshta)) {
    mshta = wsh.ExpandEnvironmentStrings("%SystemRoot%\\SysWOW64\\mshta.exe");
  }
  run(wsh, "\"" + mshta + "\" \"" + tname + "\"  \"" + xmlfile + "\"");
  importSettingsFromXMLFile(xmlfile);
  fso.DeleteFile(tname);
  fso.DeleteFile(xmlfile);
}

function run(sh, cmd) {
  sh.Run(cmd, 1, true);
}

function exportSettingsToXMLFile(filepath) {
  var key_defvalues = {
    "UnpackToFolder" : false,
    "CompareDocumentProperties" : false,
    "CompareSlideAsImage" : true,
    "CompareTextsInShapes" : true,
    "CompareTextsInNotesPage" : true,
    "CompareVBAMacros" : true
  };
  var doc = new ActiveXObject("MSXML2.DOMDocument");
  var fso = new ActiveXObject("Scripting.FileSystemObject");
  var ts = fso.OpenTextFile(filepath, 2, true, -1);
  var root = doc.createElement("properties");
  for (var key in key_defvalues) {
    var el = doc.createElement("property");
    var val = regRead(REGKEY_PATH + key, key_defvalues[key]);
    var cdata = doc.createCDATASection(val);
    el.appendChild(cdata);
    el.setAttribute("name", REGKEY_PATH + key);
    el.setAttribute("type", typeof val);
    root.appendChild(el);
  }
  doc.appendChild(root);
  ts.Write(doc.xml);
  ts.Close();
}

function importSettingsFromXMLFile(filepath) {
  var fso = new ActiveXObject("Scripting.FileSystemObject");
  var ts = fso.OpenTextFile(filepath, 1, true, -1);
  var xml = ts.ReadAll();
  var doc = new ActiveXObject("MSXML2.DOMDocument");
  doc.async = false;
  doc.loadXML(xml);
  ts.Close();
  var nodes = doc.documentElement.childNodes;
  for (var i = 0; i < nodes.length; i++) {
    regWrite(nodes[i].getAttribute("name"), nodes[i].text, (nodes[i].getAttribute("type") === "string") ? "REG_SZ" : "REG_DWORD");
  }
}

</script>

<resource id="dialog1">
<![CDATA[
<!DOCTYPE html>
<html>
  <head>
    <HTA:APPLICATION ID="objHTA">
    <title>${CompareMSPowerPointFiles.sct WinMerge Plugin Options}</title>
    <meta content="text/html" charset="UTF-16">
    <style>
    body { background-color: #f2f2f2; font-family: Arial, sans-serif; }
    .container { margin: 2em; }
    ul { list-style-type: none; margin: 0; padding: 0; }
    li ul li { padding-left: 2em }
    .btn-container { margin-top: 1.5em; text-align: right; }
    input[type="button"] { border: none; padding: 0.6em 2em; height: 2.5em; text-align: center; }
    .btn-ok { color: #fff; background-color: #05c; }
    .btn-ok:hover { background-color: #04b; }
    .btn-cancel { color: #333; background-color: #ddd; }
    .btn-cancel:hover { background-color: #ccc; }
    </style>
    <script type="text/javascript">
      var REGKEY_PATH = "Plugins\\CompareMSPowerPointFiles.sct/";
      var xmlFilePath;
      var settings = {};

      function regRead(key, defaultValue) {
        return settings.hasOwnProperty(key) ? settings[key] : defaultValue;
      }

      function regWrite(key, value, type) {
        settings[key] = (type === "REG_DWORD") ? Number(value) : String(value);
      }

      function loadSettingsFromXMLFile(filepath) {
        var fso = new ActiveXObject("Scripting.FileSystemObject");
        var ts = fso.OpenTextFile(filepath, 1, true, -1);
        var xml = ts.ReadAll();
        var doc = new ActiveXObject("MSXML2.DOMDocument");
        doc.async = false;
        doc.loadXML(xml);
        ts.Close();
        var nodes = doc.documentElement.childNodes;
        for (var i = 0; i < nodes.length; i++) {
          regWrite(nodes[i].getAttribute("name"), nodes[i].text, (nodes[i].getAttribute("type") === "string") ? "REG_SZ" : "REG_DWORD");
        }
        return settings;
      }

      function saveSettingsToXMLFile(filepath, settings) {
        var doc = new ActiveXObject("MSXML2.DOMDocument");
        var fso = new ActiveXObject("Scripting.FileSystemObject");
        var ts = fso.OpenTextFile(filepath, 2, true, -1);
        var root = doc.createElement("properties");
        for (var key in settings) {
          if (settings.hasOwnProperty(key)) {
            var el = doc.createElement("property");
            var val = settings[key];
            var cdata = doc.createCDATASection(val);
            el.appendChild(cdata);
            el.setAttribute("name", key);
            el.setAttribute("type", typeof val);
            root.appendChild(el);
          }
        }
        doc.appendChild(root);
        ts.Write(doc.xml);
        ts.Close();
      }

      function onload() {
        xmlFilePath = objHTA.commandLine.split('"')[3];
        settings = loadSettingsFromXMLFile(xmlFilePath);

        var dpi = window.screen.deviceXDPI;
        var w = 600 * dpi / 96, h = 400 * dpi / 96;
        window.resizeTo(w, h);
        window.moveTo((screen.width - w) / 2, (screen.height - h) / 2);

        chkUnpackToFolder.checked = regRead(REGKEY_PATH + "UnpackToFolder", false);
        chkCompareDocumentProperties.checked = regRead(REGKEY_PATH + "CompareDocumentProperties", false);
        chkCompareSlideAsImage.checked = regRead(REGKEY_PATH + "CompareSlideAsImage", true);
        chkCompareTextsInShapes.checked = regRead(REGKEY_PATH + "CompareTextsInShapes", true);
        chkCompareTextsInNotesPage.checked = regRead(REGKEY_PATH + "CompareTextsInNotesPage", true);
        chkCompareVBAMacros.checked = regRead(REGKEY_PATH + "CompareVBAMacros", true);
        chkUnpackToFolder_onclick();
        chkCompareSlideAsImage_onclick();
        document.onkeydown = onkeydown;
      }

      function onkeydown() {
        var k = event.keyCode;
        if (k == 13/*Enter*/) {
          btnOk_onclick();
        } else if (k == 27/*Escape*/) {
          btnCancel_onclick();
        }
      }

      function chkUnpackToFolder_onclick() {
        if (!chkUnpackToFolder.checked)
          chkCompareSlideAsImage.checked = false;
      }

      function chkCompareSlideAsImage_onclick() {
        if (chkCompareSlideAsImage.checked)
          chkUnpackToFolder.checked = true;
      }

      function btnOk_onclick() {
        regWrite(REGKEY_PATH + "UnpackToFolder", chkUnpackToFolder.checked, "REG_DWORD");
        regWrite(REGKEY_PATH + "CompareDocumentProperties", chkCompareDocumentProperties.checked, "REG_DWORD");
        regWrite(REGKEY_PATH + "CompareSlideAsImage", chkCompareSlideAsImage.checked, "REG_DWORD");
        regWrite(REGKEY_PATH + "CompareTextsInShapes", chkCompareTextsInShapes.checked, "REG_DWORD");
        regWrite(REGKEY_PATH + "CompareTextsInNotesPage", chkCompareTextsInNotesPage.checked, "REG_DWORD");
        regWrite(REGKEY_PATH + "CompareVBAMacros", chkCompareVBAMacros.checked, "REG_DWORD"); window.close();

        saveSettingsToXMLFile(xmlFilePath, settings);

        window.close();
      }

      function btnCancel_onclick() {
        saveSettingsToXMLFile(xmlFilePath, {});

        window.close();
      }

    </script>
  </head>
  <body onload="onload();">
    <div class="container">
      <ul>
        <li>
          <input id="chkUnpackToFolder" type="checkbox" onclick="chkUnpackToFolder_onclick();"/>
          <label for="chkUnpackToFolder">${Extract slide data to multiple files}</label>
        </li>
        <li>
          <input id="chkCompareDocumentProperties" type="checkbox" />
          <label for="chkCompareDocumentProperties">${Compare document properties}</label>
        </li>
        <li>
          <input id="chkCompareSlideAsImage" type="checkbox" onclick="chkCompareSlideAsImage_onclick();"/>
          <label for="chkCompareSlideAsImage">${Compare slides as image (very slow)}</label>
        </li>
        <li>
          <input id="chkCompareTextsInShapes" type="checkbox" />
          <label for="chkCompareTextsInShapes">${Compare texts in shapes}</label>
        </li>
        <li>
          <input id="chkCompareTextsInNotesPage" type="checkbox" />
          <label for="chkCompareTextsInNotesPage">${Compare texts in notes page}</label>
        </li>
        <li>
          <input id="chkCompareVBAMacros" type="checkbox" />
          <label for="chkCompareVBAMacros">${Compare VBA macros}</label>
        </li>
      </ul>
      <div class="btn-container">
        <input type="button" class="btn-ok" onclick="btnOk_onclick();" value="${OK}" />
        <input type="button" class="btn-cancel" onclick="btnCancel_onclick();" value="${Cancel}" />
      </div>
    </div>
  </body>
</html>
]]>
</resource>

</scriptlet>
