batでフォルダ内の画像ファイルをHTML一覧表示

# batでフォルダ内の画像ファイルをHTML一覧表示

データ分析などしていると大量のグラフを一覧表示して比較したいときがある。その際、多くの場合はtableuやquicksightなどのBI toolを用いてダッシュボードを作成し、動的なフィルターなどを用いて一覧表示やフィルター表示する。しかし、BI toolによるダッシュボード作成は時間がかかり、手軽にグラフを一覧表示したい場合は少し手間である。

今回はそのようなBI toolを用いることなく、数十秒~数分で大量のグラフを一覧表示する方法を紹介する。さらに今回紹介する方法は、Pythonなどのプログラミング言語を必要としない、Windows標準のOS言語とbat処理でフォルダ内のグラフを一覧表示するため、環境構築の面でも導入がスムーズである。

## ソースコード
下記のようなフォルダ構成で、まずは特定のフォルダに画像ファイル(*.gif, *.png, *.jpeg, *.jpg, *.bmp)、bat,css,jsファイルを保存する。画像ファイルは名前に規則性を持たせて保存すると、batによるhtml化の際に並べて表示してくれる。各ファイルを置いたら、batファイルをクリックして実行する。

フォルダ構成
```
folder/
　├ graph1.jpg
　├ graph2.jpg
　├ graph3.jpg
　├ graphview.html
　├ makehtml.bat
　├ css/
　│　└ sample.css
　│　└ searchicon.png
　├ js/
　└　└ search.js
```

