404 motivation not found | t_ishidaのブログ

CAT | PHP

8月/11

10

create_functionのアレ

PHP5.2以下のPHPを使ってアレしてる人なんか、なんだかんだでcreate_function でアレしたりしてアレするでしょう?

でも、これって調子こいてJavaScriptやLISPのノリや、Perl のサブルーチンのリファレンスのつもりで使ってるとアレしちゃうんですよ。そもそもそこまで便利じゃないので、そのノリで使えないっていうのはアレしちゃってください。で、create_functionをよくアレしてる人は、以下のコード実行してみてください。

for ( $i = 0; $i < 1000000; $i++ ){
  hoge ('$x', 'return $x;' );
}
function hoge ( $x, $y ) { return create_function ( $x, $y ); }

スコープぬけようがcreate_functionした関数は破棄しないし、同じコードだろうが問答無用で新しい関数定義するという男らしい仕様でアレしているので使用可能メモリを一瞬でオーバーして男らしく散り際をわきまえてアレしちゃうんですね。でも、そんなアホみたいな回数をcreate_functionするような場所で回さないもんへへーんとかアレしてる方、

// バッチコントローラの階層
for ( $i = 0; $i < 10; $i++ ) {
  // モデルの階層
  for ( $j = 0; $j < 100; $j++ ) {
    // そのモデルの下請けモデルの階層
    for ( $k = 0; $k < 1000; $k++ ){
      hoge ('$x', 'return $x;' );
    }
  }
}
function hoge ( $x, $y ) { return create_function ( $x, $y ); }

これ位の計算量は普通に有り得るでしょう。うげーやべぇって思ってアレしました? 思ってないなら思った方が良いです。どう考えても思えないなら、ここでは先に進まなくてアレしちゃうので思ってください。

で、こうしてみることにしました。

function create_function_ex ( $args, $code )  {
  return LambdaFactory::create ( $args, $code );
}
class LambdaFactory {
   private static $_Cache = array ();
   public function create ( $args, $code )  {
     if ( !self::$_Cache[$args . "\x0b" . $code]  ) self::$_Cache[$args."\x0b". $code] = create_function ( $args, $code );
     return self::$_Cache[$args . "\x0b" . $code];
   }
}

です。
じゃあ、これと同じファイルに

for ( $i = 0; $i < 10000000; $i++ ){
   $fnc = create_function_ex ( '$x', 'print "$x\n";' );
   $fnc ( $i );
}

を追記して実行してみてください。
PHPさん、ここでは男らしく踏ん張ってくださいましたね。
これで安心してcreate_function 多用した可読性の低いコードを
ハッピーに書けますね!

※勿論、5.3以降のPHPなら create_function なんか使わずに。 function ( $x ) { return $x; } というようにしましょう。

Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

No tags

8月/10

11

PHPの比較のアレ

この結果ってどうなると思います?

<?php
print '*' ==  0 ? 'true' : 'false' . "\n";

実はtrueですね、
数値 0 と文字列を比較すると、trueが返ってくるっぽいです。

こうするか

<?php
print '*' ===  0 ? 'true' : 'false' . "\n";

こうしましょう

<?php
print '*' ===  0 . '' ? 'true' : 'false' . "\n";
Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

No tags

7月/10

28

備忘録:PHP配列同士の比較

件名の通り。
まさかの

print  (
  array( 1, 2, 3 ) == array( 1, 2, 3 )
  ? 'OK' : 'NG'
    ) . "\n"; // OK
print  (
  array( 1, 2, 3 ) == array( 1, 2, 'ほげ'  )
  ? 'OK' : 'NG'
  ) . "\n"; // NG

print  (
  array( 1, 2, 3,
    array( 1, 2, 3 ,
      array( 1,
        array( 2,
          array( 3, 4
  ))))) ==
  array( 1, 2, 3,
    array( 1, 2, 3 ,
      array( 1,
        array( 2,
          array( 3, 4
  )))))
  ? 'OK' : 'NG'
  ) . "\n";            // OK

print  (
  array( 1, 2, 3,
    array( 1, 2, 3 ,
      array( 1,
        array( 2,
          array( 3, 4
   ))))) ==
   array( 1, 2, 3,
     array( 1, 2, 3 ,
       array( 1,
         array( 2,
           array( 3, 4,
             array( 5
   ))))))
   ? 'OK' : 'NG'
  ). "\n";            // NG

知らなかった僕もアホなのだけど、
これはさすがにアホかと。

print ( var_export( array( 1, 2 ), true ) == var_export( array( 1, 2 ), true )  ?  'OK' : 'NG' ) . "\n";

とかやってたよヽ(゜▽、゜)ノ

Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

No tags

3月/10

2

意味無し

テストコード書いてたら括弧を削りたくなって、
そこで遊びだしてしまった。

function getQuotedString( $str ){
  list( $quote, $buf, $result ) = array( null, null, array() );
  for( $i = 0; $i < mb_strlen( $str ); $i++ )
    if( $quote )
      if    ( $str[$i] === $quote ) list( $buf, $quote ) = array( '', '' ) && $buf !== null && $result[] = $buf;
      elseif( $str[$i] === '\\' )   $buf .= $str[$i] . $str[++$i];
      else                          $buf .= $str[$i];
    elseif( $str[$i] === "'" || $str[$i] === '"' ) $quote = $str[$i];
  return $result;
}
Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

No tags

2月/10

23

PHPで文字に一文字ずつアクセス

<?php
function getMicroTime(){ list( $s, $m ) = split( ' ',  microtime() ); return (float)$s + (float)$m; }
function say( $s ){ echo "$s\n"; }
$text = '';
for( $i = 0; $i < 250000; $i++ ) $text .= 'あ';  

$brefore = getMicroTime();
foreach( preg_split( '##', $text )  as $char ){}
say( 'preg_split:' .( getMicroTime() - $brefore ) );

