1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HTAで納品時用Excel整頓ツールを作るよ

1
Last updated at Posted at 2025-12-12

HTAで納品時用Excel整頓ツールを作るよ

これの焼き直し

なぜ、WSHやHTAにこだわるか

→ 客先などでアプリを自由にインストールできない時の為。

以下に対応してるよ

  • 全シートA1へ移動
  • 全セルを指定フォントへ変更
  • 全セルを指定フォントサイズへ変更
  • ウィンドウ表示形式を指定形式へ変更
  • ズーム値を指定値へ変更
  • 印刷向きを指定形式へ変更
  • 印刷領域を指定値へ変更
  • 用紙サイズを指定値へ変更
  • 拡大縮小を指定形式へ変更
  • シート1を選択状態にする
  • 処理フォルダ指定で、複数ファイル一気に整頓

## 本体(TidyExcel.hta)
ソース
utf-8で保存する
<!doctype html>
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta charset="utf-8">
<title>Excel納品用ツール</title>
<head><meta charset="utf-8"/><style type="text/css"><!-- body{font-family:'メイリオ',Meiryo;}ul{padding-left: 0px; list-style-type: none;}span.box-left{display: inline-block; width: 35%; margin-right: 2%; vertical-align: middle;}span.box-right{display: inline-block; width: 40%; vertical-align: middle;}li[id="wrapper"]{text-align:center;}input[type="button"]{font-size: 1.4em; font-weight: bold; padding: 10px 30px; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px;}--> progress {width: 90%;} #progressBar {width: 0%; height: 30px; background-color: green; text-align: center; line-height: 30px; color: white;} .status-text{ font-family: Arial, sans-serif;} #status { font-size: 12px; }</style></head>

<body>
<form><ul>
<li>対象フォルダ:
<input type="text" id="folderPath" style="width: 50%;" readonly>
<button onclick="selectFolder();return false;">...</button>
</li>
<br/>
<li>整頓条件を設定してください</li><br/>
<li><span class="box-left">フォント</span><span class="box-right"><select id="font"><option value="" selected>変更しない</option><option value="MS Pゴシック">MS Pゴシック</option><option value="MS ゴシック">MSゴシック</option><option value="游ゴシック">游ゴシック</option><option value="HGPゴシックM">HGPゴシックM</option><option value="HGP明朝B">HGP明朝B</option><option value="HGP明朝E">HGP明朝E</option><option value="HGP行書体">HGP行書体</option><option value="HGP教科書体">HGP教科書体</option><option value="HGP創英角ゴシックUB">HGP創英角ゴシックUB</option><option value="HGP創英角ポップ体">HGP創英角ポップ体</option><option value="HGP創英プレゼンスEB">HGP創英プレゼンスEB</option><option value="HG丸ゴシックM-Pro">HG丸ゴシックM-Pro</option><option value="HG正楷書体PRO">HG正楷書体PRO</option><option value="メイリオ">メイリオ</option><option value="Meiryo UI">Meiryo UI</option><option value="Arial Unicode MS">Arial Unicode MS</option><option value="Broadway">Broadway</option><option value="Calibri">Calibri</option><option value="Cambria & Cambria Math">Cambria & Cambria Math</option><option value="Consolas">Consolas</option></select></span></li>
<li><span class="box-left">フォントサイズ</span><span class="box-right"><select id="fontSize"><option value="" selected>変更しない</option><option value="6">6</option><option value="8">8</option><option value="9">9</option><option value="10">10</option><option value="10.5">10.5</option><option value="11">11</option><option value="12">12</option><option value="14">14</option><option value="16">16</option><option value="18">18</option><option value="20">20</option><option value="22">22</option><option value="24">24</option><option value="26">26</option><option value="28">28</option></select></span></li>
<li><span class="box-left">ウィンドウ表示</span><span class="box-right"><select id="xlWindowView"><option value="" selected>変更しない</option><option value="xlNormalView">標準</option><option value="xlPageBreakPreview">改ページ プレビュー</option><option value="xlPageLayoutView">ページレイアウト</option></select></span></li>
<li><span class="box-left">ズーム</span><span class="box-right"><input type="text" id="zoom" value="100"/></span></li><br/>
<li><span class="box-left">用紙方向</span><span class="box-right"><select id="xlPageOrientation"><option value="" selected>変更しない</option><option value="xlPortrait">縦方向</option><option value="xlLandscape">横方向</option></select></span></li>
<li><span class="box-left">印刷範囲</span><span class="box-right"><input type="text" id="printArea" value=""/></span></li>
<li><span class="box-left">用紙サイズ</span><span class="box-right"><select id="xlPaperSize"><option value="" selected>変更しない</option><option value="xlPaperA3">A3</option><option value="xlPaperA4">A4</option><option value="xlPaperA5">A5</option><option value="xlPaperB4">B4</option><option value="xlPaperB5">B5</option></select></span></li>
<li><span class="box-left">拡大縮小</span><span class="box-right"><select id="pageSetupZoom"><option value="" selected>変更しない</option><option value="zoomOff">拡大縮小なし</option><option value="Sheet1page">シートを1ページに印刷</option><option value="column1page">全ての列を1ページに印刷</option><option value="row1page">全ての行を1ページに印刷</option></select></span></li><br/><li id="wrapper"><input type="button" onClick="execute()" value="実行"/></li></ul>
<li><div id="progressBar">0%</div></li>
<li><div id="status"></div></li>
</ul></form>

