WSFファイルを1ファイルに結合する:wsf2jse.wsf

自作スクリプトです。便利なので公開します。
WSFへの結合版「wsfpack.wsf」もあります。

wsf2jse.wsf

実行画面

WSFファイルを1ファイルに結合する。

wsfファイルのXMLを解析して、1つのjseファイルに出力するスクリプトです。ファイルを分割すると開発するには便利ですが、実用には不向きなため、1ファイルにまとめます。

説明

cscript wsf2jse.wsf /?
wsf2jse Conversion tool.
使い方 : wsf2jse.wsf path [jobid]

オプション :

path  : WSF file path
jobid : job ID
  • wsfファイル名に「.jse」を追加したファイルを作成します
    • 既に存在する場合、上書きします
  • scriptタグのlanguage属性がJavaScriptかJScript以外の場合、動作を停止します
  • 外部スクリプトが指定位置に格納されていな場合、動作を停止します
  • wsfファイルをドラッグ&ドロップすることでも実行できます
  • 拡張子が「.jse」なのは、「.js」の関連付けを変更しているWindows環境が多いと考えたため、「.jse」を採用しています(「.js」拡張子でも動作します)
    • 「.jse」も「.js」同様のスクリプトです(「.exe」の様に実行されます)

ソースコード

wsf2jse.wsf<?xml version="1.0" encoding="UTF-16" standalone="yes" ?>
<package>
  <job>
    <?job error="false" debug="false" ?>
    <runtime>
      <description>wsf2jse Conversion tool.</description>
      <unnamed name="path"  many="false" required="true"  helpstring="WSF file path" />
      <unnamed name="jobid" many="false" required="false" helpstring="job ID" />
    </runtime>
    <script language="JavaScript">
//<![CDATA[
/*!
 * wsf2jse.wsf v5
 *
 * Copyright (c) 2018 toshi - https://www.bugbugnow.net/p/profile.html
 * Released under the MIT license.
 * see https://opensource.org/licenses/MIT
 */

/**
 * WSFファイルを1ファイルに結合する
 * wsfファイルのXMLを解析して、1つのjseファイルに出力するスクリプトです。
 * @requires    module:WScript
 * @auther      toshi(https://www.bugbugnow.net/p/profile.html)
 * @version     5
 * @see         1 - 20180512 - add - 初版
 * @see         2 - 20180513 - update - リファクタリング
 * @see         3 - 20190403 - update - Process.jsライブラリから分離
 * @see         4 - 20190525 - fix - 相対パスが正常に読み込めない問題修正
 * @see         5 - 20200116 - update - FileUtility.jsから分離(単独ファイル化)
 */