$brefore = getMicroTime();
for( $i = 0; $i < strlen( $text ); $i++ ){ $text[$i]; }
say( 'array_access:' .( getMicroTime() - $brefore ) );

と言うわけで、$text[$i]でアクセスする方がかなり速い。

Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

No tags

件名の通り。
いい加減、ちゃんとテストして一本アプリ組みながら調整を加えていこうとというタイミングで、作りかけのアレですが固定ページ作りました。

ver0.2にはドキュメントがあります。嘘です。ただ、コメントを抽出する機能があるのでドキュメントっぽく読めるかも知れません。また、MVCフレームワークっぽいものとしても使えるような気がしますが、include_pathを汚さないライブラリ群としても使えるかも知れません。先日のHTMLっぽいテキストのタグパーサっぽいものは、これの一部です。

興味有る方はプログラムを読んだり書いたりしてください。リポジトリとかは特に設けないので、固定ページのライセンス事項に従って扱ってください。

Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

No tags

11月/09

16

先日のチロッと直した

先日のを昼休みにチロッと直した

<?php
/*************************************************
 * HTMLっぽい文字列をパースっぽいことをする
 * @todo クエリの結果に対してattrやタグ名の操作を、
 *       やっても現在別オブジェクトなのでDOM操作的な
 *       ことが出来るようにした方が良いのかね?
 * @todo タグ比べるところは性能の問題あるし正規表現じゃなくてsubstrにしよう
 *************************************************/
class fwwTag {
  private $Elements = array();

  /// removeTagのデフォルト値
  private $ExcludeList = array(
    'onload',      'onunload',  'onabort',     'onerror',    'onmove',      'onresize',  'ondragdrop', 'onfocus',
    'onblur',      'onsubmit',  'onreset',     'onclick',    'ondblclick',  'onkeydown', 'onkeypress', 'onkeyup',
    'onmousedown', 'onmouseup', 'onmouseover', 'onmouseout', 'onmousemove', 'onchange',  'onselect',
    array( 'target'=> 'href', 'pattern' => '#^javascript:#' ) ,
    );

  /// removeTagのデフォルト値
  private $ExcludeTag = array(
    'script',
    );

  /// PCDATA扱いにするもの
  private $isPCDATA  = array( 'style', 'script' );

  /*******************************************
   * コンストラクタ
   ******************************************/
  public function __construct( $html = null ){
    if ( is_array( $html ) )                      $this->Elements = $html;
    elseif( preg_match( '#^https?://#', $html ) ) $this->Elements = $this->parse( file_get_contents( $html) );
    elseif( is_scalar( $html ) )                  $this->Elements = $this->parse( $html );
  }

  /******************************************************
   * アトリビュートを取得する
   * @param アトリビュート名
   * @return アトリビュート値
   ******************************************************/
  public function getAttribute( $name ){
    return $this->Elements['attrs'][$name];
  }

  /******************************************************
   * アトリビュートを設定する
   * @param アトリビュート名
   * @param 値
   ******************************************************/
  public function setAttribute( $name, $value ){
    $this->Elements['attrs'][$name] = $value;
  }

  /******************************************************
   * タグ名を取得する
   * @param 文字列(Path構文)
   ******************************************************/
  public function getTagName(){
    return $this->Elements['tagName'];
  }

  /******************************************************
   * タグ名を変更する
   * @param 文字列(Path構文)
   ******************************************************/
  public function setTagName( $tagName ){
    $this->Elements['tagName'] = $tagName;
  }

  /******************************************************
   * 配下ノードに添え字でアクセス
   * @param 添え字
   * @return 対象ノードのオブジェクト
   ******************************************************/
  public function getChildNode( $idx ){
    $class = __CLASS__;
    return new $class( $this->Elements['childNodes'][$idx] );
  }

  /******************************************************
   * テキストノードだけを返す
   ******************************************************/
  public function getTextNode(){
    $result = '';
    foreach( $this->Elements['childNodes'] as $child ) {
      if( $child['tagName'] == 'textNode' ) $result .= $child['attrs']['value'];
    }
    return $result;
  }

  /******************************************************
   * Path構文でHTMLをツリーから対象のタグを取得する
   * @param "/" 区切りでid => "#",class => ".", タグ名で取得する
   * @return 対象タグの配列とか
   ******************************************************/
  public function query( $q ){
    $path = preg_split( '#/#', $q );
    $current = array( $this );
    foreach( $path as $x ){
      if( !$x ) continue;
      $next = array();
      foreach( $current as $elm ){
        if ( preg_match( '/^#(\S+)$/', $x, $tmp ) ){
          $buf = $elm->getElementById( $tmp[1] );
          if( $buf ) $next[] = $buf;
        } elseif( preg_match( '#^\.(\S+)$#', $x, $tmp ) ){
          $buf = $elm->getElementsByClassName( $tmp[1] );
          if( $buf ) $next = array_merge( $next, $buf );
        } elseif( preg_match( '#^(\S+?)(?:([\#\.])(\S+))?$#', $x, $tmp ) ) {
          $buf = $elm->getElementsByTagName( $tmp[1] );
          if( $buf ){
            if( $tmp[2] ){
              $buf2 = array();
              foreach( $buf as $elm ){
                if( $elm->getAttribute( $tmp[2] == '.' ? 'class' : 'id' ) == $tmp[3] ){
                  $buf2[] = $elm;
                }
              }
              $buf = $buf2;
            }
            $next = array_merge( $next, $buf );
          }
        }
      }
      $current = $next;
    }
    if( !$current )                     return array();
    if( preg_match('|^#[^/]+$|', $q ) ) return $current[0];
    return $current;
  }

  /*************************************************
   * IDアトリビュートからタグオブジェクトを取得する
   * @param id
   * @return 対象オブジェクト
   *************************************************/
  public function getElementById( $id, $tag = null ){
    if( !$tag ) $tag = $this->Elements;
    $class = __CLASS__;
    if( $tag['attrs']['id'] == $id ) return new $class( $tag );
    foreach( $tag['childNodes'] as $child ){
      $result = $this->getElementById( $id, $child );
      if( $result ) return $result;
    }
    return null;
  }

