見出し画像

WSHがお好きでしょ?だからdocxやPDFをつくれるようにした(2回目)

こんにちは!なるーらぼです。前回に引き続き、タイトルのとおりです。
WSHが現代の環境に合わせて変化しているところ、現代のブラウザのほうがよいところ、node.jsのほうがよいところなど見えてくるものがありますね。一方で現代でもWSHが便利だなと思うところもありますので、バランスよく活用できるといいでしょう。今回もWSHにスポットライトをあてつつ、活用するにはどこがポイントになりそうかみていきたいと思います。

UTF-8のテキストファイルを保存すること

HTAを久しぶりにつかってみて「うわー」っとなったのが、文字コードがUTF-8であるテキストファイルの扱いです。むかしから、こうした要求にこたえるにはADODB.Streamを使うのが一般的でした。ここはVBAでも同じではないかと思います。ほかにファイルを作成する手段がないため、DLLなど外部ライブラリを使用するかつくって使用するかのいずれかになりますよね。

今回も同様にADODB.Streamを使うことにしたのですが、完全にうかつでした。HTAからADODB.Streamを使うことができませんでした。おそらくセキュリティ上のリスクを低減するためだと思われるのですが、実はADODBはウェブ上のリソースともやりとりができます。

HTAではクロスドメイン(というより、HTAは自分のPCがドメインの代わり)のウェブリソースとXMLHTTPによるやりとりができます。今回は使用していませんが、これも相当にパワフルすぎる機能です。

そういうわけでHTAから直接ADODBは利用することができません。そこで使ったのが別のWSHです。単独のJScriptファイルを作成して、そちらでUTF-8のテキストファイルをつくるということにしました。しかしデータ入力されているのはHTAですので、直接JScriptファイルと通信することができません。さすがにWSHの範疇のJScriptではウェブサーバにすることができませんので(JScript.NETを使ってしまうと範疇外ということにしました)、これぞレガシーな「起動時パラメータでファイルパスを渡す」ことにしました。

UTF-8ファイルをつくる(現実)

本来は渡されたテキストファイルが妥当なものなのか検証するべきですが、今回はファイルの存在チェックをするのみにしました。

    function validate() {
       var args = WScript.Arguments
       WScript.StdOut.WriteLine(args.length)
       if(args.length === 0) {
           return null
       }
       var md = args.Item(0)
       WScript.StdOut.WriteLine(md)
       if(!fso.FileExists(md)) {
           return null
       }
       return md
   }

なかなかの脆弱な感じではありますね。WScript.Argumentsはさらに名前付き(Named)と名前なし(UnNamed)があります。今回は名前なしを使いましたが、きっちりやるときは名前付きにしてやると使いまわしがよいかもしれません。ほかに、WScript.StdOutというものを使っています。ふだんWScript.Echoをつかうと思うのですが、あちらはスクリプトファイルを起動するときどのスクリプティングホストを利用するかによってコンソールに表示になるか、メッセージダイアログになるかが決まります。ここではいちいち使用者のアクションを介する必要がない情報ですので、WScript.Echoを使わず直接コンソールへ出力しています。この関数ではファイルが存在していたら指定されているファイルパスを返すことにしています。

そして最後にファイルをUTF-8にするところですが、ここはウェブ検索すればたくさんサンプルが見つかると思います。むかしからこの記述はあまり好きではありません。ここまでくるとPythonやPowerShellをつかったほうが…

    function doConvert(md) {
       try {
           var tf = fso.OpenTextFile(md, 1)
           var ts = tf.ReadAll()
           tf.Close()
           var st = new ActiveXObject('ADODB.Stream')
           st.Charset = 'UTF-8'
           st.Type = 2  // text
           st.Open()
           st.WriteText(ts)
           st.Position = 0
           st.Type = 1  // binary
           st.Position = 3
           var data = st.Read()
           st.Close()
           st.Open()
           st.Write(data)
           st.SaveToFile(md, 2)
           st.Close()
           return true
       } catch(e) {
           WScript.StdErr.WriteLine(e.message)
           return false
       }
   }

書いておいてなんですが、UTF-8のBOMなしになるようにしてあります。正直いってBOMなしにする必要があったのか?そこを検証し忘れていました。
ちなみにBOMって「Byte Order Mark」なのですが、Windows系のものだけ、しかもBOMなしにこだわるのは日本特有なのですかね。「バイト順マーク」の箇所に説明があります。

さて、これでUTF-8のテキストファイルができあがるようになるので、HTAから呼び出しを行うことにします。ここもまた一般的なWScript.ShellのExecメソッドを使用します。

    function __execScript(mdFile, js) {
       var sh = new ActiveXObject('WScript.Shell')
       var cmd = ['cscript //B', js, mdFile].join(' ')
       var exec = sh.Exec(cmd)
       var id = window.setInterval(function() {
           if(exec.Status === 0) {
               window.clearInterval(id)
               if(exec.ExitCode === 0) {
                   alert('マークダウンを保存しました')
               } else {
                   alert('マークダウンの保存に失敗しました')
               }
           }
       }, 100)
   }

上述のコードだとちらりと黒い画面が表示されるのですが、隠すほどのことでもないのでそのままにしています。ただ、1つウェブ上にあるサンプルと違うのが待ち合わせの部分です。大抵はExecメソッドが返すWshExecオブジェクトのステータスが変化するのをWhileなどで待ち合わせするケースがほとんどです。しかしHTAはブラウザですので、そんなことをすると制御をすべてもっていかれてしまいます。そこで100ミリ秒ずつ確認する程度のゆるい待ち合わせにしてあります。

また、この関数の呼び出しにもちょっとだけ手を加えています。これは、普通のテキストファイル書き込みがディスクに書き込まれる前にJScript起動が始まってしまうことを回避しています。もしかするとPCスペックによって待ち時間をもっと短くしてもいいのかもしれません。

	window.setTimeout(__execScript, 200, mdFile, js)

ここで一息

さて、2回目でやっとUTF-8のマークダウンを保存することができました。これで読み書きできるようになったので、多少はエディタとしてマシになりました。次回はWordに立ち向かうお話しをしようと思います。

なお、今回の投稿はMeryをつかっています。マークダウンエディタとして特筆して優れているわけではありませんが、文章を書くことを考えれば相当素敵なエディタです。マクロ機能としてVBScriptやJScriptをつかうことができるというのも特徴のひとつです。WSHがお好きな方にはたまらないですよね。べつの投稿でMeryについてのお話しもしようかな、と思います。

この記事が気に入ったらサポートをしてみませんか?