windows で C++ から xpathが使いたい!!
windowsでC++からxpathが利用したいんですよ。
どうするべきか。
1.ビルのOSの機能を利用する
ビルのOSだったら、 ActiveXだろうって感じだけど、
msxmlで xpathが利用できるけど、こいつはxml専用でhtmlを読み込むことはできない。
ちょっと前にやって挫折した。。。
→達成された方がいらっしゃいました。すごい!!。
WSH で HTML を XPath したいんじゃあああぁぁ
別途、JavaScript-XPathが必要になります。
2.libxml2 を利用する
phpも内部でlibxmlを利用しているので、この前の phpのルーチンをそのまま移植すればいけそうです。
ただし、libxml は utf8しか扱えないので、 sjis←→utf8変換をしまくる必要があるようです。
http://xmlsoft.org/
↓macosでの例だけどライブラリを使う上では参考になりました。
CocoaでHTMLを扱う
↓この前のルーチン
phpでxpath
3.TinyXPathを利用する
まだ試していないんですが、これでもいけそうです。libxml2 よりこっちのほうが簡単?
TinyXPath
4.xpcom経由で firefoxを呼び出す。
多分これが一番理想なんだろうけど、文献がないのでよくわからん。
↓こーゆーのができるからがんばれば可能だとは思うが、、、
FirefoxのhtmlparserをXPCOM経由で呼び出して壊れたHTMLを修復する
まず、どうやってxpathを作成するかによると思う。
xpathはまだまだ方言があって、実装によって機能が使えたり使えなかったりするので、xpathを作る環境と実際に使う環境とで差異があるとめんどい。
私は、firefoxの xpatherで作成したいので 4のxpcomの方法が取れれば一番いいんだけど、どうやればいいかよくわからない。
libxml2 は utf8変換を考えないなら、とりあえず動いた。
utf8というのがwindowsにとって鬼門だよな。
ビルのOSは、早めにunicode化したのが災いしているよね。
もし、ビルのOSが今unicode化をするならutf8を採用している気がするよね。
そんで世界は utf8の統合されて開発者はみんなハッピーハッピーだったと思う。
いろんなところで使われているライブラリだから、移植性もよさげ。
TinyXPath はサンプルを見る限り簡単そう。
ただ、libxml2 に比べると実績が不安。どうなの?
ぐだくだいわずコードを書くべきか。。。
独断と偏見によるまとめ
ライブラリ名 | 実装難易度 | ユーザの導入し易さ | クロスプラットフォーム | firefoxとの親和性 | ライセンス |
---|---|---|---|---|---|
msxml | ○ | ○ OSディフォは最高なんだけど IEはアレなのでJavaScript-XPathが必要になる |
× windows限定 |
IEはそもそもダメなので JavaScript-XPath次第だけど結構よさげ? |
IEはOSディフォだけど、 JavaScript-XPath がMIT ライセンス |
libxml2 | ○ (windows系だとutf8は鬼門) |
△ libiconvとか依存が、windowsだと面倒 |
◎ | △ 結構癖があるよ |
MITライセンス |
TinyXPath | ? まだ試していない |
◎ 組み込みだしね |
◎ 組み込みだし |
? | zlib/libpngライセンス |
xpcom | × わけわかりません |
△ mozilla firefoxが必要 |
? | ◎ firefoxそのもの |
Mozilla Public License |
COMをhookできないもんかと考える
APIフックはあれど COM フックってないよね。
APIの CoCreateInstance をフックすればいいんぢゃね?
CoCreateInstanceフックしてプロクシの COMオブジェクト返せばいい気がする。
インターフェースとか動的に作成できるのなぁ。
よくわかんない。
COMSPYとかどうやっているんだろうか、、
実はCOMをそんなに深く理解していないので何を足かかりにすればいいかよくわからない。
こつこつブレークポイント立てながら見ていくしかないのかな。
やりたいのは、ODBC というか、ADOをフックしたい。
んで、ADOに渡しているパラメタをぶっこぬきたい。
パスとか、パスとか、パスとかね。
あとクエリーとか処理結果とか。
楽しそうだと思うんだけど。。。
php で xpath
結構前に、Liner NoteさんのPHP汎用スクレイピングライブラリを作ってみたで公開されているソースを元に改造したxpathルーチンを作ったのでおいときます。いろいろあって作ってから1年ぐらい経過したけど。。。
ライセンスは、派生元が修正BSDなんで修正BSDでおながいします。
ダウソする↓
http://rtilabs.net/files/2009_09_02/simple_scraper.zip
メリット。
firefox xpatherぽい値を返してくれるよう最善の努力をしている。
つまり、努力賞ってことね。
<? // //htmlをxpathでぬはぬはするルーチン //FireFoxのxpatherコンパチを目指して挫折しています。 // //派生元 http://note.openvista.jp/2008/php-scraing-library/ //ライセンスは、派生元が修正BSDなんで修正BSDでおながいします。 // //このソフトウェア上記 scraper.Class.php の内容を改変したものが含まれています。 class SimpleScraper { //読み込んだHTML var $XML = FALSE; //おまけ ファイルまたは、HTTP GET public function LoadURL($url,$encode = NULL) { return $this->LoadHTML(file_get_contents($url),$encode); } //HTTP POST public function LoadURLPost($url,$data,$encode = NULL) { $context = array( "http" => array( "method" => "POST" ,"header" => array( "Content-Type: application/x-www-form-urlencoded" ,"Content-Length: ".strlen($data) ) ,"content" => $data ) ); return $this->LoadHTML(file_get_contents($url, false, stream_context_create($context)),$encode); } //文字列から読み込む public function LoadHTML($html,$encode = NULL) { //UTF-8にする if ($encode == NULL) { $html = mb_convert_encoding($html,'UTF-8',$this->getContentType($html)); } else if ($encode = 'pass') { //nop } else { $html = mb_convert_encoding($html,'UTF-8',$encode); } // コメントを削除 $html = preg_replace("/<!--.*?-->/iusm", "", $html); $html = preg_replace("/&/iusm", "&", $html); //scriptタグを消滅させる $html = preg_replace("/<script[^>]*?>.*?<\/script>/iusm", "", $html); $html = preg_replace("/<noscript[^>]*?>.*?<\/noscript>/iusm", "", $html); //styleタグを消滅させる $html = preg_replace("/<style[^>]*?>.*?<\/style>/iusm", "", $html); //DOMDocumentの文字化け対策1 charsetを消す $html = preg_replace('/<meta[^>]*?charset[^>]*?>/uis', '', $html); //DOMDocumentの文字化け対策2 headの直後に utf8のcharsetを入れる $html = preg_replace('/<head[^>]*?>/uis', '<meta http-equiv="content-type" content="text/html; charset=utf-8">', $html); //DOCTYPE が付いていないならつける if (!preg_match("/<!DOCTYPE /ius",$html)) { $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' ."\r\n". $html; } $html = preg_replace('/xml:lang="ja"/uis','',$html); //DOMに読み込ませる $dom = new DOMDocument("1.0", "utf-8"); @$dom->loadHTML($html); // DOMをSimpleXMLへ変換 $ret = simplexml_import_dom($dom); $html = $ret->asXML(); // XML宣言付与 if (true !== preg_match('/^<\\?xml version="1.0"/uis', $html)) { $html = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $html; } //xmlnsの消去 ネームスペースの関係でうまく動作しないことがある //xmlnsだけ消すのは大変だから、htmlタグ自体を交換しちゃうよ! $html = preg_replace("/<html [^>]*?xmlns=[^>]*?>/uis",'<html>',$html); $this->XML = simplexml_load_string($html); return ($this->XML !== FALSE); } //xpathで取得した結果をテキストでもらう //xpather の Textに相当する内容 public function XPathText($xpath) { assert($this->XML !== FALSE); $retNodes = array(); $isAttribute = $this->IsAttributeXPath($xpath); $arr = $this->XML->xpath($xpath); if (!is_array($arr)) { return array(); } foreach($arr as $node) { if ($isAttribute) { $h = (string)$node; } else { $h = preg_replace('/<.*?>/uis' , '' , (string)$node->asXML()); } //& -> &asm; となってしまうので戻す. $retNodes[] = $this->EscapeCodeReverse($h); } return $retNodes; } //xpathで取得した結果をInnerHTMLでもらう //xpather の InnerHTMLに相当する内容 public function XPathInnerHTML($xpath) { assert($this->XML !== FALSE); $retNodes = array(); $isAttribute = $this->IsAttributeXPath($xpath); $arr = $this->XML->xpath($xpath); if (!is_array($arr)) { return array(); } foreach($arr as $node) { if ($isAttribute) {//属性だと取れません $retNodes[] = NULL; } else { $tagName = $node->getName(); $h = preg_replace('/^<'.$tagName.'[^>]*?>(.*?)<\/'.$tagName.'[^>]*?>$/uis' ,'\1', (string)$node->asXML() ); //& -> &asm; となってしまうので戻す. $retNodes[] = $this->EscapeCodeReverse($h); } } return $retNodes; } //xpathで取得した結果をOuterHTMLでもらう //InnerHTMLがあるなら OuterHTMLもほしいぢゃん。 public function XPathOuterHTML($xpath) { assert($this->XML !== FALSE); $retNodes = array(); $isAttribute = $this->IsAttributeXPath($xpath); $arr = $this->XML->xpath($xpath); if (!is_array($arr)) { return array(); } foreach($arr as $node) { if ($isAttribute) {//属性だと取れません $retNodes[] = NULL; } else { $retNodes[] = $this->EscapeCodeReverse((string)$node->asXML()); } } return $retNodes; } //xpathで指定したデータを削除する. public function XPathRemove($xpath) { foreach( $this->XPathNode($xpath) as $node ) { $node->parentNode->removeChild($node); } return TRUE; } //xpathで取得した結果をそのままNodeでもらう public function XPathNode($xpath) { assert($this->XML !== FALSE); return $this->XML->xpath($xpath); } //文字コード判別 private function getContentType($html) { $matches = array(); preg_match("/<meta.*?charset=(?:\"')?([0-9A-Za-z\-_]+)(?:\"')?/uis",$html ,$matches ); if (! isset($matches[1]) ) { mb_detect_order("sjis-win,eucjp-win,euc-jp,UTF-8"); return mb_detect_encoding($html); } else { return $matches[1]; } } //属性を求めるxpathですか? private function IsAttributeXPath($xpath) { // /span/@class みたいなヤツ? if ( preg_match('/\/@[^\/]+$/uis',$xpath) ) { return true; } return false; } //XMLの使用上エスケープされてしまう文字列を元に戻す private function EscapeCodeReverse($text) { $text = preg_replace("/>/uis","<",$text); $text = preg_replace("/</uis",">",$text); $text = preg_replace("/&/uis","&",$text); return $text; } }; ?>
使い方
<? //このソースはサンプルなので、 //ソースで echo なんてサンプルソースしかできないダーティな技をたくさん利用していますwww //ライブラリのロード require_once('simple_scraper.inc'); //構築 $ss = new SimpleScraper(); //読み込み 文字コードは自動推測してUTF-8に変換してくれます。 $ret = $ss->LoadURL("http://www.yahoo.co.jp/"); if (!$ret) { echo "パースできませんでした。"; exit; } //textだけよこせ echo "<h2>textだけよこせ</h2>"; $nodes = $ss->XPathText("//table"); foreach($nodes as $n) { echo htmlspecialchars($n,ENT_QUOTES) . "<br /><br />"; } //innerHTMLでよこせ echo "<h2>innerHTMLでよこせ</h2>"; $nodes = $ss->XPathInnerHTML("//table"); foreach($nodes as $n) { echo htmlspecialchars($n,ENT_QUOTES) . "<br /><br />"; } //OuterHTMLでよこせ echo "<h2>OuterHTMLでよこせ</h2>"; $nodes = $ss->XPathOuterHTML("//table"); foreach($nodes as $n) { echo htmlspecialchars($n,ENT_QUOTES) . "<br /><br />"; } //meta の Description値 echo "<h2>meta の description値</h2>"; $r = $ss->XPathText("//meta[@name='description']/@content"); echo $r[0]; ?>
バグがあったらごめんね。
EDFはバグを恐れない。