  /*************************************************
   * タグ名からタグオブジェクトの配列を取得する
   * @param タグ名
   * @return 対象オブジェクトの配列
   *************************************************/
  public function getElementsByTagName( $tag_name,  $tag = null ){
    if( !$tag ) $tag = $this->Elements;
    $class = __CLASS__;
    $result = array();
    if( $tag['tagName'] == $tag_name ) $result[] = new $class( $tag );
    foreach( $tag['childNodes'] as $child ){
      $result2 = $this->getElementsByTagName( $tag_name, $child );
      if( $result2 ) $result = array_merge( $result, $result2 );
    }
    return $result;
  }

  /*************************************************
   * クラス名からタグオブジェクトの配列を取得する
   * @param クラス名
   * @return 対象オブジェクトの配列
   *************************************************/
  public function getElementsByClassName( $class, $tag = null ){
    if( !$tag ) $tag = $this->Elements;
    $c = __CLASS__;
    $result = array();
    if( $tag['attrs']['class'] == $class ) $result[] = new $c( $tag );
    foreach( $tag['childNodes'] as $child ){
      $result2 = $this->getElementsByClassName( $class, $child );
      if( $result2 ) $result = array_merge( $result, $result2 );
    }
    return $result;
  }

  /***********************************************************
   * HTMLツリーを再帰的に遡ってアトリビュートを削除する
   * @param array() 削除したりアトリビュートの名前
   * @return 対象タグのオブジェクト
   **********************************************************/
  public function removeAttribute( $targets = array() ){
    if( !$targets ) $targets = $this->ExcludeList;
    $class = __CLASS__;
    return new $class( $this->__removeAttribute( $this->Elements, $targets ) );
  }

  /***********************************************************
   * HTMLツリーを再帰的に遡ってアトリビュートを削除する(再起のための受け皿)
   * @param @tag
   * @param array() 削除したりアトリビュートの名前
   * @return 対象タグの配列とか
   **********************************************************/
  private function __removeAttribute( $tag, $targets ){
    foreach( $targets as $t ){
      if( is_array( $t ) ){
        if( preg_match( $t['pattern'], $tag['attrs'][$t['target']] ) ) unset( $tag['attrs'][$t['target']] );
      } else {
        unset( $tag['attrs'][$t] );
      }
    }
    if( $tag['childNodes'] ){
      foreach( $tag['childNodes'] as $key => $value ) {
        $tag['childNodes'][$key] = $this->__removeAttribute( $tag['childNodes'][$key], $targets );
      }
    }
    return $tag;
  }

  /***********************************************************
   * HTMLツリーを再帰的に遡ってタグを削除する(再起のための受け皿)
   * @param @tag
   * @param array() 削除したりタグの名前
   * @return 対象タグの配列とか
   **********************************************************/
  public function removeTag( $targets = array() ){
    if( !$targets ) $targets = $this->ExcludeTag;
    $class = __CLASS__;
    if( in_array( $this->Elements['tagName'], $targets ) ) return new $class( array() );
    return new $class( $this->__removeTag( $this->Elements, $targets ) );
  }

  /***********************************************************
   * HTMLツリーを再帰的に遡ってタグを削除する(再起のための受け皿)
   * @param @tag
   * @param array() 削除したいタグの名前
   * @return 対象タグの配列とか
   **********************************************************/
  private function __removeTag( $tag, $targets ){
    foreach( $tag['childNodes'] as $key => $value ) {
      if( in_array( $tag['childNodes'][$key]['tagName'], $targets ) ) unset( $tag['childNodes'][$key] );
      else                                                            $tag['childNodes'][$key] = $this->__removeTag( $tag['childNodes'][$key], $targets );
  
    }
    return $tag;
  }

  /*****************************************
   * HTMLツリーを再帰的にHTML化する
   * @return 出力結果のHTML
   *****************************************/
  public function build( $flg = false ){
    $html = '';
    // rootは出さない
    if( $this->Elements['tagName'] == 'root' ){
      foreach( $this->Elements['childNodes'] as $child ) $html .= $this->__build( $child );
    } else{
      $html = $this->__build( $this->Elements, $flg );
    }
    return $html;
  }

  /*****************************************
   * HTMLツリーを再帰的にHTML化する(再起のための受け皿)
   * @param 対象のタグ
   * @return 出力結果のHTML
   *****************************************/
  private function __build( $tag, $no_esc = false ){
    if( !$tag ) return '';
    if( $tag['tagName'] == 'comment') {
      $html = '<!--';
      $html .= $tag['attrs']['value'];
      $html .= '-->';
    } elseif( $tag['tagName'] == 'cdata') {
      $html = '<![CDATA[';
      $html .= $tag['attrs']['value'];
      $html .= ']]>';
    } elseif( $tag['tagName'] == 'textNode' ){
      $html = $no_esc ? $tag['attrs']['value'] : htmlspecialchars( $tag['attrs']['value'], ENT_QUOTES );
    } else {
      $html = '<' . $tag['tagName'];
      if( $tag['attrs'] ) $html .= $this->buildAttributes( $tag['attrs'] );
      if( $tag['childNodes'] ){
        $html .=  '>';
        $no_esc = in_array( $tag['tagName'], $this->isPCDATA );
        foreach( $tag['childNodes'] as $child ) $html .= $this->__build( $child, $no_esc );
        $html .= '</' . $tag['tagName'] . '>';
      } else {
        $html .= ' />';
      }
    }
    return $html;
  }