<script>
document.body.style.overflow="hidden";
var _ = document.querySelector.bind( document );
var files = [];

// window settings.
var baseWidth = 380;
var baseHeight = 540;
var zoomFactor = screen.deviceXDPI / 96;
var finalWidth = baseWidth * zoomFactor;
var finalHeight = baseHeight * zoomFactor;
window.resizeTo(finalWidth, finalHeight);
var x = (screen.width - finalWidth) / 2;
var y = (screen.height - finalHeight) / 2;
window.moveTo(x, y);


///////////////////////////////////////////////////  CommonUtil.js
(function() {
  "use strict";

  var root = this;

  var $ = function( obj ) {
    if ( obj instanceof $ ) return obj;
    if ( !( this instanceof $ ) ) return new $( obj );
    this._wrapped = obj;
  }

  root.$ = $;

  $.Fso = new ActiveXObject( "Scripting.FileSystemObject" );
  $.Shell = new ActiveXObject( "WScript.Shell" );
  $.ShApp = new ActiveXObject("Shell.Application");

  $.FileSeparator = "/";
  $.AdoDbStream = {
    obj : new ActiveXObject( "ADODB.Stream" ),

    // StreamTypeEnum.
    adTypeBinary : 1,
    adTypeText : 2,

    // SaveOptionsEnum.
    adSaveCreateNotExist : 1,
    adSaveCreateOverWrite : 2,

    // StreamWriteEnum.
    adWriteChar : 0,
    adWriteLine : 1,
    
    // StreamReadEnum.
    adReadAll : -1,
    adReadLine : -2,
    
    LF : 10,

    // read file with charset.
    readFile : function( fullPath, charset, func ) {
      if ( charset == null ) {
        charset = "_autodetect_all";
      }
      var st = $.AdoDbStream.obj;
      st.Type = $.AdoDbStream.adTypeText;
      st.charset = charset;
      st.LineSeparator = $.AdoDbStream.LF;
      st.Open();
      st.LoadFromFile( fullPath );
      st.Position = 0;
      while( ! st.EOS ){
        func( st.ReadText( $.AdoDbStream.adReadLine ) );
      }
      st.Close();
    },
      
    // write file with charset.
    writeFile : function( fullPath, text, charset ) {
      if ( charset == null ) {
        charset = "_autodetect_all";
      }
      var st = $.AdoDbStream.obj;
      st.Type = $.AdoDbStream.adTypeText;
      st.charset = charset;
      st.Open();
      st.WriteText( text, $.AdoDbStream.adWriteLine );
      st.SaveToFile( fullPath, $.AdoDbStream.adSaveCreateOverWrite );
      st.Close();
    }
  }

  $.yesno = function( message, title ) {
    return $.Shell.Popup( message, 0, title, 1 );
  }

  $.info = function( message, title ) {
    return $.Shell.Popup( message, 0, title, 0 );
  }

  $.log = function( m ) {
    alert( m );
  }

  // each with activeXObject
  $.eachX = function( obj, func ) {
    for( var _obj = new Enumerator( obj ); !_obj.atEnd(); _obj.moveNext() ){
      var v = _obj.item();
      func( v )
    }
  }

  // map with activeXobject
  $.mapX = function( obj, func ) {
    var ret = [];
    for( var _obj = new Enumerator( obj ); !_obj.atEnd(); _obj.moveNext() ){
      var v = _obj.item();
      var value = func( v );
      if ( !( value === false ) ) ret[ret.length] = value
    }
    return ret;
  }

  $.assignObject = function( obj1, obj2 ) {
    if ( !obj2 ) {
      obj2 = {};
    }
    for ( var attrName in obj2 ) {
      if ( obj2.hasOwnProperty( attrName ) ) {
        obj1[attrName] = obj2[attrName];
      }
    }
    return obj1;
  }

  $.objectkeys = function( obj ) {
    var keys = [];
    for ( var attrName in obj ) {
      if ( obj.hasOwnProperty( attrName ) ) {
        keys.push( attrName );
      }
    }
    return keys;
  }

  $.getZeroPadding = function( number, digits ) {
    return ( Array( digits +1 ).join( '0' ) + number ).slice( -digits );
  }

  $.getDateString = function( date, dateFormat ) {
    var months = [
      "Jan", "Feb", "Mar", "Apr", "May", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    ];
    var dayOfWeekStr = [ "", "", "", "", "", "", "" ];
    var dayOfWeekEngStr = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ];
    return dateFormat
      .replace( /@YYYY@/g, date.getFullYear() )
      .replace( /@YY@/g, ( "" + date.getFullYear() ).slice( -2 ) )
      .replace( /@MMM@/g, months[ date.getMonth() ] )
      .replace( /@MM@/g, $.getZeroPadding( date.getMonth() + 1, 2) )
      .replace( /@M@/g, date.getMonth() + 1 )
      .replace( /@DD@/g, $.getZeroPadding( date.getDate(), 2) )
      .replace( /@D@/g, date.getDate() )
      .replace( /@hh@/g, $.getZeroPadding( date.getHours(), 2) )
      .replace( /@h@/g, date.getHours() )
      .replace( /@mm@/g, $.getZeroPadding( date.getMinutes(), 2) )
      .replace( /@m@/g, date.getMinutes() )
      .replace( /@ss@/g, $.getZeroPadding( date.getSeconds(), 2) )
      .replace( /@s@/g, date.getSeconds() )
      .replace( /@EEE@/g, dayOfWeekStr[ date.getDay() ] )
      .replace( /@eee@/g, dayOfWeekEngStr[ date.getDay() ] );
  }

  $.isDate = function( s ){
    var d = new Date( s );
    return d.toString() !== 'NaN' && d.toString() !== 'Invalid Date';
  }

  $.compareDateStr = function( dateStr1, dateStr2 ){
    var date1 = new Date( dateStr1 );
    var date2 = new Date( dateStr2 );
    return date1.getTime() < date2.getTime() ? -1 : date1.getTime() > date2.getTime() ? 1 : 0;
  }

  Array.prototype.indexOf = function(obj, start){ for(var i = (start || 0), j = this.length; i < j; i++){ if(this[i] === obj){ return i }} return -1 };

}).call( this );


