PageRenderTime 53ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/plugins/DBML/libs/PxXMLDomParser/PxXMLDomParser.php

https://github.com/tomk79/PxPlugin_DBML
PHP | 1027 lines | 734 code | 109 blank | 184 comment | 175 complexity | c1c9059abe75d4b35b75851a0b753375 MD5 | raw file
  1. <?php
  2. #============================================================================
  3. # PxXMLDomParser
  4. # varsion 1.0.2
  5. # (C)Tomoya Koyanagi.
  6. # LastUpdate : 19:18 2010/09/08
  7. class pxplugin_DBML_libs_PxXMLDomParser_PxXMLDomParser{
  8. var $last_find_selector = null; //←前回のfind()に使用したセレクタ文字列を記憶。
  9. var $bin = null; //←XMLの本体を格納(String)
  10. var $pattern_html = '(?:[a-z0-9A-Z_-]+\:)?[a-zA-Z][a-z0-9A-Z_-]*';
  11. var $pattern_attribute = '(?:[a-z0-9A-Z_-]+\:)?[a-z0-9A-Z_-]+';
  12. var $safetycounter_limit = 10000000;
  13. # 閉じタグを探すループの回数制限
  14. # 無限ループに陥ってしまわないための措置
  15. var $errorlist = array();
  16. # 内部エラーを記憶する器
  17. var $confvalues = array( // ←各種設定 PxXMLDomParser 1.0.1 追加
  18. //この設定値は、config()を通じて入出力してください。
  19. 'tags_case_sensitive'=>true ,//←タグの大文字小文字を区別するか。true=区別する(default),false=区別しない;
  20. 'atts_case_sensitive'=>true ,//←属性の大文字小文字を区別するか。true=区別する(default),false=区別しない;
  21. );
  22. #--------------------------------------
  23. # コンストラクタ
  24. function __construct( $bin , $input_type = null ){
  25. if( !strlen( $input_type ) ){
  26. # $input_type (入力の種類)を省略したら自動的に判断する
  27. if( @is_file( $bin ) || preg_match( '/^https?\:\/\//' , $bin ) ){
  28. $input_type = 'path';
  29. }else{
  30. $input_type = 'bin';
  31. }
  32. }
  33. if( $input_type == 'path' ){
  34. # 指定されたのがパスだったら
  35. $bin = file_get_contents( $bin );
  36. }
  37. $detect_encoding = $this->html_detect_encoding_by_src( $bin );
  38. if( strlen( $detect_encoding ) && is_callable( 'mb_internal_encoding' ) ){
  39. $bin = $this->convert_encoding( $bin , mb_internal_encoding() , $detect_encoding );
  40. }
  41. $this->bin = $bin;
  42. }
  43. #--------------------------------------
  44. # 設定の入出力
  45. function config( $key , $val = null ){
  46. if( !strlen( $key ) ){ return null; }
  47. $args = func_get_args();
  48. if( count($args) <= 1 ){
  49. //GETモード
  50. return $this->confvalues[$key];
  51. }else{
  52. //SETモード
  53. switch( strtolower($key) ){
  54. case 'tags_case_sensitive':
  55. case 'atts_case_sensitive':
  56. //boolな項目
  57. $this->confvalues[$key] = !empty($val);
  58. break;
  59. default:
  60. //なんでもありな項目
  61. $this->confvalues[$key] = $val;
  62. break;
  63. }
  64. return true;
  65. }
  66. }
  67. #--------------------------------------
  68. # セレクタをセットする(セットするだけ)
  69. function select( $selector ){
  70. $selector = trim( $selector );
  71. if( !strlen( $selector ) ){ return false; }
  72. $this->last_find_selector = $selector;
  73. return true;
  74. }
  75. #--------------------------------------
  76. # セレクタから要素を探す
  77. function find( $selector ){
  78. $selector = trim( $selector );
  79. if( !strlen( $selector ) ){ return false; }
  80. $this->last_find_selector = $selector;
  81. return $this->dom_command( 'find' );
  82. }
  83. #--------------------------------------
  84. # 属性をセットする
  85. function attr( $attname , $value ){
  86. if( !strlen( $this->last_find_selector ) ){ return false; }
  87. return $this->dom_command( 'att' , array('attname'=>trim($attname),'value'=>$value) );
  88. }
  89. #--------------------------------------
  90. # スタイルをセットする
  91. function css( $property , $value ){
  92. if( !strlen( $this->last_find_selector ) ){ return false; }
  93. return $this->dom_command( 'css' , array('property'=>trim($property),'value'=>$value) );
  94. }
  95. #--------------------------------------
  96. # CSSクラスを追加する
  97. function addclass( $className ){
  98. if( !strlen( $this->last_find_selector ) ){ return false; }
  99. return $this->dom_command( 'addclass' , array('className'=>trim($className)) );
  100. }
  101. #--------------------------------------
  102. # CSSクラスを削除する
  103. function removeclass( $className ){
  104. if( !strlen( $this->last_find_selector ) ){ return false; }
  105. return $this->dom_command( 'removeclass' , array('className'=>trim($className)) );
  106. }
  107. #--------------------------------------
  108. # innerHTMLをセットする
  109. function html( $html ){
  110. if( !strlen( $this->last_find_selector ) ){ return false; }
  111. return $this->dom_command( 'html' , array('html'=>$html) );
  112. }
  113. #--------------------------------------
  114. # innerHTMLにテキストをセットする
  115. function text( $html ){
  116. if( !strlen( $this->last_find_selector ) ){ return false; }
  117. return $this->dom_command( 'html' , array('html'=>htmlspecialchars($html)) );
  118. }
  119. #--------------------------------------
  120. # outerHTMLを置き換える
  121. function replace( $method ){
  122. if( !strlen( $this->last_find_selector ) ){ return false; }
  123. return $this->dom_command( 'replace' , array('replace_method'=>$method) );
  124. }
  125. #--------------------------------------
  126. # 現時点でのソースコード全体を取得する
  127. function get_src(){
  128. return $this->bin;
  129. }
  130. #--------------------------------------
  131. # HTMLファイル $path の文字エンコード名を得る
  132. function html_detect_encoding( $path ){
  133. if( !is_file( $path ) ){ return false; }
  134. $src = file_get_contents( $path );
  135. return $this->html_detect_encoding_by_src( $src );
  136. }
  137. function html_detect_encoding_by_src( $bin = null ){
  138. if( is_null( $bin ) ){ $bin = $this->bin; }
  139. if( is_null( $bin ) ){ return false; }
  140. $RTN = null;
  141. if( !strlen( $RTN ) && preg_match( '/<meta(.*?)>/si' , $bin ) ){
  142. preg_match_all( '/<meta(.*?)>/si' , $bin , $matched );
  143. foreach( $matched[0] as $num=>$value ){
  144. $attrStr = $matched[1][$num];
  145. $attr = array();
  146. while( strlen( trim( $attrStr ) ) ){
  147. preg_match( '/([a-zA-Z0-9\-\_]+?)=(?:("|\'|)(.*?)\2|(.*?)(?:\s+|$))(.*)$/s' , $attrStr , $matched2 );
  148. $keyStr = $matched2[1];
  149. if( !is_null( $matched2[3] ) ){
  150. $valStr = $matched2[3];
  151. }else{
  152. $valStr = $matched2[4];
  153. }
  154. $attrStr = trim( $matched2[5] );
  155. $attr[strtolower($keyStr)] = $valStr;
  156. if( $attrStr == '/' ){
  157. $attrStr = '';
  158. }
  159. }
  160. if( strtolower( $attr['http-equiv'] ) == 'content-type' ){
  161. $content = trim( strtolower( $attr['content'] ) );
  162. if( preg_match( '/^[a-zA-Z0-9\-\_]+\/[a-zA-Z0-9\-\_]+\s*\;\s*charset\=(.*)$/si' , $content , $matched3 ) ){
  163. switch( strtolower( $matched3[1] ) ){
  164. case 'sjis':
  165. case 'shift_jis':
  166. return 'Shift_JIS';break;
  167. case 'utf-8':
  168. case 'utf8':
  169. return 'UTF-8';break;
  170. case 'euc-jp':
  171. return 'EUC-JP';break;
  172. }
  173. }
  174. }
  175. }
  176. }
  177. if( !strlen( $RTN ) && is_callable('mb_detect_encoding') ){
  178. $i = 0;
  179. while( 1 ){
  180. $RTN = mb_detect_encoding( $bin );
  181. if( $RTN !== false ){
  182. break;
  183. }
  184. $bin = preg_replace( '/^.*?[a-zA-Z0-9'.preg_quote('-_.,=<>{}[]()"\'^~+*:;/|','/').']+/s' , '' , $bin );
  185. if( $i > 10000 ){ break; }
  186. }
  187. }
  188. return $RTN;
  189. }
  190. #--------------------------------------
  191. # HTMLファイルからコンテンツ領域のみを抜き出す
  192. function get_contents( $options = null ){
  193. if( strlen( $options['start'] ) && strlen( $options['end'] ) ){
  194. # マークで見つける
  195. $bin = $this->get_contents_by_mark( $options['start'] , $options['end'] );
  196. }elseif( strlen( $options['selector'] ) ){
  197. # セレクタで見つける
  198. $bin = $this->find( $options['selector'] );
  199. }
  200. return $bin;
  201. }
  202. #--------------------------------------
  203. # コンテンツをマークで見つける
  204. function get_contents_by_mark( $mark_start , $mark_end ){
  205. $bin = $this->bin;
  206. $detect_encoding = $this->html_detect_encoding_by_src( $bin );
  207. if( $detect_encoding ){
  208. $bin = $this->convert_encoding( $bin , mb_internal_encoding() , $detect_encoding );
  209. }
  210. if( preg_match( '/'.preg_quote( $mark_start , '/' ).'(.*)$/s' , $bin , $matched ) ){
  211. $bin = $matched[1];
  212. }
  213. if( preg_match( '/^(.*)'.preg_quote( $mark_end , '/' ).'/s' , $bin , $matched ) ){
  214. $bin = $matched[1];
  215. }
  216. return $bin;
  217. }//get_contents_by_mark();
  218. # / コンテンツをマークで見つける
  219. #--------------------------------------
  220. #--------------------------------------
  221. # DOMコマンドを実行する
  222. function dom_command( $command_name = 'find' , $option = array() ){
  223. if( !strlen( $this->last_find_selector ) ){ return false; }
  224. $selector = $this->last_find_selector;
  225. $bin = $this->bin;
  226. # セレクタを整理する
  227. $selectorInfoList = $this->parse_cssselector( $selector );
  228. $RTN = array();//返却値:ヒットしたinnerHTMLを上から順に格納する
  229. $pedigree = array();//上位階層エレメント情報を世代順に格納する
  230. $pattern_html = $this->get_pattern_html( null );//←全部のエレメントが調査対象
  231. $str_prev = '';
  232. $str_next = $bin;
  233. while( strlen( $str_next ) ){
  234. $is_hit = 0;
  235. $str_nextMemo = '';
  236. while( 1 ){
  237. # PxFW 0.6.6 : ヒットしなかった場合にリトライするようにした
  238. # PxFW 0.6.7 : リトライのロジックを見直した
  239. # (http://www.pxt.jp/ja/diary/article/218/index.html この問題への対応)
  240. $tmp_start = strpos( $str_next , '<' );
  241. if( !is_int( $tmp_start ) || $tmp_start < 0 ){
  242. # [<]記号 が見つからなかったら
  243. # 本当にヒットしなかったものとみなせる
  244. $str_next = $str_nextMemo.$str_next;
  245. break;
  246. }
  247. $str_nextMemo .= substr( $str_next , 0 , $tmp_start );
  248. $str_next = substr( $str_next , $tmp_start );
  249. $is_hit = preg_match( $pattern_html , $str_next , $results );
  250. if( $is_hit ){
  251. # ヒットしたらここでbreak;
  252. $results[1] = $str_nextMemo.$results[1];
  253. $str_next = $str_nextMemo.$str_next;
  254. break;
  255. }
  256. //今回先頭にあるはずの[<]記号を $str_nextMemo に移してリトライ
  257. $str_nextMemo .= substr( $str_next , 0 , 1 );
  258. $str_next = substr( $str_next , 1 );
  259. }
  260. unset( $str_nextMemo );
  261. unset( $tmp_start );
  262. if( !$is_hit ){
  263. $this->bin = $str_prev.$str_next;
  264. return $RTN;
  265. }
  266. if( !is_null( $results[0] ) ){
  267. $MEMO = array();
  268. $preg_number = 0;
  269. $preg_number ++;
  270. $MEMO['str_prev'] = $results[$preg_number++];
  271. $MEMO['start_tag'] = $results[$preg_number++];
  272. $MEMO['commentout'] = $results[$preg_number++];
  273. $MEMO['cdata'] = $results[$preg_number++];
  274. $MEMO['php_script'] = $results[$preg_number++];
  275. $MEMO['close_tag_mark'] = $results[$preg_number++];
  276. $MEMO['tag'] = $results[$preg_number++];
  277. $MEMO['tagOriginal'] = $MEMO['tag'];
  278. $MEMO['attribute_str'] = $results[$preg_number++];
  279. $MEMO['att_quot'] = $results[$preg_number++];
  280. $MEMO['self_closed_flg'] = $results[$preg_number++];
  281. $MEMO['str_next'] = $results[$preg_number++];
  282. unset( $preg_number );
  283. $MEMO['attribute'] = $this->html_attribute_parse( $MEMO['attribute_str'] );
  284. $str_prev .= $MEMO['str_prev'];
  285. $str_next = $MEMO['str_next'];
  286. if( strlen( $MEMO['commentout'] ) || strlen( $MEMO['php_script'] ) ){
  287. $str_prev .= $MEMO['start_tag'];
  288. continue;
  289. }
  290. //閉じタグ探しとく
  291. $searched_closetag = array();
  292. if( !strlen( $MEMO['close_tag_mark'] ) && !strlen( $MEMO['self_closed_flg'] ) ){
  293. $searched_closetag = $this->search_closetag( $MEMO['tagOriginal'] , $MEMO['str_next'] );
  294. if( $searched_closetag['content_str'] === false && strlen( $searched_closetag['error'] ) ){
  295. # 閉じタグがなかったら(パースエラー)
  296. # 自己完結タグなことにしちゃう
  297. $searched_closetag['content_str'] = null;
  298. // $MEMO['start_tag'] = preg_replace( '/\/*>$/si' , ' />' , $MEMO['start_tag'] );
  299. $MEMO['self_closed_flg'] = '/';
  300. }
  301. }
  302. if( strlen( $MEMO['self_closed_flg'] ) ){
  303. //自己完結タグを見つけた場合
  304. array_push( $pedigree , array( 'tag'=>$MEMO['tag'] , 'attribute'=>$MEMO['attribute'] ) );
  305. }else{
  306. if( !strlen( $MEMO['close_tag_mark'] ) ){
  307. //開始タグを見つけた場合
  308. array_push( $pedigree , array( 'tag'=>$MEMO['tag'] , 'attribute'=>$MEMO['attribute'] ) );
  309. }else{
  310. //閉じタグを見つけた場合
  311. array_pop( $pedigree );
  312. $str_prev .= $MEMO['start_tag'];
  313. continue;
  314. }
  315. }
  316. //カレントの要素が収集対象か否か評価
  317. $is_hit = $this->is_element_hit( $pedigree , $selectorInfoList );
  318. if( strlen( $MEMO['self_closed_flg'] ) ){
  319. # 自己完結タグだったら
  320. array_pop( $pedigree );
  321. }
  322. if( $is_hit ){
  323. if( $command_name == 'html' && strlen( $option['html'] ) ){
  324. $MEMO['self_closed_flg'] = null;
  325. }
  326. #--------------------------------------
  327. # 収集対象だったら
  328. if( $command_name == 'html' || $command_name == 'replace' || $command_name == 'addclass' || $command_name == 'removeclass' || $command_name == 'att' || $command_name == 'css' ){
  329. if( $command_name == 'addclass' || $command_name == 'removeclass' ){
  330. #--------------------------------------
  331. # クラスの追加削除
  332. $classList = array();
  333. if( strlen( trim( $MEMO['attribute']['class'] ) ) ){
  334. $classList = preg_split( '/\s+/si' , trim( $MEMO['attribute']['class'] ) );
  335. }
  336. $className_ishit = false;
  337. foreach( $classList as $classNum=>$classLine ){
  338. if( $option['className'] == $classLine ){
  339. if( $command_name == 'removeclass' ){
  340. unset( $classList[$classNum] );//削除する場合
  341. }
  342. $className_ishit = true;
  343. break;
  344. }
  345. }
  346. if( $command_name == 'addclass' && !$className_ishit ){
  347. array_push( $classList , $option['className'] );
  348. }
  349. if( count( $classList ) ){
  350. $MEMO['attribute']['class'] = implode( ' ' , $classList );
  351. }else{
  352. unset( $MEMO['attribute']['class'] );
  353. }
  354. unset( $classList );
  355. # / クラスの追加削除
  356. #--------------------------------------
  357. }elseif( $command_name == 'att' ){
  358. #--------------------------------------
  359. # 属性のセット
  360. if( strlen( $option['attname'] ) ){
  361. if( !is_null( $option['value'] ) ){
  362. $MEMO['attribute'][$option['attname']] = $option['value'];
  363. }else{
  364. unset( $MEMO['attribute'][$option['attname']] );
  365. }
  366. }
  367. # / 属性のセット
  368. #--------------------------------------
  369. }elseif( $command_name == 'css' ){
  370. #--------------------------------------
  371. # スタイルのセット
  372. $styleList = array();
  373. if( strlen( trim( $MEMO['attribute']['style'] ) ) ){
  374. $tmp_styleList = preg_split( '/\s*\;\s*/si' , trim( $MEMO['attribute']['style'] ) );
  375. foreach( $tmp_styleList as $tmp_styleNum=>$tmp_styleLine ){
  376. if( strlen( trim($tmp_styleLine) ) ){
  377. $tmp_styleLine = explode(':',trim($tmp_styleLine));
  378. $styleList[trim(strtolower($tmp_styleLine[0]))] = trim($tmp_styleLine[1]);
  379. }
  380. }
  381. unset( $tmp_styleList );
  382. }
  383. $styleName_ishit = false;
  384. foreach( $styleList as $styleName=>$styleLine ){
  385. if( trim( strtolower( $option['property'] ) ) == trim( strtolower( $styleName ) ) ){
  386. if( is_null( $option['value'] ) ){
  387. unset( $styleList[$styleName] );//削除する場合
  388. }
  389. $styleName_ishit = true;
  390. break;
  391. }
  392. }
  393. if( !is_null( $option['value'] ) ){
  394. $styleList[trim( strtolower( $option['property'] ) )] = trim( $option['value'] );
  395. }
  396. if( count( $styleList ) ){
  397. $tmp_styleList = array();
  398. foreach( $styleList as $styleName=>$styleLine ){
  399. array_push( $tmp_styleList , implode(':',array($styleName,$styleLine)) );
  400. }
  401. $MEMO['attribute']['style'] = implode( '; ' , $tmp_styleList ).';';
  402. unset( $tmp_styleList );
  403. }else{
  404. unset( $MEMO['attribute']['style'] );
  405. }
  406. unset( $styleList );
  407. # / スタイルのセット
  408. #--------------------------------------
  409. }
  410. $starttag = '<'.$MEMO['tagOriginal'];
  411. if( is_array($MEMO['attribute']) ){
  412. foreach( $MEMO['attribute'] as $attName=>$attVal ){
  413. $starttag .= ' '.htmlspecialchars($attName);
  414. if( !is_null( $attVal ) ){
  415. $starttag .= '="'.htmlspecialchars( $attVal ).'"';
  416. }
  417. }
  418. }
  419. if( strlen( $MEMO['self_closed_flg'] ) ){
  420. $starttag .= ' '.$MEMO['self_closed_flg'];
  421. }
  422. $starttag .= '>';
  423. $MEMO['start_tag'] = $starttag;
  424. unset($starttag);
  425. }
  426. $tmpRTN = array();
  427. $tmpRTN['tagName'] = $MEMO['tagOriginal'];
  428. $tmpRTN['innerHTML'] = $searched_closetag['content_str'];
  429. if( strlen( $searched_closetag['content_str'] ) ){
  430. $tmpRTN['outerHTML'] = $MEMO['start_tag'].$searched_closetag['content_str'].'</'.$MEMO['tagOriginal'].'>';
  431. }else{
  432. $tmpRTN['outerHTML'] = $MEMO['start_tag'];
  433. }
  434. $tmpRTN['attributes'] = $MEMO['attribute'];
  435. if( $command_name == 'html' ){
  436. # HTMLの置き換え要求への対応
  437. if( count( $searched_closetag ) ){
  438. $searched_closetag['content_str'] = $option['html'];
  439. $MEMO['start_tag'] .= $option['html'].'</'.$MEMO['tagOriginal'].'>';
  440. $str_next = $searched_closetag['str_next'];
  441. }elseif( strlen( $option['html'] ) ){
  442. $MEMO['start_tag'] .= $option['html'].'</'.$MEMO['tagOriginal'].'>';
  443. }
  444. $tmpRTN['innerHTML'] = $option['html'];
  445. $tmpRTN['outerHTML'] = $MEMO['start_tag'];
  446. }elseif( $command_name == 'replace' ){
  447. # HTMLの書き換え要求への対応
  448. if( is_array( $option['replace_method'] ) ){
  449. if( is_object( $option['replace_method'][0] ) ){
  450. $tmpRTN['outerHTML'] = $option['replace_method'][0]->$option['replace_method'][1]( $tmpRTN , count($RTN) );
  451. }elseif( is_string( $option['replace_method'][0] ) && class_exists( $option['replace_method'][0] ) ){
  452. $tmpRTN['outerHTML'] = eval( 'return '.$option['replace_method'][0].'::'.$option['replace_method'][1].'( $tmpRTN , count($RTN) );' );
  453. }
  454. }else{
  455. $tmpRTN['outerHTML'] = $option['replace_method']( $tmpRTN , count($RTN) );
  456. }
  457. $MEMO['start_tag'] = $tmpRTN['outerHTML'];
  458. if( count( $searched_closetag ) ){
  459. $str_next = $searched_closetag['str_next'];
  460. }
  461. }
  462. array_push( $RTN , $tmpRTN );
  463. unset( $tmpRTN );
  464. }
  465. $str_prev .= $MEMO['start_tag'];
  466. unset( $searched_closetag );
  467. }
  468. unset( $MEMO );
  469. }
  470. $this->bin = $str_prev.$str_next;
  471. return $RTN;
  472. }//dom_command();
  473. # / コンテンツをセレクタで見つける
  474. #--------------------------------------
  475. #--------------------------------------
  476. # セレクタにヒットする要素か否か調べる
  477. function is_element_hit( $pedigree , $selectorInfoList ){
  478. $kouhoList = array();
  479. array_push( $kouhoList , $pedigree );
  480. #--------------------------------------
  481. # セレクタを順に解釈
  482. foreach( $selectorInfoList as $selectorNum=>$selectorInfo ){
  483. $tmp_kouhoList = array();
  484. #--------------------------------------
  485. # 残っている候補を順に評価
  486. foreach( $kouhoList as $kouho ){
  487. $tmp_kouho = array();
  488. for( $i = 0; $i < count( $kouho ); $i ++ ){
  489. $is_hit = true;
  490. # タグ名を評価
  491. if( strlen( $selectorInfo['tagName'] ) && $selectorInfo['tagName'] != '*' ){
  492. if( !$this->config( 'tags_case_sensitive' ) ){
  493. //大文字小文字の区別をしない : PxXMLDomParser 1.0.1
  494. if( strtolower($selectorInfo['tagName']) != strtolower($kouho[$i]['tag']) ){
  495. $is_hit = false;//ヒットしない
  496. }
  497. }else{
  498. if( $selectorInfo['tagName'] != $kouho[$i]['tag'] ){
  499. $is_hit = false;//ヒットしない
  500. }
  501. }
  502. }
  503. # ID名を評価
  504. if( strlen( $selectorInfo['id'] ) ){
  505. if( !$this->config( 'atts_case_sensitive' ) ){
  506. //大文字小文字の区別をしない : PxXMLDomParser 1.0.1
  507. if( strtolower($selectorInfo['id']) != strtolower($kouho[$i]['attribute']['id']) ){
  508. $is_hit = false;//ヒットしない
  509. }
  510. }else{
  511. if( $selectorInfo['id'] != $kouho[$i]['attribute']['id'] ){
  512. $is_hit = false;//ヒットしない
  513. }
  514. }
  515. }
  516. # class名を評価
  517. if( is_array( $selectorInfo['class'] ) && count( $selectorInfo['class'] ) ){
  518. $classname_is_hit = false;
  519. foreach( $selectorInfo['class'] as $selector_class ){
  520. foreach( preg_split( '/\s+/' , $kouho[$i]['attribute']['class'] ) as $tag_class ){
  521. if( !strlen( $tag_class ) ){ continue; }
  522. if( !$this->config( 'atts_case_sensitive' ) ){
  523. //大文字小文字の区別をしない : PxXMLDomParser 1.0.1
  524. if( strtolower($selector_class) == strtolower($tag_class) ){
  525. $classname_is_hit = true;
  526. break 2;
  527. }
  528. }else{
  529. if( $selector_class == $tag_class ){
  530. $classname_is_hit = true;
  531. break 2;
  532. }
  533. }
  534. }
  535. }
  536. if( !$classname_is_hit ){
  537. $is_hit = false;//ヒットしない
  538. }
  539. }
  540. # 属性値を評価
  541. if( is_array( $selectorInfo['attributes'] ) && count( $selectorInfo['attributes'] ) ){
  542. $att_is_hit = false;
  543. foreach( $selectorInfo['attributes'] as $selector_att_key=>$selector_att_val ){
  544. if( !$this->config( 'atts_case_sensitive' ) ){
  545. //大文字小文字の区別をしない : PxXMLDomParser 1.0.1
  546. if( strtolower($kouho[$i]['attribute'][$selector_att_key]) == strtolower($selector_att_val) ){
  547. $att_is_hit = true;
  548. break;
  549. }
  550. }else{
  551. if( $kouho[$i]['attribute'][$selector_att_key] == $selector_att_val ){
  552. $att_is_hit = true;
  553. break;
  554. }
  555. }
  556. }
  557. if( !$att_is_hit ){
  558. $is_hit = false;//ヒットしない
  559. }
  560. }
  561. if( $selectorInfo['child_flg'] && !$is_hit ){
  562. # 次のセレクタが直子じゃないと該当しない指示の場合
  563. $is_hit = false;//ヒットしない
  564. break;//かつ、この候補はこれで寿命が尽きる。
  565. }
  566. # 候補として残す
  567. if( $is_hit ){
  568. $tmp_tmp_kouho = array();
  569. for( $tmp_start_i = $i+1; $tmp_start_i < count( $kouho ); $tmp_start_i ++ ){
  570. array_push( $tmp_tmp_kouho , $kouho[$tmp_start_i] );
  571. }
  572. array_push( $tmp_kouhoList , $tmp_tmp_kouho );
  573. }
  574. }
  575. }
  576. # / 残っている候補を順に評価
  577. #--------------------------------------
  578. $kouhoList = $tmp_kouhoList;
  579. unset( $tmp_kouhoList );
  580. if( !count( $kouhoList ) ){
  581. break;//候補がなくなったら終わり。
  582. }
  583. }
  584. # / セレクタを順に解釈
  585. #--------------------------------------
  586. if( !count( $kouhoList ) ){
  587. # 残ってなかったら false
  588. return false;
  589. }
  590. foreach( $kouhoList as $kouho ){
  591. if( count( $kouho ) === 0 ){
  592. # 要素の階層が1層だけ残っている場合のみ
  593. # その要素自身が対象となる
  594. return true;
  595. }
  596. }
  597. return false;
  598. }
  599. # / セレクタにヒットする要素か否か調べる
  600. #--------------------------------------
  601. #--------------------------------------
  602. # セレクタを整理する
  603. function parse_cssselector( $selector ){
  604. $tmp_selectorList = preg_split( '/\s+/' , $selector );
  605. $selectorList = array();
  606. $tmp_memo = '';
  607. foreach( $tmp_selectorList as $line ){
  608. $line = trim( $line );
  609. $tmp_memo .= $line;
  610. if( $tmp_memo == '>' ){
  611. continue;
  612. }
  613. array_push( $selectorList , $tmp_memo );
  614. $tmp_memo = '';
  615. }
  616. unset( $tmp_selectorList );
  617. unset( $tmp_memo );
  618. unset( $line );
  619. $RTN = array();
  620. #--------------------------------------
  621. # セレクタを順番に検索する
  622. for( $i = 0; $i < count( $selectorList ); $i ++ ){
  623. $selectorLine = $selectorList[$i];
  624. #--------------------------------------
  625. # セレクタを解析する
  626. $selectorInfo = array();
  627. $selectorInfo['child_flg'] = false;
  628. if( preg_match( '/^>(.*)$/si' , $selectorLine , $matched ) ){
  629. $selectorInfo['child_flg'] = true;
  630. $selectorLine = $matched[1];
  631. }
  632. preg_match( '/^((?:[a-zA-Z0-9\-\_]+\:)?[a-zA-Z0-9\-\_]+)?((?:(?:#|\\.)[a-zA-Z0-9\-\_]+)*)?((?:\[[0-9a-zA-Z\-\_]+\=[0-9a-zA-Z\-\_]*\])*)$/s' , $selectorLine , $matched );
  633. $selectorInfo['tagName'] = $matched[1];
  634. $selectorInfo['id'] = null;
  635. $selectorInfo['class'] = array();
  636. $selectorInfo['attributes'] = array();
  637. $str_options = trim( $matched[2] );
  638. $str_attributes = trim( $matched[3] );
  639. while( strlen( $str_options ) ){
  640. if( !preg_match( '/^(#|\\.)([a-zA-Z0-9\-\_]+)(.*)$/s' , $str_options , $matched ) ){
  641. break;
  642. }
  643. $type = $matched[1];
  644. $name = $matched[2];
  645. $str_options = trim( $matched[3] );
  646. if( $type == '#' ){
  647. $selectorInfo['id'] = $name;
  648. }elseif( $type == '.' ){
  649. array_push( $selectorInfo['class'] , $name );
  650. }
  651. }
  652. while( strlen( $str_attributes ) ){
  653. if( !preg_match( '/^\[(.*?)=(.*?)\](.*)$/s' , $str_attributes , $matched ) ){
  654. break;
  655. }
  656. $key = $matched[1];
  657. $val = $matched[2];
  658. $str_attributes = trim( $matched[3] );
  659. $selectorInfo['attributes'][trim($key)] = trim($val);
  660. }
  661. unset( $type );
  662. unset( $name );
  663. unset( $matched );
  664. # / セレクタを解析する
  665. #--------------------------------------
  666. array_push( $RTN , $selectorInfo );
  667. }
  668. # / セレクタを順番に検索する
  669. #--------------------------------------
  670. return $RTN;
  671. }//parse_cssselector()
  672. # / セレクタを整理する
  673. #--------------------------------------
  674. #--------------------------------------
  675. # HTMLタグを検出するPREGパターンを生成して返す
  676. # (base_resources_htmlparser からの移植->改造)
  677. function get_pattern_html( $tagName = null ){
  678. # タグの種類
  679. $tag = $this->pattern_html;
  680. if( strlen( $tagName ) ){
  681. $tag = $tagName;
  682. }
  683. $att = $this->pattern_attribute;
  684. $rnsp = '(?:\r\n|\r|\n| |\t)';
  685. # 属性のパターン
  686. # 属性の中にタグがあってはならない
  687. $atteribute = ''.$rnsp.'*(?:'.$rnsp.'*(?:'.$att.')(?:'.$rnsp.'*\='.$rnsp.'*(?:(?:[^"\' ]+)|([\'"]?).*?\9))?'.$rnsp.'*)*'.$rnsp.'*';
  688. # コメントタグのパターン
  689. $commentout = '(?:<\!--((?:(?!-->).)*)(?:-->)?)';
  690. $cdata = '(?:<\!\[CDATA\[(.*?)\]\]>)';
  691. $php_script = '(?:<\?(?:php)?((?:(?!\?'.'>).)*)(?:\?'.'>)?)';
  692. $pregstring = '/(.*?)('.$commentout.'|'.$cdata.'|'.$php_script.'|(?:<(\/?)('.$tag.')(?:'.$rnsp.'+('.$atteribute.'))?(\\/?)>))(.*)/s';
  693. return $pregstring;
  694. }//get_pattern_html();
  695. # / HTMLタグを検出するPREGパターンを生成して返す
  696. #--------------------------------------
  697. #--------------------------------------
  698. # タグの属性情報を検出するPREGパターンを生成して返す
  699. # (base_resources_htmlparser からの移植)
  700. function get_pattern_attribute(){
  701. # 属性の種類
  702. $rnsp = '(?:\r\n|\r|\n| |\t)';
  703. $prop = $this->pattern_attribute;
  704. $typeA = '([\'"]?)(.*?)\3'; # ダブルクオートあり
  705. $typeB = '[^"\' ]+'; # ダブルクオートなし
  706. # 属性指定の式
  707. $prop_exists = '/'.$rnsp.'*('.$prop.')(?:\=(?:('.$typeB.')|'.$typeA.'))?'.$rnsp.'*/s';
  708. return $prop_exists;
  709. }//get_pattern_attribute();
  710. # / タグの属性情報を検出するPREGパターンを生成して返す
  711. #--------------------------------------
  712. #--------------------------------------
  713. # 閉じタグを検索する
  714. # (base_resources_htmlparser からの移植)
  715. function search_closetag( $tagname , $strings ){
  716. # タグの深さ
  717. $att = $this->pattern_attribute;
  718. $depth = 0;
  719. $strings_original = $strings;
  720. $rnsp = '(?:\r\n|\r|\n| |\t)';
  721. # 属性のパターン
  722. # 属性の中にタグがあってはならない
  723. $atteribute = ''.$rnsp.'*?(?:'.$rnsp.'*(?:'.$att.')(?:'.$rnsp.'*'.preg_quote('=','/').''.$rnsp.'*?(?:(?:[^"\' ]+?)|([\'"]?).*?\7))?'.$rnsp.'*)*';
  724. $case_sensitive_option = '';
  725. if( !$this->config( 'tags_case_sensitive' ) ){
  726. $case_sensitive_option = 'i';//←大文字小文字の区別をしない : PxXMLDomParser 1.0.1
  727. }
  728. $pregstring = '/^(.*?)(?:('.preg_quote('<'.'?','/').'(?:php)?)|('.preg_quote('<![CDATA[','/').')|('.preg_quote('<!--','/').')|(<(\/?)?(?:'.preg_quote($tagname,'/').')(?:'.$rnsp.'+?'.$atteribute.')?'.'>))(.*)$/'.$case_sensitive_option.'s';
  729. $safetycounter = 0;
  730. $RTN = array(
  731. 'content_str'=>'' ,
  732. 'str_next'=>'' ,
  733. );
  734. while( true ){
  735. $safetycounter ++;
  736. if( is_int( $this->safetycounter_limit ) && $safetycounter > intval( $this->safetycounter_limit ) ){
  737. # 安全装置作動
  738. # $this->safetycounter_limitに設定した数以上先の
  739. # 閉じタグは探せません
  740. $msg = '[SafetyBreak!] on HTML Parser of LINE ['.__LINE__.']. COUNTER = ['.$safetycounter.'] TAGNAME = ['.$tagname.']';
  741. $this->error( $msg , __FILE__ , __LINE__ );
  742. return array( 'content_str'=>$msg , 'str_next'=>'' );
  743. }
  744. $i = 0;
  745. $is_hit = 0;
  746. $stringsMemo = '';
  747. while( 1 ){
  748. # PxFW 0.6.6 : ヒットしなかった場合にリトライするようにした
  749. # PxFW 0.6.7 : リトライのロジックを見直した
  750. # (http://www.pxt.jp/ja/diary/article/218/index.html この問題への対応)
  751. $tmp_start = strpos( $strings , '<' );
  752. if( !is_int( $tmp_start ) || $tmp_start < 0 ){
  753. # [<]記号 が見つからなかったら
  754. # 本当にヒットしなかったものとみなせる
  755. $strings = $stringsMemo.$strings;
  756. break;
  757. }
  758. $stringsMemo .= substr( $strings , 0 , $tmp_start );
  759. $strings = substr( $strings , $tmp_start );
  760. $is_hit = preg_match( $pregstring , $strings , $results );
  761. if( $is_hit ){
  762. # ヒットしたらここでbreak;
  763. $results[1] = $stringsMemo.$results[1];
  764. $strings = $stringsMemo.$strings;
  765. break;
  766. }
  767. //今回先頭にあるはずの[<]記号を $stringsMemo に移してリトライ
  768. $stringsMemo .= substr( $strings , 0 , 1 );
  769. $strings = substr( $strings , 1 );
  770. }
  771. unset( $stringsMemo );
  772. unset( $tmp_start );
  773. if( $is_hit ){
  774. # 何かしらの結果があった場合
  775. $preg_i = 0;
  776. $MEMO = array();
  777. $preg_i ++;
  778. $MEMO['str_prev'] = $results[$preg_i++];
  779. $MEMO['start_php'] = $results[$preg_i++];
  780. $MEMO['start_cdata'] = $results[$preg_i++];
  781. $MEMO['start_htmlcomment'] = $results[$preg_i++];
  782. $MEMO['mytag'] = $results[$preg_i++];
  783. $MEMO['closed_flg'] = $results[$preg_i++];
  784. $MEMO['attribute_str'] = $results[$preg_i++];
  785. $MEMO['str_next'] = $results[$preg_i++];
  786. #--------------------------------------
  787. # 戻り値を作成
  788. if( strlen( $MEMO['start_php'] ) ){
  789. # PHPスクリプト内の閉じタグは検出してはいけない
  790. preg_match( '/^(.*?)(?:'.preg_quote('?'.'>','/').')(.*)$/si' , $MEMO['str_next'] , $php_preg_matched );
  791. $RTN['content_str'] .= $MEMO['str_prev'];
  792. $RTN['content_str'] .= $MEMO['start_php'];
  793. $RTN['content_str'] .= $php_preg_matched[1];
  794. $RTN['content_str'] .= '?'.'>';
  795. $strings = $php_preg_matched[2];
  796. continue;
  797. }elseif( strlen( $MEMO['start_cdata'] ) ){
  798. # CDATAセクション内の閉じタグは検出してはいけない
  799. preg_match( '/^(.*?)(?:'.preg_quote(']]'.'>','/').')(.*)$/si' , $MEMO['str_next'] , $php_preg_matched );
  800. $RTN['content_str'] .= $MEMO['str_prev'];
  801. $RTN['content_str'] .= $MEMO['start_cdata'];
  802. $RTN['content_str'] .= $php_preg_matched[1];
  803. $RTN['content_str'] .= ']]'.'>';
  804. $strings = $php_preg_matched[2];
  805. continue;
  806. }elseif( strlen( $MEMO['start_htmlcomment'] ) ){
  807. # コメントタグ内の閉じタグは検出してはいけない
  808. preg_match( '/^(.*?)(?:'.preg_quote('--'.'>','/').')(.*)$/si' , $MEMO['str_next'] , $php_preg_matched );
  809. $RTN['content_str'] .= $MEMO['str_prev'];
  810. $RTN['content_str'] .= $MEMO['start_htmlcomment'];
  811. $RTN['content_str'] .= $php_preg_matched[1];
  812. $RTN['content_str'] .= '--'.'>';
  813. $strings = $php_preg_matched[2];
  814. continue;
  815. }elseif( strlen( $MEMO['closed_flg'] ) && $depth <= 0 ){
  816. $RTN['content_str'] .= $MEMO['str_prev'];
  817. # 深さ0階層で閉じタグを発見した場合
  818. $RTN['str_next'] .= $MEMO['str_next'];
  819. return $RTN;
  820. break;
  821. }elseif( strlen( $MEMO['closed_flg'] ) && $depth > 0 ){
  822. $RTN['content_str'] .= $MEMO['str_prev'].$MEMO['mytag'];
  823. # 深さ1階層以上で閉じタグを発見した場合
  824. $depth --;
  825. $strings = $MEMO['str_next'];
  826. continue;
  827. }elseif( !strlen( $MEMO['closed_flg'] ) ){
  828. $RTN['content_str'] .= $MEMO['str_prev'].$MEMO['mytag'];
  829. # 入れ子の開始タグを発見してしまった場合
  830. $depth ++;
  831. $strings = $MEMO['str_next'];
  832. continue;
  833. }else{
  834. break;
  835. }
  836. }
  837. break;
  838. }
  839. # 解析が最後まで行ってしまった場合
  840. # つまり閉じタグがなかった場合
  841. # またはファイルが大きすぎてpregの限界を超えた場合
  842. $RTN = array( 'content_str'=>false , 'str_next'=>$strings_original );
  843. $RTN['error'] = 'Parse error: 閉じタグが見つかりません。('.$tagname.')';
  844. $this->error( $RTN['error'] , __FILE__ , __LINE__ );
  845. return $RTN;
  846. }//search_closetag();
  847. # / 閉じタグを検索する
  848. #--------------------------------------
  849. #----------------------------------------------------------------------------
  850. # HTML属性の解析
  851. function html_attribute_parse( $strings ){
  852. preg_match_all( $this->get_pattern_attribute() , $strings , $results );
  853. for( $i = 0; !is_null($results[0][$i]); $i++ ){
  854. if( !strlen($results[3][$i]) ){
  855. $results[4][$i] = null;
  856. }
  857. if( $results[2][$i] ){
  858. $RTN[strtolower( $results[1][$i] )] = $results[2][$i];
  859. }else{
  860. $RTN[strtolower( $results[1][$i] )] = $results[4][$i];
  861. }
  862. }
  863. return $RTN;
  864. }//html_attribute_parse();
  865. # / HTML属性の解析
  866. #----------------------------------------------------------------------------
  867. #----------------------------------------------------------------------------
  868. # 内部エラーハンドラ
  869. # クラス内にエラーを保持する
  870. # (base_resources_htmlparser からの移植)
  871. function error( $errormessage , $FILE = null , $LINE = null ){
  872. $ERROR = array();
  873. $ERROR['msg'] = $errormessage;
  874. $ERROR['file'] = $FILE;
  875. $ERROR['line'] = $LINE;
  876. array_push( $this->errorlist , $ERROR );
  877. return true;
  878. }
  879. # 保持したエラーを取得する
  880. # (base_resources_htmlparser からの移植)
  881. function get_errorlist(){
  882. return $this->errorlist;
  883. }
  884. # エラーが発生したか否か調べる
  885. # (base_resources_htmlparser からの移植)
  886. function is_error(){
  887. if( count( $this->errorlist ) ){
  888. return true;
  889. }
  890. return false;
  891. }
  892. #----------------------------------------------------------------------------
  893. # 受け取ったテキストを指定の文字コードに変換する
  894. # PxFW base_static_text からの移植
  895. function convert_encoding( $TEXT = null , $encode = null , $encodefrom = null ){
  896. if( !is_callable( 'mb_internal_encoding' ) ){ return $TEXT; }
  897. if( !strlen( $encodefrom ) ){ $encodefrom = mb_internal_encoding().',UTF-8,SJIS,EUC-JP,JIS'; }
  898. if( !strlen( $encode ) ){ $encode = mb_internal_encoding(); }
  899. if( is_array( $TEXT ) ){
  900. $RTN = array();
  901. if( !count( $TEXT ) ){ return $TEXT; }
  902. $TEXT_KEYS = array_keys( $TEXT );
  903. foreach( $TEXT_KEYS as $Line ){
  904. $KEY = mb_convert_encoding( $Line , $encode , $encodefrom );
  905. if( is_array( $TEXT[$Line] ) ){
  906. $RTN[$KEY] = $this->convert_encoding( $TEXT[$Line] , $encode , $encodefrom );
  907. }else{
  908. $RTN[$KEY] = @mb_convert_encoding( $TEXT[$Line] , $encode , $encodefrom );
  909. }
  910. }
  911. }else{
  912. if( !strlen( $TEXT ) ){ return $TEXT; }
  913. $RTN = @mb_convert_encoding( $TEXT , $encode , $encodefrom );
  914. }
  915. return $RTN;
  916. }
  917. }
  918. ?>