```bat:makehtml.bat
chcp 65001
@echo off
echo Make HTML for viewing image file on this folder
rem 画像をタイル状に表示させるHTMLを作るバッチ

pushd \\hoge\hogehoge\hogehogehoge\folder

rem 出力するファイル名
set HTML_FILE=thumbnail.html
rem タイルの列数
set TABLE_COLUMNS=1
rem タイルの幅（画像の幅）
set IMAGE_WIDTH=1650

rem 検出する画像ファイルの拡張子
set FILE_TYPES=*.gif, *.png, *.jpeg, *.jpg, *.bmp

rem 出力先のファイルを消す（あったら）
if exist "%HTML_FILE%" del "%HTML_FILE%"

rem フォルダの名前を取得する
for %%I in (.) do set CUR_DIRNAME=%%~nI%%~xI

rem HTMLファイルの先頭部を出力する
call :OUT_HTML "^<!DOCTYPE html^>"
call :OUT_HTML "^<html lang='ja'^>"
call :OUT_HTML "^<head^>"
call :OUT_HTML "^<meta http-equiv="content-type" charset="UTF-8"^>"
call :OUT_HTML "^<link rel="stylesheet" href="sample.css"^>"
call :OUT_HTML "^<title^>%CUR_DIRNAME%^</title^>"
call :OUT_HTML "^<style type='text/css'^>^<!-- td {vertical-align:top;}--^>^</style^>"
call :OUT_HTML "^</head^>"
call :OUT_HTML "^<body^>"
call :OUT_HTML "^<h1^>sample html^</h1^>"
call :OUT_HTML "^<h2^>Search^</h2^>"
call :OUT_HTML "^<input type='text' id='myInput' onkeyup='myFunction()' placeholder='Search for names..' title='Type in a name'^>"

set /a FILE_COUNT=0
set /a COLUMN_NO=0

rem call :OUT_HTML "^<table^>"

rem ファイルを検索してタイルを出力する
for %%a in (%FILE_TYPES%) do (
     echo %%a 
     call :OUT_TD_TAG "%%a"
)
rem 列数に満たなかった分だけ空のTDタグを出力する
if %COLUMN_NO% LSS %TABLE_COLUMNS% (
    call :OUT_DUMMY_TD_TAG
)
rem call :OUT_HTML "^</table^>"

rem HTMLファイルの終端部を出力する
call :OUT_HTML "^<script^>"
call :OUT_HTML "function myFunction() {"
call :OUT_HTML "  var input, filter, table, tr, td, i, txtValue, img;"
call :OUT_HTML "  input = document.getElementById('myInput');"
call :OUT_HTML "  filter = input.value.toUpperCase();"
call :OUT_HTML "  table = document.getElementById('myTable');"
call :OUT_HTML "  tr = document.getElementsByTagName('tr');"
call :OUT_HTML "  for (i = 0; i < tr.length; i++) {"
call :OUT_HTML "    td = tr[i].getElementsByTagName('td')[0];"
call :OUT_HTML "    img = tr[i].getElementsByTagName('img')[0];"
call :OUT_HTML "    txtValue = img.src;"
call :OUT_HTML "    console.log(img.src);"
call :OUT_HTML "    if (td) {"
call :OUT_HTML "      if (txtValue.toUpperCase().indexOf(filter) ^> -1) {"
call :OUT_HTML "       tr[i].style.display = '';"
call :OUT_HTML "      } else {"
call :OUT_HTML "        tr[i].style.display = 'none';"
call :OUT_HTML "      }"
call :OUT_HTML "    }     "  
call :OUT_HTML "}"
call :OUT_HTML "^</script^>"
call :OUT_HTML "^</body^>"
call :OUT_HTML "^</html^>"

goto :EOF

rem ============================================================
:OUT_TD_TAG

set IMAGE_FILE=%~1

rem 列番号を求める
set /A COLUMN_NO=%FILE_COUNT% %% %TABLE_COLUMNS% + 1

rem 1列目ならTRタグを出力する
if %COLUMN_NO% EQU 1 (
    call :OUT_HTML "^<table^>"
    call :OUT_HTML "^<tr^>"
)

rem 画像用タグを出力する
rem call :OUT_HTML "  ^<td^>"
set /A amari=%FILE_COUNT% %%2
if "%IMAGE_FILE%" NEQ "" (
	if "%IMAGE_FILE:~56,1%"=="." (
		call :OUT_HTML "    ^<h2^>種=%IMAGE_FILE:~10,12% 材=%IMAGE_FILE:~23,18% 製=%IMAGE_FILE:~38,7% 型=%IMAGE_FILE:~46,10%　 ^</h2^>"
    		call :OUT_HTML "  ^<td^>"
    		call :OUT_HTML "    ^<img width='%IMAGE_WIDTH%' src='%IMAGE_FILE%' /^>"
	) else if "%IMAGE_FILE:~55,1%"=="." (	
		call :OUT_HTML "    ^<h2^>種=%IMAGE_FILE:~10,12% 材=%IMAGE_FILE:~23,18% 製=%IMAGE_FILE:~38,7% 型=%IMAGE_FILE:~46,9%　 ^</h2^>"
    		call :OUT_HTML "  ^<td^>"
    		call :OUT_HTML "    ^<img width='%IMAGE_WIDTH%' src='%IMAGE_FILE%' /^>"
	) else (
		call :OUT_HTML "    ^<h2^>種=%IMAGE_FILE:~10,12% 材=%IMAGE_FILE:~23,18% 製=%IMAGE_FILE:~38,7% 型=%IMAGE_FILE:~46,11%　 ^</h2^>"
    		call :OUT_HTML "  ^<td^>"
    		call :OUT_HTML "    ^<img width='%IMAGE_WIDTH%' src='%IMAGE_FILE%' /^>"
	)
)
call :OUT_HTML "  ^</td^>"

rem 最終列なら/TRタグを出力する
if %COLUMN_NO% EQU %TABLE_COLUMNS% (
    call :OUT_HTML "^</tr^>"
    call :OUT_HTML "^</table^>"
)

rem ファイル数を数える
set /a FILE_COUNT=%FILE_COUNT%+1

exit /b

rem ============================================================
:OUT_DUMMY_TD_TAG

rem 列数に満たない分のTDタグの個数を計算する
set /A DUMMY_LOOP=%TABLE_COLUMNS%-%COLUMN_NO%

rem 空のTDタグを出力する
for /l %%i in (1,1,%DUMMY_LOOP%) do (
    call :OUT_TD_TAG ""
)

exit /b

rem ============================================================
:OUT_HTML
set OUT_TEXT=%1
set OUT_TEXT=%OUT_TEXT:^^=^%

echo %OUT_TEXT:"=% >> %HTML_FILE%

exit /b
```