////////////////////////////////////////////////    XlsxUtil.js
(function () {
  "use strict";

  var root = this;

  if ( typeof $ === "undefined" ) {
    alert('"CommonUtil.js" must be loaded.');
    return;
  }

  // For Excel.
  $.Xlsx = {
    // XlPageOrientation.
    xlPortrait: 1,
    xlLandscape: 2,

    // XlWindowView.
    xlNormalView: 1,
    xlPageBreakPreview: 2,
    xlPageLayoutView: 3,

    // XlPaperSize.
    xlPaperA3: 8,
    xlPaperA4: 9,
    xlPaperA5: 11,
    xlPaperB4: 12,
    xlPaperB5: 13,

    withXlsx : function ( fileName , func ) {
      var ole = new ActiveXObject( "Excel.Application" );
      ole.Visible = false;
      ole.DisplayAlerts = false;
      var book = null;
      var full_path = null;
      try {
        full_path = $.Fso.getAbsolutePathName( fileName );
        book = ole.Workbooks.Open( full_path );
        func( ole , book );
      } catch( e ) {
        alert( e.name + ":" + e.message );
      } finally {
        if( book != null){
          book.Close();
        }
        ole.Quit();
        ole = null;
      }
    },

    range2hash : function ( range ) {
      var ret = [];
      ret = $.mapX( range.rows , function( row ) {
        var _v = $.mapX( row.columns , function( cell ) {
          var v = cell.value;
          if ( typeof v == "undefined" ) v = "";
          if ( typeof v == "unknown" ) v = "#REF"
            return v;
        })
        return _v;
      })
      return ret;
    },

    range2AddressMap : function ( sheetName ,range ) {
      var ret = {};
      $.mapX( range.rows , function( row ) {
        $.mapX( row.columns , function( cell ) {
          var text = cell.Text;
          if ( text ) ret[sheetName + "!" + cell.Address( false, false )] = text;
        });
      });
      return ret;
    },

    replaceCellValue : function ( ole, rng, targetStr, replaceStr ) {
      var data = rng.Value.toArray(); 
      var rep = "\"" + replaceStr + "\"";
      var target = "\"" + targetStr + "\"";
      var newData = ole.Evaluate(
        "IF(" + rng.address + "=" + target + "," + rep + "," + rng.address + "&\"\")"
        );
      rng.Value = newData;
    },

    replaceShapeValue : function ( sheet, reg, replaceStr ) {
      $.mapX( sheet.Shapes , function( shape ) {
        if ( shape.TextFrame2.HasText ) {
          var textRange = shape.TextFrame2.TextRange;
          var text = textRange.Text;

          if ( text && reg.test( text ) ){
            textRange.Text = text.replace( reg, replaceStr );
          }
        }
      });
    },
    
    speak : function ( str ) {
      var ole = new ActiveXObject( "Excel.Application" );
      ole.Visible = false;
      ole.DisplayAlerts = false;
      try {
        ole.Speech.Speak ( str );
      } finally {
        ole.Quit();
        ole = null;
      }
    }
    
  }
}).call( this );