(function () {
  "use strict";

  var fs = new ActiveXObject('Scripting.FileSystemObject');
  var sh = new ActiveXObject('WScript.Shell');

  /**
   * PrivateUnderscore.js
   * @version   5
   */
  {
    function _FileUtility_createFolder(folderpath) {
      var ret = false,
          fullpath = fs.GetAbsolutePathName(folderpath);
      if (!(fs.FolderExists(fullpath) || fs.FileExists(fullpath))) {
        var parentpath = fs.GetParentFolderName(fullpath);
        if (parentpath != '') {
          _FileUtility_createFolder(fs.GetParentFolderName(fullpath));
        }
        try {
          fs.CreateFolder(fullpath);
          ret = true;
        } catch (e) {
          // ファイルが見つかりません(パス長問題) || パスが見つかりません(パス不正 || 存在しない)
        }
      }
      return ret;
    };
    function _FileUtility_createFileFolder(filepath) {
      var ret = false;
      var fullpath  = fs.GetAbsolutePathName(filepath);
      var parentpath= fs.GetParentFolderName(fullpath);
      if (parentpath != '') {
        ret = _FileUtility_createFolder(parentpath);
      }
      return ret;
    };
    function _storeText(src, path, opt_option, opt_charset, opt_bom) {
      var option  = (opt_option === true)?  2:
                    (opt_option === false)? 1:
                    (opt_option == null)?   1: opt_option;
      var charset = (opt_charset== null)?   'utf-8': opt_charset;
      var bom     = (opt_bom    == null)?   true: opt_bom;
      var fullpath, skip, bin;
      var ret = true;

      // 前処理
      charset = charset.toLowerCase();
      skip = {};
      skip['utf-8'] = 3;
      skip['utf-16'] = 2;
      skip['utf-16le'] = 2;
      // UTF-16BEは、スキップ不要(ADODB.StreamがBOMを書き込まないため)
      fullpath = fs.GetAbsolutePathName(path);

      // (存在しない場合)フォルダを作成する
      _FileUtility_createFileFolder(fullpath);

      // ファイルに書き込む。
      var sr = new ActiveXObject('ADODB.Stream');
      sr.Type = 2;
      sr.Charset = charset;
      sr.Open();
      sr.WriteText(src);
      if (bom === true && charset == 'utf-16be') {
        // ADODB.Streamは、UTF-16BEのBOMを書き込まないため、自力でBOMを書き込む
        // LEのBOMを確保
        var le = new ActiveXObject('ADODB.Stream');
        le.Type = 2;
        le.Charset = 'utf-16le';
        le.Open();
        le.WriteText('');
        le.Position = 0;
        le.Type = 1;

        // BEのバイナリを確保
        var be = sr;
        be.Position = 0;
        be.Type = 1;
        bin = be.Read();
        be.Close();
        be  = null;
        sr  = null;

        // 再度BOMありを書き込み
        sr = new ActiveXObject('ADODB.Stream');
        sr.Type = 1;
        sr.Open();

        // BOM(LEの1Byteと2Byteが逆)を書き込み
        // BEのバイナリを書き込み
        le.Position = 1;
        sr.Write(le.Read(1));
        le.Position = 0;
        sr.Write(le.Read(1));
        if (bin != null)  sr.Write(bin);

        le.Close();
        le  = null;
      }
      if (bom === false && skip[charset]) {
        // BOMなし書込処理
        var pre = sr;
        pre.Position = 0;
        pre.Type = 1;
        // skipバイト(BOM)を読み飛ばす
        pre.Position = skip[charset];
        bin = pre.Read();
        pre.Close();
        pre = null;
        sr  = null;

        // 再度BOMなしを書き込み
        sr = new ActiveXObject('ADODB.Stream');
        sr.Type = 1;
        sr.Open();
        if (bin != null)  sr.Write(bin);
      }
      try {
        sr.SaveToFile(fullpath, option);
      } catch (e) {       // ADODB.Stream: ファイルへ書き込めませんでした。
        // ファイルあり時、上書きなし
        ret = false;
      }
      sr.Close();
      sr = null;
      return ret;
      // 補足:LineSeparatorプロパティは、全行読み出しのため、無意味
    };
    function _loadText(path, opt_charset) {
      var ret, fullpath, 
          charset = opt_charset,
          skip = false;

      if (charset == null) { charset = '_autodetect_all'; }
      charset = charset.toLowerCase();

      fullpath = fs.GetAbsolutePathName(path);
      if (!fs.FileExists(fullpath)) {
        // ファイルなし
        return null;
      } else if (fs.GetFile(fullpath).size === 0) {
        // 空ファイル
        return '';
      }

      var sr = new ActiveXObject('ADODB.Stream');
      sr.Type = 2;
      if (charset == '_autodetect_all' || charset == 'utf-16be') {
        // BOMを確認してUTF-8とUTF-16だけ、手動で判定する
        // UTF-16BEは、BOMあり時にBOM削除されないため、手動でスキップする
        var pre = new ActiveXObject('ADODB.Stream');
        pre.Type = 2;
        pre.Charset = 'us-ascii';
        pre.Open();
        pre.LoadFromFile(fullpath);
        var bom = [];
        bom.push(pre.EOS || escape(pre.ReadText(1)));
        bom.push(pre.EOS || escape(pre.ReadText(1)));
        bom.push(pre.EOS || escape(pre.ReadText(1)));
        if (charset == 'utf-16be') {
          if (bom[0] == '%7E' && bom[1]== '%7F') {
            skip = true;
          }
        } else if (bom[0] == 'o'   && bom[1]== '%3B' && bom[2]== '%3F') {
          charset = 'utf-8';
        } else if (bom[0] == '%7F' && bom[1]== '%7E') {
          charset = 'utf-16le';
        } else if (bom[0] == '%7E' && bom[1]== '%7F') {
          charset = 'utf-16be';
          skip = true;
        }
        pre.Close();
        pre = null;
      }
      sr.Charset = charset;

      // ファイルから読み出し
      sr.Open();
      sr.LoadFromFile(fullpath);
      if (skip) {
        // 先頭一文字(BOM)を空読み
        sr.ReadText(1);
        ret = sr.ReadText();
      } else {
        ret = sr.ReadText();
      }

      // 終了処理
      sr.Close();
      sr = null;
      return ret;
    };
    function _Process_createActiveXObjects(progIDs) {
      for (var i=0; i<progIDs.length; ++i) {
        try {
          return new ActiveXObject(progIDs[i]);
        } catch (e) {
          if (i == progIDs.length - 1) {  throw e;  }
        }
      }
      return null;
    };
    function _Process_createDOMDocument() {
      return _Process_createActiveXObjects([
        'MSXML2.DOMDocument.6.0',
        'MSXML2.DOMDocument.3.0',
        'Msxml2.DOMDocument',
        'Msxml.DOMDocument',
        'Microsoft.XMLDOM']);
    };
    function _Process_getArguments() {
      var arg = [];
      for (var i=0; i<WScript.Arguments.Unnamed.length; i++) {
        arg.push(WScript.Arguments.Unnamed.Item(i));
      }
      return arg;
    };
  }

  // 標準出力
  var stdout  = '';
  function println(message) {
    stdout += ((message !== void 0) ? message: '')+'\n';
  }

  function main() {
    var args = _Process_getArguments();
    var path = args[0];
    var jobid= args[1];

    var fullpath = fs.GetAbsolutePathName(path);
    if (fs.FileExists(fullpath) == false) {
      WScript.Echo('file not found.\n'+fullpath);
      return -1;
    } else if (fs.GetExtensionName(fullpath).toLowerCase() != 'wsf') {
      WScript.Echo('wsf file not found.');
      return -2;
    } else {
      // ルートディレクトリをパックファイルのパスに設定(相対パス対策)
      sh.CurrentDirectory = fs.GetParentFolderName(fullpath);

      // wsfを読込み
      var text = _loadText(fullpath)
                            .replace(/\r\n?/g,'\n');
      var xml = _Process_createDOMDocument();
      xml.loadXML(text);

      // wsfを解析
      var job = xml.selectSingleNode((jobid == null)? '//job': '//job[@id="'+jobid+'"]');
      var descript = null;
      var scripts = [];
      if (job != null) {
        descript = job.selectSingleNode('runtime/description');
        scripts  = job.selectNodes('script');
      }
      if (scripts.length == 0) {
        WScript.Echo('script not found.');
        return -3;
      }

      // ヘッダ部書込み
      var lines = [];
      var filename = fs.GetFileName(fullpath);
      lines.push('// ==wsf2jse:header==');
      lines.push('//     file: '+filename);
      for (var i1=0; i1<scripts.length; i1++) {
        var src = scripts[i1].getAttribute('src');
        if (src != null) {
          lines.push('//  subfile: '+src);
        }
        // スクリプトの言語をチェック
        var lang = scripts[i1].getAttribute('language');
        if (lang == null) {}
        else if (lang.toLowerCase() == 'jscript') {     continue;  }
        else if (lang.toLowerCase() == 'javascript') {  continue;  }
        // 非対応言語
        WScript.Echo('unsupported script is included.');
        return -4;
      }
      if (descript != null) {
        var descripts = descript.text.split('\n');
        for (var i2=0; i2<descripts.length; i2++) {
          lines.push('// descript: '+descripts[i2]);
        }
      }
      lines.push('// ==/wsf2jse:header==');
      lines.push('');

      // スクリプト書き込み
      println(filename);
      lines.push('// ==wsf2jse:'+filename+'==');
      for (var i3=0; i3<scripts.length; i3++) {
        var src = scripts[i3].getAttribute('src');
        if (src == null) {
        } else if (!fs.FileExists(fs.GetAbsolutePathName(src))) {
          WScript.Echo('file not found.\n'+src);
          return -5;
        } else {
          println(src);
          lines.push('// ==wsf2jse:'+src+'==');
          lines.push(_loadText(src));
          lines.push('// ==/wsf2jse:'+src+'==');
        }
        if (scripts[i3].text != '') {
          lines.push(scripts[i3].text);
        }
      }
      lines.push('// ==/wsf2jse:'+filename+'==');

      // ファイル出力
      _storeText(lines.join('\n'), fullpath+'.jse', true, 'utf-16', true);

      println();
      println('complete.');
      WScript.Echo(stdout);
    }
    return 0;
  }

  main();
})();
//]]>
    </script>
  </job>
</package>

変更履歴

更新日説明
2018/05/12v1 - add - 初版
2018/05/13v2 - update - リファクタリング
2019/04/03v3 - update - Process.jsライブラリから分離
2019/05/25v4 - fix - 相対パスが正常に読み込めない問題修正
2020/01/16v5 - update - FileUtility.jsから分離(単独ファイル化)