  /******************************************************
   * HTMLをツリーハッシュにビルド
   * @param 文字列
   * @return ハッシュ
   ******************************************************/
  public function parse( $html ){
    $in_tag     = false;
    $in_comment = false;
    $in_cdata   = false;
    $in_quote   = false;
    $in_pcdata  = false;
    $is_esc     = false;

    $buf = '';
    $length  = count( $chars );
    $parent  = array();
    $current =  array( 'tagName' => 'root', 'childNodes' => array(), 'attrs' => array() );
    $stack   = array();
    foreach( preg_split( '//', mb_convert_encoding( $html, 'utf8', 'euc-jp,sjis,utf8,auto' ) ) as $char ){
      // タグ開始のお知らせ
      if( !$in_tag ){
        if( $char === '<' ){
          $in_tag = true;
          if( $buf ){
            $current['childNodes'][] = array(
              'tagName'    => 'textNode',
              'attrs'      => array( 'value' => html_entity_decode( $buf ) ),
              'childNodes' => array()
              );
          }
          $buf = '';
          continue;
        }
      }
      /// タグの中かどうか
      else {
        // "<"の後だけど、これはタグじゃねーだろの場合
        if( !$buf && ( !$in_comment && !$in_pcdata  && !$in_cdata ) && !preg_match( '#^[\!/a-zA-Z0-9]#', $char ) ){
          $in_tag = false;
          $buf = '<' . $char;
          continue;
        }

        // クォートの中
        elseif( $in_quote ){
          if( $is_esc )                $is_esc = false;
          elseif( $in_quote == $char ) $in_quote = false;
          elseif( $char     == '\\' )  $is_esc = true;
          $buf .= $char;
          continue;
        }
        // quoteの開始
        elseif( $char === "'" || $char === '"' ) {
          $in_quote = $char;
          $buf .= $char;
          continue;
        }

        // コメント開始
        elseif( $buf . $char == '!--' ){
          $in_comment = true;
          $buf .= $char;
          continue;
        }

        // CDATA 開始
        elseif( strtoupper( $buf . $char ) == '![CDATA[' ){
          $in_cdata = true;
          $buf .= $char;
          continue;
        }

        /// タグ閉じ
        elseif( $char === '>' && (
          ( $in_comment  && mb_substr( $buf, -2 ) == '--' )  ||
          ( $in_cdata    && mb_substr( $buf, -2 ) == ']]' )  ||
          ( $in_pcdata   && "/$in_pcdata"         == trim( strtolower( $buf ) ) ) ||
          ( !$in_comment && !$in_pcdata  && !$in_cdata  )
          ) ) {
          $in_tag = false;

          // コメントの閉じ
          if( $in_comment && mb_substr( $buf, -2 ) == '--' ){
            $current['childNodes'][] = array(
              'tagName'    => 'comment',
              'attrs'      => array( 'value' =>  mb_substr( $buf, 3, -2 ) ),
              'childNodes' => array(),
              );
            $in_comment = false;
            $buf = '';
            continue;
          }

          // CDATAの閉じ
          elseif( $in_cdata && mb_substr( $buf, -2 ) == ']]' ) {
            $current['childNodes'][] = array(
              'tagName'    => 'cdata',
              'attrs'      => array( 'value' => mb_substr( $buf, 8, -2 ) ),
              'childNodes' => array(),
              );
            $in_cdata = false;
            $buf = '';
            continue;
          }

          // 想定通りの閉じタグ
          elseif(  '/' . $current['tagName'] == strtolower( trim( $buf ) ) ) {
            if( $in_pcdata == $current['tagName'] ) $in_pcdata = false;
            $parent['childNodes'][] = $current;
            $current = $parent;
            if( $stack  ) $parent = array_pop( $stack );
            $buf = '';
            continue;
          }

          // 想定外の閉じタグ
          elseif( preg_match( '#^/#', $buf ) ){
            print "不整合: debug: $buf ". $current['tagName'] . "\n\n";
            die;
          }

          // 単体のタグ
          elseif( preg_match( '#^(\S+)(.+)/$#s', $buf, $tmp ) ||
                  preg_match( '#^(input|br|img|hr|meta|link|!DOCTYPE|option|base|area)(.*)$#si',$buf, $tmp ) ) {
            $current['childNodes'][] = array(
              'tagName'    => strtolower( $tmp[1] ),
              'attrs'      => $this->parseAttributes( $tmp[2] ),
              'childNodes' => array()
              );
            $buf = '';
            continue;
          }

          // 通常の開始タグ
          elseif( preg_match( '#^([a-zA-Z0-9]+)(.*)$#s', $buf, $tmp ) ){
            if( in_array( strtolower( $tmp[1] ), $this->isPCDATA ) ) $in_pcdata = strtolower( $tmp[1] );
            if( $parent ) $stack[] = $parent;
            $parent = $current;
            $current = array(
              'tagName' => strtolower( $tmp[1] ),
              'attrs'   => $this->parseAttributes( $tmp[2] ),
              'childNodes'  => array(),
              );
            $buf = '';
            continue;
          }
        } // タグ終了のお知らせ
      }   // タグの中かどうか
      $buf .= $char;
    } // end foreach;
    return $current;
  }

  /******************************************************
   * タグ名を除いたタグの中身をAttributeのハッシュに変換
   * @param 文字列
   * @return ハッシュ
   ******************************************************/
  public function parseAttributes( $chars ){
    $in_quote = 0;
    $esc      = 0;
    $attr     = '';
    $elms     = array();
    foreach( preg_split( '//', trim( $chars ) ) as $char ){
      if( $esc === 1 ){
        $esc = 0;
        $attr .= $char;
      } elseif( !$in_quote && !trim( $char ) ){
        list( $label, $value ) = $this->parseAttribute( trim($attr ) );
        $attr && $elms[$label] = $value;
        $attr = '';
      } else {
        if    (  $in_quote === $char )                             $in_quote = null;
        elseif( !$in_quote && ( $char === "'" || $char === '"' ) ) $in_quote = $char;
        $char === '\\' && $esc = 1;
        $attr .= $char;
      }
    }
    return $elms;
  }