function execute() {
  // const.
  var outputPrefix = "TIDY";

  var currentPath = $.Fso.GetAbsolutePathName(".");

  // target folder.
  var targetFolderPath = _("#folderPath").value;
  if ( !targetFolderPath ) {
    return;
  }


  _("#status").innerText = "待機中...";
  
  try {
    var outputDir = currentPath + $.FileSeparator + outputPrefix + $.getDateString( new Date(), '@YYYY@@MM@@DD@@hh@@mm@@ss@' );
    processFolder( targetFolderPath, outputDir );
    if (files.length === 0) {
      _("#status").innerText = "対象ファイルがありません。";
      return;
    }

    // 進捗バー 初期化
    _("#progressBar").style.width = '0%';
    _("#progressBar").innerText = '0%';

    // 入力値取得
    var tidyInfo = {};
    tidyInfo.font = _("#font").value;
    tidyInfo.fontSize = _("#fontSize").value;
    tidyInfo.xlWindowView = _("#xlWindowView").value;
    tidyInfo.zoom = _("#zoom").value;
    tidyInfo.xlPageOrientation = _("#xlPageOrientation").value;
    tidyInfo.printArea = _("#printArea").value;
    tidyInfo.xlPaperSize = _("#xlPaperSize").value;
    tidyInfo.pageSetupZoom = _("#pageSetupZoom").value;

    // 各ファイルのループ処理
    var done = 0;
    $.mapX( files, function( path ){
      if (!/\.xlsx?$/i.test( path )) {
        done++;
        return;
      }
      
      $.Xlsx.withXlsx( path, function( ole, book ){
        $.mapX( book.Worksheets, function( sheet ){
          if ( ! sheet.Visible || sheet.ProtectContents ) return;
          tidyWorkSheet( ole, sheet, tidyInfo );
        });

        // saveAs.
        book.Saveas( outputDir + path.replace(targetFolderPath, '') );
      });

      var progress = Math.round((++done / files.length) * 100);
      
      // HTAのUIを更新
      _("#progressBar").style.width = progress + '%';
      _("#progressBar").innerText = progress + '%';
      var fileName = $.Fso.GetFileName( path );
      _("#status").innerText = "処理中: " + fileName + " (" + done + "/" + files.length + ")";
      
      repaintDom();
    });
    
    _("#status").innerText = "完了しました";
    $.Xlsx.speak( "処理完了しました" );
    _("#progressBar").style.width = '100%';
    _("#progressBar").innerText = '100%';

    repaintDom();
  } catch (e) {
    $.Xlsx.speak( "エラーが発生しました" );
    alert("エラーが発生しました: " + e.message);
  }
}