```css:sample.css
h1 {
    padding: 0.5em;/*文字周りの余白*/
    color: #494949;/*文字色*/
    background: #fffaf4;/*背景色*/
    border-left: solid 5px #ffaf58;/*左線（実線 太さ 色）*/
  }

  h2 {
    position: relative;
    padding: 0.3rem 1rem;
    text-align: left;
    font-size: 10px;
    font-size: 1.2rem;
    border-left: solid 3px #ffaf58;
    background: #fffaf4;
  }
  
  h3 {
    position: relative;
    padding: 0.3rem 1rem;
    text-align: left;
    font-size: 10px;
    font-size: 1.2rem;
    border-left: solid 3px #ffaf58;
    background: #fffaf4;
  }

#myInput {
  background-image: url('css/searchicon.png');
  background-position: 6px 6px;
  background-repeat: no-repeat;
  width: 100%;
  font-size: 16px;
  padding: 12px 20px 12px 40px;
  border: 1px solid #ddd;
  margin-bottom: 12px;
}
```

```javascript:search.js
function myFunction() { 
  var input, filter, table, tr, td, i, txtValue, img, h3; 
  input = document.getElementById('myInput'); 
  filter = input.value.toUpperCase(); 
  table = document.getElementById('myTable'); 
  tr = document.getElementsByTagName('tr'); 
  h3 = document.getElementsByTagName('h3');  
  for (i = 0; i < tr.length; i++) { 
    td = tr[i].getElementsByTagName('td')[0]; 
    img = tr[i].getElementsByTagName('img')[0]; 
    txtValue = img.src; 
    console.log(img.src); 
    if (td) { 
      if (txtValue.toUpperCase().indexOf(filter) > -1) { 
       tr[i].style.display = ''; 
       h3[i].style.display = ''; 
      } else { 
        tr[i].style.display = 'none'; 
        h3[i].style.display = 'none'; 
      } 
    } 
  } 
} 
```


## 出力されるHTML

```html:graphview.html
<!DOCTYPE html> 
<html lang='ja'> 
<head> 
<meta http-equiv=content-type charset=UTF-8> 
<link rel=stylesheet href=sample.css> 
<title>sample html</title> 
<style type='text/css'><!-- td {vertical-align:top;}--></style> 
</head> 
<body> 
<h1>sample html</h1> 
<h2>Search</h2> 
<input type='text' id='myInput' onkeyup='myFunction()' placeholder='Search for names..' title='Type in a name'> 
<table> 
<tr> 
    <h2>name = graph1</h2> 
  <td> 
    <img width='1650' src='graph1.jpg' /> 
  </td> 
</tr> 
</table> 
<table> 
<tr> 
    <h2>name = graph2</h2> 
  <td> 
    <img width='1650' src='graph2.jpg' /> 
  </td> 
</tr> 
</table> 
<table> 
<tr> 
    <h2>name = graph3</h2> 
  <td> 
    <img width='1650' src='graph3.jpg' /> 
  </td> 
</tr> 
</table> 

<script> 
function myFunction() { 
  var input, filter, table, tr, td, i, txtValue, img; 
  input = document.getElementById('myInput'); 
  filter = input.value.toUpperCase(); 
  table = document.getElementById('myTable'); 
  tr = document.getElementsByTagName('tr'); 
    td = tr[i].getElementsByTagName('td')[0]; 
    img = tr[i].getElementsByTagName('img')[0]; 
    txtValue = img.src; 
    console.log(img.src); 
    if (td) { 
      if (txtValue.toUpperCase().indexOf(filter) > -1) { 
       tr[i].style.display = ''; 
      } else { 
        tr[i].style.display = 'none'; 
      } 
    }      
} 
</script> 
</body> 
</html> 
```

# まとめ
今回はbat処理による大量のグラフの一覧表示プログラムを紹介した。BI toolなどによりグラフを一覧表示できない場面で有用できると思う。

## 補足

バッチのfor/if文の変数%%aなどの文字列操作をする場合は、以下のように遅延環境変数を使う。

> 仕様上、if文やfor文で複数のコマンドを一つのブロックにまとめると、変数の値への展開はブロックの入り口に到達した時点で行われる。これだとブロック内で値を変更してもそのブロックが終わるまで変更結果が反映されず、ブロック内では常に古い値が参照されてしまう。

```bat
setlocal EnableDelayedExpansion
for %%a in (*) do (
    set tmpstr=%%a
    echo !tmpstr!
    echo !tmpstr:~0,5!
)
```