  /********************************************
   * attributeっぽい文字列をlabelとvalueに分割
   * @param 文字列
   * @return array( $label, $value );
   *********************************************/
  public function parseAttribute( $chars ){
    $label = '';
    $value = '';
    $in_quote = 0;
    $esc      = 0;
    $buf = '';
    foreach( preg_split( '//', trim( $chars ) ) as $char ){
      if( !$in_quote  && $char === ' ' ) continue;
      if( $esc === 1 ){
        $esc = 0;
        $buf = $buf . $char;
      } else{
        if( $in_quote === $char ){
          $in_quote = null;
        } else{
          if( $in_quote ){
            if( $char === '\\' )  $esc  = 1;
            else                  $buf = $buf . $char;
          } else {
            if( $char === "'" || $char === '"' ) {
              $in_quote = $char;
            } elseif( $char === '=' ) {
              $label  =  $buf;
              $buf    = '';
            } else {
              $buf = $buf . $char;
            }
          }
        }
      }
    }
    $value =  $buf;
    return array( $label, $value );
  }

  /**********************************
   * ハッシュをアトリビュート化する
   * @param ハッシュ
   * @return アトリビュートっぽい文字列
   **********************************/
  private function buildAttributes( $attr ){
    $str = '';
    foreach( $attr as $key => $value ){
      if( $value == '' ) continue;
      $str .= ' ' .  $key .'="';
      if( is_array( $value ) ) $str .= htmlspecialchars( join( ' ',  $value, ENT_QUOTES ) );
      else                     $str .= htmlspecialchars( $value, ENT_QUOTES );
      $str .='"';
    }
    return $str;
  }
}

/// ためしに弾さんのブログをスクレイピングしてみる場合は以下みたいな感じ
//ob_start();
//$tag = new fwwTag('http://blog.livedoor.jp/dankogai/archives/51321141.html');
//print "title:" .  array_shift( $tag->query('title') )->getChildNode(0)->getAttribute('value') . "\n\n";
//print "date:"  .  trim( array_shift( $tag->query('h2.date') )->removeTag( array('a', 'span') )->getTextNode() ) . "\n\n";
//print "body:\n";
//print array_shift( $tag->query('div.blogbody') )->removeTag()->removeAttribute()->build();
//print mb_convert_encoding( ob_get_clean(),'sjis','utf8' );
Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

No tags

11月/09

12

こんなん書いてた

ヘッダコメントの通りです。

課題:
アトリビュート内にonclick=”this.innerHTML=”<h1>hogehoge</h1^>’”とか、やられるとパースしちゃう。タグ構造壊れている奴を上手くパースできないかも。

<?php
/*************************************************
 * HTMLっぽい文字列をパースっぽいことをする
 * @todo クエリの結果に対してattrやタグ名の操作を、
 *       やっても現在別オブジェクトなのでDOM操作的な
 *       ことが出来るようにした方が良いのかね?
 *************************************************/
class Tag {
  private $Elements = array();
  private $ExcludeList = array(
    'onload',
    'onunload',
    'onabort',
    'onerror',
    'onmove',
    'onresize',
    'ondragdrop',
    'onfocus',
    'onblur',
    'onsubmit',
    'onreset',
    'onclick',
    'ondblclick',
    'onkeydown',
    'onkeypress',
    'onkeyup',
    'onmousedown',
    'onmouseup',
    'onmouseover',
    'onmouseout',
    'onmousemove',
    'onchange',
    'onselect',
    array( 'target'=> 'href', 'pattern' => '#^javascript:#' ) ,
    );

  private $ExcludeTag = array(
    'script',
    );

  /*******************************************
   * コンストラクタ
   ******************************************/
  public function __construct( $html = null ){
    if ( is_array( $html ) )                      $this->Elements = $html;
    elseif( preg_match( '#^https?://#', $html ) ) $this->Elements = $this->parse( file_get_contents( $html) );
    elseif( is_scalar( $html ) )                  $this->Elements = $this->parse( $html );
  }

  /******************************************************
   * アトリビュートを取得する
   * @param アトリビュート名
   * @return アトリビュート値
   ******************************************************/
  public function getAttribute( $name ){
    return $this->Elements['attrs'][$name];
  }

  /******************************************************
   * アトリビュートを設定する
   * @param アトリビュート名
   * @param 値
   ******************************************************/
  public function setAttribute( $name, $value ){
    $this->Elements['attrs'][$name] = $value;
  }

  /******************************************************
   * タグ名を取得する
   * @param 文字列(Path構文)
   ******************************************************/
  public function getTagName(){
    return $this->Elements['tagName'];
  }

  /******************************************************
   * タグ名を変更する
   * @param 文字列(Path構文)
   ******************************************************/
  public function setTagName( $tagName ){
    $this->Elements['tagName'] = $tagName;
  }

  public function getChildNode( $idx ){
    $class = __CLASS__;
    return new $class( $this->Elements['childNodes'][$idx] );
  }

  public function getTextNode(){
    $result = '';
    foreach( $this->Elements['childNodes'] as $child ) {
      if( $child['tagName'] == 'textNode' ) $result .= $child['attrs']['value'];
    }
    return $result;
  }