function selectFolder(){
  //var desktopPath = $.Shell.ExpandEnvironmentStrings("%UserProfile%") + "\\Desktop";
  var targetFolder = $.ShApp.BrowseForFolder(0, "処理対象フォルダを選択してください", 0);
  if ( !targetFolder ) {
    return;
  }
  _("#folderPath").value = targetFolder.Self.Path;
}

function processFolder( currentSrc, currentDest ) {
  if (!$.Fso.FolderExists( currentDest )) {
    $.Fso.CreateFolder( currentDest );
  }

  var folder = $.Fso.GetFolder( currentSrc );
  var fileList = new Enumerator( folder.Files );
  for (; !fileList.atEnd(); fileList.moveNext()) {
    var file = fileList.item();
    files.push( file.Path );
  }

  var subFolders = new Enumerator( folder.SubFolders );
  for (; !subFolders.atEnd(); subFolders.moveNext()) {
    var subFolder = subFolders.item();
    var nextDest = $.Fso.BuildPath( currentDest, subFolder.Name );
    processFolder( subFolder.Path, nextDest );
  }
}

function repaintDom(){
  // HTAはシングルスレッドなので、UI描画を強制的に更新させる為のHack
  $.Shell.Run("powershell -command \"Start-Sleep -m 1 \", 0, true", 0, true);
}

function tidyWorkSheet( ole, sheet, tidyInfo ) {
  // const.
  var XlsxPrefix = "$.Xlsx.";
  
  sheet.Select;
  ole.Application.Goto( sheet.Range( "A1" ), true );
  
  // # PageSetup.Duplex
  var xlSimplex = 1; // 片面印刷(単票印刷)
  var xlDuplexVertical = 2; // 両面印刷(長辺とじ)
  var xlDuplexHorizontal = 3; // 両面印刷(短辺とじ)
  //if (sheet.PageSetup) sheet.PageSetup.Duplex = xlSimplex;
  
  if ( tidyInfo.font ) sheet.Cells.Font.Name = tidyInfo.font;
  if ( tidyInfo.fontSize ) sheet.Cells.Font.Size = tidyInfo.fontSize;
  if ( tidyInfo.xlWindowView )
    ole.Application.ActiveWindow.View = eval( XlsxPrefix + tidyInfo.xlWindowView );
  if ( tidyInfo.zoom ) ole.Application.ActiveWindow.Zoom = tidyInfo.zoom;
  if ( tidyInfo.xlPageOrientation )
    sheet.PageSetup.Orientation = eval( XlsxPrefix + tidyInfo.xlPageOrientation );
  if ( tidyInfo.printArea ) sheet.PageSetup.PrintArea = tidyInfo.printArea;
  if ( tidyInfo.xlPaperSize )
    sheet.PageSetup.PaperSize = eval( XlsxPrefix + tidyInfo.xlPaperSize );
  switch ( tidyInfo.pageSetupZoom ) {
    case "zoomOff":
      sheet.PageSetup.Zoom = 100;
      sheet.PageSetup.FitToPagesTall = false;
      sheet.PageSetup.FitToPagesWide = false;
      break;
    case "Sheet1page":
      sheet.PageSetup.Zoom = false;
      sheet.PageSetup.FitToPagesTall = 1;
      sheet.PageSetup.FitToPagesWide = 1;
      break;
    case "column1page":
      sheet.PageSetup.Zoom = false;
      sheet.PageSetup.FitToPagesTall = false;
      sheet.PageSetup.FitToPagesWide = 1;
      break;
    case "row1page":
      sheet.PageSetup.Zoom = false;
      sheet.PageSetup.FitToPagesTall = 1;
      sheet.PageSetup.FitToPagesWide = false;
      break;
    default:
      break;
  }
  if ( tidyInfo.pageSetupZoom ) sheet.ResetAllPageBreaks();
}
</script>
</body>
</html>

総括・ハマりポイント

  • 進捗状況表示でDOMが更新されないのにはまった。
  • AIが役に立った。がウソを頻繁につく
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?