今時XMLファイルを触る人は多くないかもしれませんが、eGovなどから発行される公文書はXML形式です。
Internet Explorerを前提で作られているため、セキュリティが強固になったモダンブラウザでは開けません。
EdgeのIEモードを有効にする手順が複雑化したこともあり、簡単にXMLを閲覧するプログラムを作ろうと思い立って実現したので共有します。
成果物
XMLファイルをダブルクリックしてブラウザで開くことが可能になります。
ソースコード
必ずShift-JISで保存してください。 (メモ帳ならANSIかも)
@echo off
rem ブラウザのセキュリティ制限により、ローカルのXSLファイルは読み込めません。
rem このバッチファイルは、簡易HTTPサーバーを立ててファイルを提供することで、その制限を回避します。
setlocal
set "TARGET_FILE=%~1"
set "WAIT_MILLISECONDS=1000"
rem --- 設定項目 ---
rem 1: 1つのXSL送信後に自動で閉じる(デフォルト) / 0: 手動で閉じるまで待機
set "AUTO_CLOSE=1"
rem 1: XMLと違う階層にあるXSLも許可する / 0: 同じ階層のみ許可(デフォルト)
set "ALLOW_EXTERNAL_XSL=0"
rem ---------------
if "%TARGET_FILE%"=="" (
echo XMLファイルをこのアイコンにドラッグ&ドロップしてください。
pause
exit /b
)
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"$targetFull = [System.IO.Path]::GetFullPath('%TARGET_FILE%');" ^
"$targetDir = [System.IO.Path]::GetDirectoryName($targetFull);" ^
"$port = 8000;" ^
"while ($true) {" ^
" $connection = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Loopback, $port);" ^
" try { $connection.Start(); $connection.Stop(); break; } catch { $port++ }" ^
" if ($port -gt 9000) { Write-Host '空きポートが見つかりませんでした。'; pause; exit }" ^
"}" ^
"$url = \"http://localhost:$port/$($targetFull.Replace('\','/'))\";" ^
"Start-Process $url;" ^
"$listener = New-Object System.Net.HttpListener;" ^
"$listener.Prefixes.Add(\"http://localhost:$port/\");" ^
"$listener.Start();" ^
"Write-Host '--------------------------------------------------' -ForegroundColor Gray;" ^
"Write-Host \"サーバー起動中 (Port: $port)\";" ^
"Write-Host \"URL: $url\";" ^
"if ('%AUTO_CLOSE%' -eq '0') { Write-Host '※ 全てのXSLが読み込まれたら、この画面を閉じてください。' -ForegroundColor Yellow };" ^
"if ('%ALLOW_EXTERNAL_XSL%' -eq '1') { Write-Host '※ 別階層XSL許可モード:全階層のXSLへのアクセスを許可しています。' -ForegroundColor Magenta };" ^
"Write-Host '--------------------------------------------------' -ForegroundColor Gray;" ^
"$xmlServed = $false;" ^
"$xslServed = $false;" ^
"while ($listener.IsListening) {" ^
" try {" ^
" $context = $listener.GetContext();" ^
" $request = $context.Request;" ^
" $response = $context.Response;" ^
" $reqPath = $request.Url.LocalPath;" ^
" if ($reqPath.StartsWith('/')) { $reqPath = $reqPath.Substring(1) }" ^
" if ($reqPath -match '^[a-zA-Z]:') { } elseif ($reqPath.StartsWith('/')) { $reqPath = $reqPath.Substring(1) }" ^
" $localPath = [System.IO.Path]::GetFullPath($reqPath.Replace('/', '\'));" ^
" $isTargetXml = ($localPath -eq $targetFull);" ^
" $isSameDir = ([System.IO.Path]::GetDirectoryName($localPath) -eq $targetDir);" ^
" $isXsl = ([System.IO.Path]::GetExtension($localPath).ToLower() -eq '.xsl');" ^
" $allowXsl = if ('%ALLOW_EXTERNAL_XSL%' -eq '1') { $isXsl } else { $isSameDir -and $isXsl };" ^
" if ($isTargetXml) {" ^
" $content = [System.IO.File]::ReadAllBytes($localPath);" ^
" $response.ContentType = 'text/xml';" ^
" $response.OutputStream.Write($content, 0, $content.Length);" ^
" $xmlServed = $true;" ^
" Write-Host \"XMLレスポンス: $reqPath\" -ForegroundColor Cyan;" ^
" } elseif ($allowXsl) {" ^
" if (Test-Path $localPath -PathType Leaf) {" ^
" $content = [System.IO.File]::ReadAllBytes($localPath);" ^
" $response.ContentType = 'text/xml';" ^
" $response.OutputStream.Write($content, 0, $content.Length);" ^
" $xslServed = $true;" ^
" Write-Host \"XSLレスポンス: $reqPath\" -ForegroundColor Green;" ^
" } else { $response.StatusCode = 404 }" ^
" } else {" ^
" $response.StatusCode = 403;" ^
" Write-Host \"[警告] ブロックされたアクセス: $reqPath\" -ForegroundColor Red;" ^
" }" ^
" $response.Close();" ^
" } catch { break }" ^
" if ('%AUTO_CLOSE%' -eq '1' -and $xmlServed -and $xslServed) {" ^
" Write-Host '読み込み完了。自動終了します。';" ^
" Start-Sleep -Milliseconds %WAIT_MILLISECONDS%;" ^
" $listener.Stop();" ^
" }" ^
"}" ^
必ずShift-JISで保存してください。
大事なことなので2度言いました。
解説もどき
.NETの機能を使い、Webサーバーを立ち上げます。8000~9000番の空いているポートを順番に探し、最初に見つかったものを使用します。その後、ブラウザを立ち上げ、XMLとXSLのリクエストを1件ずつ確認したら自動で閉じるという流れです。
セキュリティ対策のためデフォルトでは「返せるXMLは引数に指定されたものだけ。返せるXSLはXMLと同階層にあるものだけ。」という仕様になっていますが、設定可能なオプションが2つあります。
1つ目は、複数のXSLを読み込むタイプを想定して、手動で閉じるまでXSLのリクエストを待機する設定です。2つ目は、別階層にXSLがある場合を想定して、別階層のXSLを許可する設定です。
PowerShellで何をやっているか気になる人は次のコードを読んでください。
PowerShell部分の抜き出し
# バッチファイルで定義した環境変数のシミュレーション
$TARGET_FILE = "C:\Users\path\to\your-file.xml"
$AUTO_CLOSE = "1"
$ALLOW_EXTERNAL_XSL = "0"
$WAIT_MILLISECONDS = 1000
# フルパスとターゲットディレクトリの取得
$targetFull = [System.IO.Path]::GetFullPath($TARGET_FILE)
$targetDir = [System.IO.Path]::GetDirectoryName($targetFull)
$port = 8000
# 空きポートの探索
while ($true) {
$connection = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Loopback, $port)
try {
$connection.Start()
$connection.Stop()
break
} catch {
$port++
}
if ($port -gt 9000) {
Write-Host '空きポートが見つかりませんでした。'
pause
exit
}
}
# 既定のブラウザでURLを開く
$url = "http://localhost:$port/$($targetFull.Replace('\','/'))"
Start-Process $url
# HTTPリスナーの設定
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://localhost:$port/")
$listener.Start()
Write-Host '--------------------------------------------------' -ForegroundColor Gray
Write-Host "サーバー起動中 (Port: $port)"
Write-Host "URL: $url"
if ($AUTO_CLOSE -eq '0') {
Write-Host '※ 全てのXSLが読み込まれたら、この画面を閉じてください。' -ForegroundColor Yellow
}
if ($ALLOW_EXTERNAL_XSL -eq '1') {
Write-Host '※ 別階層XSL許可モード:全階層のXSLへのアクセスを許可しています。' -ForegroundColor Magenta
}
Write-Host '--------------------------------------------------' -ForegroundColor Gray
$xmlServed = $false
$xslServed = $false
# メインループ
while ($listener.IsListening) {
try {
$context = $listener.GetContext()
$request = $context.Request
$response = $context.Response
$reqPath = $request.Url.LocalPath
# パスの正規化
if ($reqPath.StartsWith('/')) { $reqPath = $reqPath.Substring(1) }
if ($reqPath -match '^[a-zA-Z]:') { } elseif ($reqPath.StartsWith('/')) { $reqPath = $reqPath.Substring(1) }
$localPath = [System.IO.Path]::GetFullPath($reqPath.Replace('/', '\'))
# 判定ロジック
$isTargetXml = ($localPath -eq $targetFull)
$isSameDir = ([System.IO.Path]::GetDirectoryName($localPath) -eq $targetDir)
$isXsl = ([System.IO.Path]::GetExtension($localPath).ToLower() -eq '.xsl')
$allowXsl = if ($ALLOW_EXTERNAL_XSL -eq '1') { $isXsl } else { $isSameDir -and $isXsl }
if ($isTargetXml) {
# XMLの配信
$content = [System.IO.File]::ReadAllBytes($localPath)
$response.ContentType = 'text/xml'
$response.OutputStream.Write($content, 0, $content.Length)
$xmlServed = $true
Write-Host "XMLレスポンス: $reqPath" -ForegroundColor Cyan
} elseif ($allowXsl) {
# XSLの配信
if (Test-Path $localPath -PathType Leaf) {
$content = [System.IO.File]::ReadAllBytes($localPath)
$response.ContentType = 'text/xml'
$response.OutputStream.Write($content, 0, $content.Length)
$xslServed = $true
Write-Host "XSLレスポンス: $reqPath" -ForegroundColor Green
} else {
$response.StatusCode = 404
}
} else {
# ブロック
$response.StatusCode = 403
Write-Host "[警告] ブロックされたアクセス: $reqPath" -ForegroundColor Red
}
$response.Close()
} catch {
break
}
# 自動終了判定
if ($AUTO_CLOSE -eq '1' -and $xmlServed -and $xslServed) {
Write-Host '読み込み完了。自動終了します。'
Start-Sleep -Milliseconds $WAIT_MILLISECONDS
$listener.Stop()
}
}
XMLをダブルクリックして開く
このバッチファイルを既定のアプリにしておけば、ダブルクリックでXMLをデフォルトのブラウザで表示できます。