  /******************************************************
   * Path構文でHTMLをツリーから対象のタグを取得する
   * @param "/" 区切りでid => "#",class => ".", タグ名で取得する
   * @return 対象タグの配列とか
   ******************************************************/
  public function query( $q ){
    $path = preg_split( '#/#', $q );
    $current = array( $this );
    foreach( $path as $x ){
      if( !$x ) continue;
      $next = array();
      foreach( $current as $elm ){
        if ( preg_match( '/^#(\S+)$/', $x, $tmp ) ){
          $buf = $elm->getElementById( $tmp[1] );
          if( $buf ) $next[] = $buf;
        } elseif( preg_match( '#^\.(\S+)$#', $x, $tmp ) ){
          $buf = $elm->getElementsByClassName( $tmp[1] );
          if( $buf ) $next = array_merge( $next, $buf );
        } elseif( preg_match( '#^(\S+?)(?:([\#\.])(\S+))?$#', $x, $tmp ) ) {
          $buf = $elm->getElementsByTagName( $tmp[1] );
          if( $buf ){
            if( $tmp[2] ){
              $buf2 = array();
              foreach( $buf as $elm ){
                if( $elm->getAttribute( $tmp[2] == '.' ? 'class' : 'id' ) == $tmp[3] ){
                  $buf2[] = $elm;
                }
              }
              $buf = $buf2;
            }
            $next = array_merge( $next, $buf );
          }
        }
      }
      $current = $next;
    }
    if( !$current )                     return array();
    if( preg_match('|^#[^/]+$|', $q ) ) return $current[0];
    return $current;
  }

  /*************************************************
   * IDアトリビュートからタグオブジェクトを取得する
   *************************************************/
  public function getElementById( $id, $tag = null ){
    if( !$tag ) $tag = $this->Elements;
    $class = __CLASS__;
    if( $tag['attrs']['id'] == $id ) return new $class( $tag );
    foreach( $tag['childNodes'] as $child ){
      $result = $this->getElementById( $id, $child );
      if( $result ) return $result;
    }
    return null;
  }

  /*************************************************
   * タグ名からタグオブジェクトの配列を取得する
   *************************************************/
  public function getElementsByTagName( $tag_name,  $tag = null ){
    if( !$tag ) $tag = $this->Elements;
    $class = __CLASS__;
    $result = array();
    if( $tag['tagName'] == $tag_name ) $result[] = new $class( $tag );
    foreach( $tag['childNodes'] as $child ){
      $result2 = $this->getElementsByTagName( $tag_name, $child );
      if( $result2 ) $result = array_merge( $result, $result2 );
    }
    return $result;
  }

  /*************************************************
   * クラス名からタグオブジェクトの配列を取得する
   *************************************************/
  public function getElementsByClassName( $class, $tag = null ){
    if( !$tag ) $tag = $this->Elements;
    $c = __CLASS__;
    $result = array();
    if( $tag['attrs']['class'] == $class ) $result[] = new $c( $tag );
    foreach( $tag['childNodes'] as $child ){
      $result2 = $this->getElementsByClassName( $class, $child );
      if( $result2 ) $result = array_merge( $result, $result2 );
    }
    return $result;
  }

  /***********************************************************
   * HTMLツリーを再帰的に遡ってアトリビュートを削除する
   * @param array() 削除したりアトリビュートの名前
   * @return 対象タグのオブジェクト
   **********************************************************/
  public function removeAttribute( $targets = array() ){
    if( !$targets ) $targets = $this->ExcludeList;
    $class = __CLASS__;
    return new $class( $this->__removeAttribute( $this->Elements, $targets ) );
  }

  /***********************************************************
   * HTMLツリーを再帰的に遡ってアトリビュートを削除する(再起のための受け皿)
   * @param @tag
   * @param array() 削除したりアトリビュートの名前
   * @return 対象タグの配列とか
   **********************************************************/
  private function __removeAttribute( $tag, $targets ){
    foreach( $targets as $t ){
      if( is_array( $t ) ){
        if( preg_match( $t['pattern'], $tag['attrs'][$t['target']] ) ) unset( $tag['attrs'][$t['target']] );
      } else {
        unset( $tag['attrs'][$t] );
      }
    }
    if( $tag['childNodes'] ){
      foreach( $tag['childNodes'] as $key => $value ) {
        $tag['childNodes'][$key] = $this->__removeAttribute( $tag['childNodes'][$key], $targets );
      }
    }
    return $tag;
  }

  /***********************************************************
   * HTMLツリーを再帰的に遡ってタグを削除する(再起のための受け皿)
   * @param @tag
   * @param array() 削除したりタグの名前
   * @return 対象タグの配列とか
   **********************************************************/
  public function removeTag( $targets = array() ){
    if( !$targets ) $targets = $this->ExcludeTag;
    $class = __CLASS__;
    if( in_array( $this->Elements['tagName'], $targets ) ) return new $class( array() );
    return new $class( $this->__removeTag( $this->Elements, $targets ) );
  }

  /***********************************************************
   * HTMLツリーを再帰的に遡ってタグを削除する(再起のための受け皿)
   * @param @tag
   * @param array() 削除したいタグの名前
   * @return 対象タグの配列とか
   **********************************************************/
  private function __removeTag( $tag, $targets ){
    foreach( $tag['childNodes'] as $key => $value ) {
      if( in_array( $tag['childNodes'][$key]['tagName'], $targets ) ) unset( $tag['childNodes'][$key] );
    }
    return $tag;
  }

  /*****************************************
   * HTMLツリーを再帰的にHTML化する
   * @return 出力結果のHTML
   *****************************************/
  public function build( $flg = false ){
    $html = '';
    // rootは出さない
    if( $this->Elements['tagName'] == 'root' ){
      foreach( $this->Elements['childNodes'] as $child ) $html .= $this->__build( $child );
    } else{
      $html = $this->__build( $this->Elements, $flg );
    }
    return $html;
  }

  /*****************************************
   * HTMLツリーを再帰的にHTML化する(再起のための受け皿)
   * @param 対象のタグ
   * @return 出力結果のHTML
   *****************************************/
  private function __build( $tag ){
    if( !$tag ) return '';
    if( $tag['tagName'] == 'comment') {
      $html = '<!--';
      $html .= $tag['attrs']['value'];
      $html .= '-->';
    } elseif( $tag['tagName'] == 'cdata') {
      $html = '<![CDATA[';
      $html .= $tag['attrs']['value'];
      $html .= ']]>';
    } elseif( $tag['tagName'] == 'textNode' ){
      $html = htmlspecialchars( $tag['attrs']['value'], ENT_QUOTES );
    } else {
      $html = '<' . $tag['tagName'];
      if( $tag['attrs'] ) $html .= $this->buildAttributes( $tag['attrs'] );
      if( $tag['childNodes'] ){
        $html .=  '>';
        foreach( $tag['childNodes'] as $child ) $html .= $this->__build( $child );
        $html .= '</' . $tag['tagName'] . '>';
      } else {
        $html .= ' />';
      }
    }
    return $html;
  }

