プログラムから(ファイルを使わないで)IEを起動しXMLを開く方法
お仕事で、WindowsアプリからIEを開くときに一時ファイルを使わない方法はないかということで調査してみた。
- IEはCOMを使えば起動・終了や、データの表示などコントロールできる。
IEの起動・終了、ステースバーなどのUIに関する表示・非表示はShDocVW.dllを使う。
HTMLをIEに投入するには、HTML・CSSなど解析・表示を行うMSHTML.dllを使う。
詳細は、MSDN「Internet ExplorerIEのアーキテクチャ」 - 上記のMSHTMLは、HTMLのみ受け付ける。プログラムからXML+XSLのXMLをIEに渡してもxml-stylesheetに書かれたXSLを読み込まない。しかし、普通にIEからXMLファイルを読み込んでみると、XSLも読み込んでくれて期待した通りに表示される。これは悩んだ。
→MSXMLのtransformNode()を使ってXMLにXSLを反映させることで、HTMLを生成できる。
超絶参考になったサイト:VC++ WebBrowser COMメモ(Hishidama's VC++ WebBrowser-control Memo)
データはOPEN DESIGN BOOKS「XML+XSLT実用スーパーサンプル集」から持ってきた。XML→XSL→XSLという形で、XSLからXSLファイルを参照しているもの。こういう形では、XMLから参照しているXSLを読み込ませればいい。XSLから参照しているXSLは読み込む必要が無い。
これとてもいい本です。しかし、絶版なのね。
以下のプログラムはWin32コンソールに貼り付ければOK。XMLは貼り付けてあるけど、XSLファイルは自前で用意する必要がある。
上記の本を何とか手に入れてくれたまえ。
// IETest1.cpp : コンソール アプリケーションのエントリ ポイントを定義します。 // #include "stdafx.h" #import <msxml3.dll> #import <shdocvw.dll> #import <mshtml.tlb> wchar_t *test2 = _T("<?xml version=\"1.0\" encoding=\"Shift_JIS\"?>") _T("<?xml-stylesheet type=\"text/xsl\" href=\"C:\\temp\\apply-imports1.xsl\"?>") _T("<全体>") _T("<情報>") _T("<氏名>愛媛花子</氏名>") _T("<ハンドル>hanako</ハンドル>") _T("<住所>松山市道後1-1-1</住所>") _T("<勤務先>道後IT株式会社</勤務先>") _T("</情報>") _T("<情報>") _T("<氏名>夏目団吾</氏名>") _T("<ハンドル>dango</ハンドル>") _T("<住所>松山市湯渡2-2-2</住所>") _T("<勤務先>松山XMLセンター</勤務先>") _T("</情報>") _T("<情報>") _T("<氏名>九里杏子</氏名>") _T("<ハンドル>あんこ</ハンドル>") _T("<住所>松山市文京町3-3-3</住所>") _T("<勤務先>伊予XSL株式会社</勤務先>") _T("</情報>") _T("</全体>"); bool convertXMLandXSLtoHTML(_bstr_t xml, const variant_t xslFileName, _bstr_t *html) { bool bRslt = true; MSXML2::IXMLDOMDocument2Ptr pXMLDoc = NULL; MSXML2::IXMLDOMDocument2Ptr pXSLDoc = NULL; HRESULT hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument, (void**)&pXMLDoc); if (FAILED(hr)) { bRslt = false; goto FUNC_END; } hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument, (void**)&pXSLDoc); if (FAILED(hr)) { bRslt = false; goto FUNC_END; } pXMLDoc->validateOnParse = TRUE; pXSLDoc->validateOnParse = TRUE; //pXMLDoc->load(xml); // XMLファイル読込 pXMLDoc->loadXML(_bstr_t(test2)); // XMLデータ設定 // XSLファイル読込(こちらも文字列で設定できるけど) pXSLDoc->load(xslFileName); try{ // XML + XSL → HTMLデータ生成 *html = pXMLDoc->transformNode(pXSLDoc); } catch (_com_error e) { bRslt = false; throw e; } FUNC_END: pXMLDoc = NULL; pXSLDoc = NULL; return bRslt; } bool writeHtml(SHDocVw::IWebBrowser2Ptr &pIE, LPCTSTR html) { bool ret = false; _bstr_t bstr = html; MSHTML::IHTMLDocument2Ptr pDoc(pIE->Document); if (pDoc == nullptr) { goto FUNC_END; } SAFEARRAY* sfArray = ::SafeArrayCreateVector(VT_VARIANT, 0, 1); VARIANT* var; HRESULT hr = ::SafeArrayAccessData(sfArray, reinterpret_cast<void**>(&var)); if (FAILED(hr)) { goto FUNC_END; } V_VT(var) = VT_BSTR; V_BSTR(var) = bstr; hr = ::SafeArrayUnaccessData(sfArray); if (FAILED(hr)) { goto FUNC_END; } hr = pDoc->write(sfArray); if (FAILED(hr)) { goto FUNC_END; } hr = pDoc->close(); if (FAILED(hr)) { goto FUNC_END; } ret = true; FUNC_END: pDoc = NULL; ::SafeArrayDestroy(sfArray); return ret; } // IEを起動してhtmlデータを書き込む // IEをコントロールしたい(IEを閉じるなど)場合は、WebBrowserのインスタンスを外に出して管理した方がよい void openIE(const BSTR html) { SHDocVw::IWebBrowser2Ptr pIE; // IE 起動+HTMLデータ設定+表示 HRESULT hr = pIE.CreateInstance(__uuidof(SHDocVw::InternetExplorer)); if (FAILED(hr)) { return; } pIE->Navigate(L"about:blank"); //何か設定しないでHTMLを書き込むと例外が発生する。空文字を入れると"http:///"となる bool bRslt = writeHtml(pIE, html); if (!bRslt) { return; } pIE->MenuBar = VARIANT_FALSE; pIE->ToolBar = VARIANT_FALSE; pIE->StatusBar = VARIANT_TRUE; pIE->Visible = VARIANT_TRUE; // IEを表示状態にする。ここで見えるようになる。 pIE = NULL; } int main(int argc, char* argv[]) { int rtn = -1; _bstr_t html; _bstr_t xml = _bstr_t(_T("C:\\temp\\apply-imports.xml")); variant_t xslFileName = _bstr_t(_T("c:\\temp\\apply-imports1.xsl")); HRESULT hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); try { bool bRslt = convertXMLandXSLtoHTML(xml, xslFileName, &html); if (!bRslt) { goto FUNC_END; } } catch (_com_error e) { fprintf(stderr, "COM ERROR!\n"); fprintf(stderr, " Error = 0x%08lx\n", e.Error()); fprintf(stderr, " ErrorMessage = %s\n", (LPCSTR)_bstr_t(e.ErrorMessage())); fprintf(stderr, " Description = %s\n", (LPCSTR)_bstr_t(e.Description())); fprintf(stderr, " Source = %s\n", (LPCSTR)_bstr_t(e.Source())); } if (html.length() == 0) { goto FUNC_END; } openIE(html); rtn = 0; FUNC_END: ::CoUninitialize(); return rtn; }
●2017/7/22 追記
IEに書き込むときSAFEARRYにBSTR型でデータを入れている。
BSTR型は、UNICODE(内部ではUTF-16)なので、結果、IEに渡せるデータはUTF-16となる。IEのエンコードもUNICODEとなっている。
このことが何の影響がでるかというと、FORMのPOSTデータがUNICODEしか渡せないということ。実際、SJISで試してみてWireSharkでPOSTデータを見るとUTF-8になっていた。IEなのでFORMタグにaccept-charsetが効かない。METAのcharsetを変えてもダメっぽい。
詳しい方法は割愛するが、javascriptでSJISのコードをinputタグのvalue属性に設定してみたけど(String.fromCharCodeでSJISのコードを変換してみたり、数値文字参照から文字列を設定してみたり)全然だめだった。UTF-8になってしまう。
オイラはWebサーバ構築の経験があまり無いので、UTF-8を受け付けないサーバはあるかしらんが、この機能を使って実装するには気を付ける必要がある。
XMLHttpRequestを使えばSJISをPOSTで渡すことができるけど、そのままだとResponseを受けてもデータとして受け取ってしまってブラウザで表示できないんだよね・・・。そこもjavascriptで表示する?URLがabout:blankになってしまうのはいまいちだと思うのだが。
« 株:現物買い約定(ユニー・ファミリーマートホールディングス)現物売り約定(ヘリオス テクノ ホールディングス) | トップページ | 今週届いた本 »
« 株:現物買い約定(ユニー・ファミリーマートホールディングス)現物売り約定(ヘリオス テクノ ホールディングス) | トップページ | 今週届いた本 »
コメント