  /******************************************************
   * HTMLをツリーハッシュにビルド
   * @param 文字列
   * @return ハッシュ
   ******************************************************/
  public static function parse( $html ){
    $in_tag = false;
    $buf = '';
    $length  = count( $chars );
    $parent  = array();
    $current =  array( 'tagName' => 'root', 'childNodes' => array(), 'attrs' => array() );
    $stack   = array();
    foreach( preg_split( '//', mb_convert_encoding( $html, 'utf8', 'euc-jp,sjis' ) ) as $char ){
      // タグ開始のお知らせ
      if( $char === '<' ){
        if( !$in_tag ){
          $in_tag = true;
          if( $buf ){
            $current['childNodes'][] = array(
              'tagName'    => 'textNode',
              'attrs'      => array( 'value' => html_entity_decode( $buf ) ),
              'childNodes' => array()
              );
          }
          $buf = '';
        }
        continue;
      }

      // タグ終了のお知らせ
      if( ( preg_match( '#^!\[CDATA\[(.*)\]\]$#s', $buf ) && $char === '>' ) ||
          ( preg_match( '#^!--(.*)--$#s'         , $buf ) && $char === '>' ) ||
          ( !preg_match( '#^(?:!\[CDATA\[|!--)#' ,$buf )  && $char === '>' )  ){

        if( $in_tag ) {
          $in_tag = false;

          // コメント
          if( preg_match( '#^!--(.*)--$#s', $buf, $tmp ) ){
            $current['childNodes'][] = array(
              'tagName'    => 'comment',
              'attrs'      => array( 'value' => $tmp[1] ),
              'childNodes' => array(),
              );
          }

          // CDATA
          elseif( preg_match( '#^!\[CDATA\[(.*)\]\]$#s',$buf, $tmp ) ) {
            $current['childNodes'][] = array(
              'tagName'    => 'cdata',
              'attrs'      => array( 'value' => $tmp[1] ),
              'childNodes' => array(),
              );

          }

          // 想定通りの閉じタグ
          elseif( preg_match( '#^/' . $current['tagName'] . '#i', $buf ) ) {
            $parent['childNodes'][] = $current;
            $current = $parent;
            if( $stack  ) $parent = array_pop( $stack );
          }

          // 想定外の閉じタグ
          elseif( preg_match( '#^/#', $buf ) ){
            ///
            /// @todo: 満足したらスタックを遡って補完するようにする
            /// まだまだ想定外がありそうなので、とりあえずはデバッグモードにしておく
            ///
            print mb_convert_encoding( $html, 'utf8', 'euc-jp,sjis' );
            print "debug:$buf\n\n";
            var_dump( $parent );
            var_dump( $current );
            var_dump( $stack );
            return ;
          }

          // 単体のタグ
          elseif( preg_match( '#^(\S+)(.+)/$#s', $buf, $tmp ) ||
                  preg_match( '#^(input|br|img|hr|meta|link|!DOCTYPE|option|base|area)(.*)$#si',$buf, $tmp ) ) {
            $current['childNodes'][] = array(
              'tagName'    => strtolower( $tmp[1] ),
              'attrs'      => self::parseAttributes( $tmp[2] ),
              'childNodes' => array()
              );

          }

          // 通常の開始タグ
          elseif( preg_match( '#^([a-zA-Z0-9]+)(.*)$#s', $buf, $tmp ) ){
            if( $parent ) $stack[] = $parent;
            $parent = $current;
            $current = array(
              'tagName' => strtolower( $tmp[1] ),
              'attrs'   => self::parseAttributes( $tmp[2] ),
              'childNodes'  => array(),
              );
          }
          ///
          /// 超例外
          ///
          elseif( preg_match( '#^(.+)/([a-zA-Z0-9]+)$#', $buf, $tmp ) && $tmp[2] == $current['tagName'] ) {
            $current['childNodes'][] = array( 'tagName' => 'textNode', 'attrs' => array( 'value' => $tmp[1] ), 'childNodes' => array() );
            $current = $parent;
            if( $stack  ) $parent = array_pop( $stack );
          } else {
            /// textNodeとして"<"が含まれてるとか色々あるようだ
            continue;
          }
          $buf = '';
        }
        continue;
      }
      $buf .= $char;
    }
    return $current;
//    return $parent;
  }

  /******************************************************
   * タグ名を除いたタグの中身をAttributeのハッシュに変換
   * @param 文字列
   * @return ハッシュ
   ******************************************************/
  public static function parseAttributes( $chars ){
    $in_quote = 0;
    $esc      = 0;
    $attr     = '';
    $elms     = array();
    foreach( preg_split( '//', trim( $chars ) ) as $char ){
      if( $esc === 1 ){
        $esc = 0;
        $attr .= $char;
      } elseif( !$in_quote && !trim( $char ) ){
        list( $label, $value ) = self::parseAttribute( trim($attr ) );
        $attr && $elms[$label] = $value;
        $attr = '';
      } else {
        if    (  $in_quote === $char )                             $in_quote = null;
        elseif( !$in_quote && ( $char === "'" || $char === '"' ) ) $in_quote = $char;
        $char === '\\' && $esc = 1;
        $attr .= $char;
      }
    }
    return $elms;
  }

  /********************************************
   * attributeっぽい文字列をlabelとvalueに分割
   * @param 文字列
   * @return array( $label, $value );
   *********************************************/
  public static function parseAttribute( $chars ){
    $label = '';
    $value = '';
    $in_quote = 0;
    $esc      = 0;
    $buf = '';
    foreach( preg_split( '//', trim( $chars ) ) as $char ){
      if( !$in_quote  && $char === ' ' ) continue;
      if( $esc === 1 ){
        $esc = 0;
        $buf = $buf . $char;
      } else{
        if( $in_quote === $char ){
          $in_quote = null;
        } else{
          if( $in_quote ){
            if( $char === '\\' )  $esc  = 1;
            else                  $buf = $buf . $char;
          } else {
            if( $char === "'" || $char === '"' ) {
              $in_quote = $char;
            } elseif( $char === '=' ) {
              $label  =  $buf;
              $buf    = '';
            } else {
              $buf = $buf . $char;
            }
          }
        }
      }
    }
    $value =  $buf;
    return array( $label, $value );
  }

  /**********************************
   * ハッシュをアトリビュート化する
   * @param ハッシュ
   * @return アトリビュートっぽい文字列
   **********************************/
  private function buildAttributes( $attr ){
    $str = '';
    foreach( $attr as $key => $value ){
      if( $value == '' ) continue;
      $str .= ' ' .  $key .'="';
      if( is_array( $value ) ){
        // まあ、あんまり使わないで
        $str .= htmlspecialchars( join( ' ',  $value, ENT_QUOTES ) );
      } else {
        $str .= htmlspecialchars( $value, ENT_QUOTES );
      }
      $str .='"';
    }
    return $str;
  }
}

///
/// 弾さんのブログを試しにスクレイピングするとこうなる
///

// ob_start();
//$tag = new Tag('http://blog.livedoor.jp/dankogai/archives/51321141.html');
//print "title:" .  array_shift( $tag->query('title') )->getChildNode(0)->getAttribute('value') . "\n\n";
//print "date:"  .  trim( array_shift( $tag->query('h2.date') )->removeTag( array('a', 'span') )->getTextNode() ) . "\n\n";
//print "body:\n";
//print array_shift( $tag->query('div.blogbody') )->removeTag()->removeAttribute()->build();
//print mb_convert_encoding( ob_get_clean(), 'sjis', 'utf8' );
Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

No tags

近況

  • 飲んだくれてます
  • 仕事は割りと忙しかったりします
  • フレームワーク(笑) ver0.2という構想を細々形にしています

フレームワーク(笑) ver0.2

経緯

フレームワーク(笑) ver0.1系でウザかったところの修正がメイン。

まず、viewでヘルパを呼びつつ値を埋め込もうととすると、

<?php echo $this->escapeDeeply( $this->value1 ) );?>

とかって$thisを二回も三回も書かなきゃいけなくなるんだよね。これがウザイ。これはviewに限った話ではなくてactionのvalidateする時とかもそう。

if( $this->isMailAddress( $this->mail_address ) ){
   print 'hogehoge';
}

それから実は、ini_setでincludeパスをやたらめったらに通したりとか、require_once を一杯書いたりとかしなきゃならなかったり、モジュールの独立性(つまりクラスファイルをコピッてくれば、そのまま使えるような)を意識して作っていた事に起因する設定値の受け渡しとかが結構厄介になったりとかしていたところ。この辺をまとめてどうにかする事にしようとしているところですね。きわめて一部だけどver0.2で書くコードはこんな感じになります。これは基幹となるfwwObjectというクラスの自動テストの一部です。

FWW::start();
FWW::import( 'org.fww.core.*' );
FWW::import( 'org.fww.test.fwwTest' );
$obj = FWW::toObject(
  array(
    'html' => '<fuga>',
    'date' => '2009-09-09 20:20:00',
    'url'  => 'http://ishida-tak.sakura.ne.jp/wordpress' ,
    'ten'  => '0123456789',
    ) );
$t = new fwwTest( 'fwwObjectTest' );
$t->start();
$t->ok( $obj->html == '<fuga>' );
$t->ok( $obj->html( 'esc' ) == '&lt;fuga&gt;' );
$t->ok( $obj->url  == 'http://ishida-tak.sakura.ne.jp/wordpress' );
$t->ok( $obj->url( 'isURL' )  );
$t->ng( $obj->date( 'isURL' ) );
$t->ok( $obj->date == '2009-09-09 20:20:00' );
$t->ok( $obj->date( 'fmtD', 'YYYY年' ) == '2009年' );
$t->ok( $obj->ten( 'inStrLen', 10, true ) );
$t->ng( $obj->ten( 'inStrLen', 9, true ) );
$t->ok( $obj->ten( '#^(\d)(\d+$)#' ) );
$t->ok( $obj->ten( '$1' ) == '0'  );
$t->ok( $obj->ten( '$2' ) == '123456789'  );
$t->ng( $obj->date('#^(\d+$)#' ) );
$t->ok( $obj->date( '#^20#', '' ) == '09-09-09 20:20:00');
$t->end();
Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

No tags

6月/09

18

次世代var_dump

そのため、var_dumpで出力した後にブラウザのソースで配列の順番を確認する、といった操作はよくあることだろう。だがこれからはKrumoを使ってデバッグを行えば良さそうだ。

今回紹介するオープンソース・ソフトウェアはKrumo、次世代のvar_dump、print_rを目指すデバッグライブラリだ。

Krumoはデバッグ用のライブラリであり、特別な拡張は必要なく利用できる。開発時にはphp.iniにKrumoを設定しておくことが推奨されている。そしてKrumoの関数にデータを見たい変数を設定したら準備は完了だ。

引用元: MOONGIFT: » var_dump2.0を標榜するデバッグライブラリ「Krumo」:オープンソースを毎日紹介.

と言う事らしい。パっと見 便利そうだったので、後で試そうと思う。これ読んでるPHPerの人は是非試してみてください。

Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

No tags

Older posts >>

Find it!

Theme Design by devolux.org

Tag Cloud