ラベル プログラム の投稿を表示しています。 すべての投稿を表示
ラベル プログラム の投稿を表示しています。 すべての投稿を表示

2014-05-11

【ニコニコ】chrome系ブラウザでエコノミー画質に強制指定するユーザJavaScript

仕事用のデスクトップPCは性能がまぁまぁなのでニコニコ動画の再生くらいでは遅く感じたことはないのですが、普段使いのミニノートは640x480程度の解像度でかなり遅く感じます。
そこで、エコノミー画質にして再生するのですが、いちいち動画再生ページを開いて、プレイヤの設定画面を開いて。。。という手順を踏むのが面倒くさいわけです。

そこで、chromeのユーザJavaScriptを利用して強制的に動画再生ページリンクURLへ「eco=1」のパラメータを付加することでエコノミー画質にしてしまうことにしました。


// ==UserScript==
// @name         ニコニコ動画のエコノミー強制スクリプト
// @namespace    http://www.tagajo.tv
// @description  ニコニコ動画のエコノミー強制スクリプト
// @include      http://www.nicovideo.jp/*
// @exclude
// ==/UserScript==

var anchors = document.getElementsByTagName( 'a' );

for( var i in anchors ){
  if ( String(anchors[ i ].href).indexOf( 'watch' ) > -1 ) {
    anchors[ i ].href = add_eco_paramater( anchors[ i ] );
  } else if ( String(anchors[ i ].href).indexOf( 'playlist' ) > -1 ) {
    anchors[ i ].href = add_eco_paramater( anchors[ i ] );
  } else {
    ;
  }
}

function add_eco_paramater( a_tag )
{
  var url = '';
  if ( String(a_tag.href).indexOf( '?' ) > -1 ) {
    return a_tag.href + '&eco=1';
  } else {
    return a_tag.href + '?eco=1';
  }
}


これを何らかのテキストファイルに保存して「NicoNico.user.js」といったファイル名で保存しchromeの画面にドラッグ&ドロップすれば使用できるようになります。
削除するときは「拡張機能」に登録してあるものを削除すれば消えます。

それでは。

2013-12-25

【PHP】マニュアルのShift_JIS変換と整形・容量削減について

PHPの関数・言語マニュアルはWindows用のCHMファイル形式でも配布されていますが、このファイルがまったくの役立たずです。
ダウンロードしただけでは、中のヘルプHTML内容を見ることすらできないものなのです。

そこで、HTML Help Workshopというソフトで一度CHMファイルをバラして修正する必要があります。
2つのファイル名を変更するくらいならば手でも簡単にできますが、すべてのファイル(2013-12現在1万3千ファイル程度あります)の文字コードとメタタグ変更を手でやるのは時間の無駄なので、これを一気に行えるプログラムを作ってみました。
ただ、自分の環境ではうまくいっただけなので、注意して使ってくださいね。


 define( 'DEST_PATH', 'HTML Help WorkshopでデコンパイルしたHTMLファイルのディレクトリパス' );
 
 $d = dir( DEST_PATH );
 
 $i = 0;
 while( false != ($entry = $d->read()) ) {
  
  if( substr_count( $entry, '.html' ) > 0 ) {
   
   $get_file_path = DEST_PATH . $entry;
   
   $contents = file_get_contents( $get_file_path );
   print( '[' . $get_file_path . "] has input. \r\n" );
   
   $contents = mb_convert_encoding( $contents, 'sjis-win', 'UTF-8' );
   $contents = preg_replace( '/utf\-8/i', 'Shift_JIS', $contents );
   $contents = preg_replace( '/\"+/i', '"', $contents );
   $contents = preg_replace( '/\s+/i', ' ', $contents );
   
   file_put_contents( $get_file_path, $contents );
   print( '[' . $get_file_path . "] has output. \r\n" );
   
   $i += 1;
  }
 }
 $d->close();
 
 exit();



以上です。

2013-10-21

【テキスト】正規表現パターン作成代行サービス開始!

久しぶりに何かお客様から依頼されてモノを作りたくなったので、ちっちゃいところからやり直そうと言うことで正規表現を作成代行してみようと思います。
あまりにも複雑な正規表現パターン(たとえば、規約どおりのメールアドレスのパターンとか)じゃ無ければ作りますので、良かったらオーダーしてみてください。

→依頼ページ:正規表現についての簡単なものを作成します!

□依頼時の注意点
・料金は一つの依頼につき500円です。
・処理対象テキストデータを送ってもらいます。
 たとえばHTML、Aタグのhref属性を取得したいなら具体的なAタグのテキストを複数ください。
・処理内容の具体的な内容
 たとえば、XMLデータならどのタグの要素が欲しい、とか書いてください。

□依頼してからパターンが納品される日数
・簡単なものなら1日、比較的複雑なもので2~3日程度で納品します。
・パターン個数は基本的に1つの依頼で1つにしようと思っています。

以上です。それでは。

2013-10-05

【青空文庫】青空文庫のXMLをパースしてみた

【青空文庫】青空文庫のXMLをパースしてみた 自分の読書用に青空文庫に掲載してあるXMLをパースしてみたよ。
青空文庫のXMLは仕様通りにキッチリ書いてあるものと、そうでないものがあり、ちょっと苦労した。

以下のクラスは基本的に「大見出し」と「中見出し」で区切って配列にして返すものだけど、面倒な処理は正規表現やexplodeでばっさり切り分けたりしてます。

---
このクラスを使って小説を転載したものは、以下のサイトで読めます。
 ・多賀城[たがのき]小説投稿サイト
良かったら見てくださいませ。
---

/*
*   青空文庫XMLパーサ
*     2013/10/01
*/
class Aozora
{
  const         FROM_ENCODING = 'sjis-win';
  const         TO_ENCODING   = 'UTF-8';
  
  public        $filename;
  public        $title;
  public        $author;
  /*
    本文の階層構造
    
    大見出しがある場合
    bodies = array(
      '大見出し_01' => array( '中見出し_01' => '本文', '中見出し_02' => '本文', ... '中見出し_nn' => '本文' ),
      '大見出し_02' => array( '中見出し_01' => '本文', '中見出し_02' => '本文', ... '中見出し_nn' => '本文' ),
      '大見出し_03' => array( '中見出し_01' => '本文', '中見出し_02' => '本文', ... '中見出し_nn' => '本文' ),
              ...
      '大見出し_nn' => array( '中見出し_01' => '本文', '中見出し_02' => '本文', ... '中見出し_nn' => '本文' ) );
      
    大見出しが無い場合
    bodies = array( '中見出し_01' => '本文', '中見出し_02' => '本文', ... '中見出し_nn' => '本文' );
  */
  public        $bodies;
  public        $infomation;          // 小説情報
  public        $notes;               // 注
  public        $is_omidasi;
  public        $is_tyumidasi;
  
  private       $raw_text;
  private       $main_text;
  private       $biblio;
  
  public function __construct( $filename )
  {
    $this->filename = $filename;
    $this->raw_text = file_get_contents( $this->filename );
    $this->raw_text = mb_convert_encoding( $this->raw_text, self::TO_ENCODING, self::FROM_ENCODING );
    $this->bodies = array();
  }
  
  public function get_title()
  {
    return sprintf( '%s (作者:%s)', $this->title, $this->author );
  }
  
  public function get_bodies()
  {
    return $this->bodies;
  }
  
  /**
    XMLパース本体
  */
  public function parse()
  {
    // 改行を全て消す
    $this->raw_text = preg_replace( '#[\n\r]+#i', '', $this->raw_text );
    $this->raw_text = preg_replace( '#[\s]+#i', ' ', $this->raw_text );
    
    // タイトルと著者を取得する
    $ret = preg_match( '#

(.*?)

#i', $this->raw_text, $temp_text ); $this->title = $this->replace_imgtag( $temp_text[ 1 ] ); $this->title = trim( $this->title ); $ret = preg_match( '#

(.*?)

#i', $this->raw_text, $temp_text ); $this->author = $this->replace_imgtag( $temp_text[ 1 ] ); $this->author = trim( $this->author ); // 付加情報を取得する $ret = preg_match( '#
(.*?)
#i', $this->raw_text, $temp_text ); $this->infomation = $this->remove_htmltag( $temp_text[ 1 ] ); $this->infomation = $this->add_return( $this->infomation ); $this->infomation = trim( $this->infomation ); // 本文を全て取得する $ret = preg_match( '#
(.*)
#i', $this->raw_text, $temp_text ); $this->main_text = trim( $temp_text[ 1 ] ); // 大見出しから切り出し $this->parse_omidasi(); } /* 大見出しがある場合の本文切り出し */ private function parse_omidasi() { if( $this->is_omidasi( $this->main_text ) ) { // 大見出しがある場合 $temp_text = explode( '

', $this->main_text ); foreach( $temp_text AS $temp ) { if( trim( $temp ) == "" ) { continue; } if( !$this->is_omidasi( $temp ) ) { continue; } $ret = preg_match( '#(.*?)

(.*)$#', $temp, $temp_omidasi ); $omidasi = $this->remove_htmltag( $temp_omidasi[ 1 ] ); $omidasi = $this->replace_imgtag( $omidasi ); $omidasi = trim( $omidasi ); $this->bodies[ $omidasi ] = $this->parse_tyumidasi( $temp_omidasi[ 2 ] ); } } else { // 大見出しがない場合 $this->bodies[] = $this->parse_tyumidasi( $this->main_text ); } } /* 中見出しから本文切り出し @param $html 大見出し1つ分のテキスト または 本文全体のテキスト */ private function parse_tyumidasi( $html ) { $tyumidasi_array = array(); if( $this->is_tyumidasi( $html ) ) { $temp_text = explode( '

', $html ); foreach( $temp_text AS $idx => $temp ) { if( trim( $temp ) == "" ) { continue; } if( !$this->is_tyumidasi( $temp ) ) { continue; } $ret = preg_match( '#(.*?)

(.*)$#', $temp, $temp_tyumidasi ); $tyumidasi = $this->remove_htmltag( $temp_tyumidasi[ 1 ] ); $tyumidasi = $this->replace_imgtag( $tyumidasi ); $tyumidasi = trim( $tyumidasi ); $tyumidasi_array[ $tyumidasi ] = $this->remove_htmltag( $temp_tyumidasi[ 2 ] ); $tyumidasi_array[ $tyumidasi ] = $this->replace_imgtag( $tyumidasi_array[ $tyumidasi ] ); $tyumidasi_array[ $tyumidasi ] = $this->add_return( $tyumidasi_array[ $tyumidasi ] ); } } else { $tyumidasi_array[ 1 ] = $this->remove_htmltag( $html ); $tyumidasi_array[ 1 ] = $this->replace_imgtag( $tyumidasi_array[ 1 ] ); $tyumidasi_array[ 1 ] = $this->add_return( $tyumidasi_array[ 1 ] ); } return $tyumidasi_array; } /* 大見出しがあるかどうか */ private function is_omidasi( $html ) { $ret = substr_count( $html, '' ); if( $ret > 0 ) { $this->is_omidasi = true; } else { $this->is_omidasi = false; } return $this->is_omidasi; } /* 中見出しがあるかどうか */ private function is_tyumidasi( $html ) { $ret = substr_count( $html, '' ); if( $ret > 0 ) { $this->is_tyumidasi = true; } else { $this->is_tyumidasi = false; } return $this->is_tyumidasi; } /* 余分なHTMLタグを除去する */ private function remove_htmltag( $text ) { return preg_replace( '#<(?:|/)(?:em|div|span|a|h3|h4|hr)[^>]*>#', '', $text ); } /* 画像タグを置き換える */ private function replace_imgtag( $text ) { return preg_replace( '#]+>#', "$1", $text ); } /* * 改行を元に戻す */ private function add_return( $text ) { return preg_replace( '#]+>#', "\r\n", $text ); } }


以上です。

2013-09-16

【PHPで画像処理】ガンマ補正を行うクラス

自分で撮った写真や画像の明るさを偏向するのに便利なのがガンマ補正なのですが、映像からキャプチャした連続画像などを手で補正するのがダルいわけです。 そこで、ちょこっとPHPで組んでみました。

ちなみにガンマ補正とは、画像の「暗すぎ」とか「明るすぎ」というものを補正するのが目的の画像処理のことです。 Windows用のディスプレイはガンマ補正値が「2.2」となっているそうです。

/*
* ガンマ補正用クラス
*/
class Gamma
{
  private   $image;       // 入力画像リソース
  private   $new_image;   // 出力画像リソース
  private   $size_x;
  private   $size_y;
  
  /**
    コンストラクタ
    @param $image 入力画像のリソース
  */
  public function __construct( $image )
  {
    $this->image      = $image;
    $this->size_x     = imagesx( $this->image );
    $this->size_y     = imagesy( $this->image );
    $this->new_image  = imagecreatetruecolor( $this->size_x, $this->size_y );
  }
  
  /**
    デストラクタ
  */
  public function __destruct()
  {
    imagedestroy( $this->image );
  }

  /**
    変更後の画像リソース取得
    @return 画像リソース
  */
  public function get_image()
  {
    return $this->new_image;
  }
  
  /**
    出力用画像リソースを新しく作成する
  */
  public function renew_image()
  {
    unset( $this->new_image );
    $this->new_image  = imagecreatetruecolor( $this->size_x, $this->size_y );
  }
  
  /**
    ガンマ補正
    @param $gamma ガンマ補正値 ※ 0 < $gamma <= 5
    @return 成功:true 失敗:false
  */
  public function gamma( $gamma )
  {
    if( $gamma < 0 ) {
      return false;
    }
    
    if( $gamma > 5 ) {
      return false;
    }
    
    for( $x = 0 ; $x < $this->size_x ; $x += 1 ) {
      for( $y = 0 ; $y < $this->size_y ; $y += 1 ) {
        $this->culc_gamma( $x, $y, $gamma );
      }
    }
    
    return true;
  }
  
  /*
    ガンマ補正用計算
  */
  private function culc_gamma( $x, $y, $gamma )
  {
    $rgb = imagecolorat( $this->image, $x, $y );
    $r = ($rgb >> 16) & 0xFF;
    $g = ($rgb >> 8) & 0xFF;
    $b = $rgb & 0xFF;
    
    $temp_gamma = 1 / $gamma;
    $r_base     = $r / 255;
    $g_base     = $g / 255;
    $b_base     = $b / 255;
    $nr = (int)floor( 255 * pow( $r_base, $temp_gamma ) );
    $ng = (int)floor( 255 * pow( $g_base, $temp_gamma ) );
    $nb = (int)floor( 255 * pow( $b_base, $temp_gamma ) );
    
    $nrgb = imagecolorexact( $this->image, $nr, $ng, $nb );
    imagesetpixel( $this->new_image, $x, $y, $nrgb );
  }
}


ちなみに、以下にプログラムを使って編集した画像を載せてみます。
1枚目が元画像、2枚目が補正値0.5のもの(暗くしたもの)、3枚目が補正値2.0のもの(明るくしたもの)です。




2012-06-19

【PHP】PC用・スマートフォン用・携帯用のページ振り分けする方法

PC、スマートフォンおよび携帯電話のページを自動的に切り替える方法を考えてみました。
SEO的にどのような影響が出るかはわかりませんw


◆仕様
・(1)切り替え用のプログラムは、公開しているディレクトリ直下に置く
・レスポンスされるHTMLを生成するプログラムは公開されていないディレクトリに、「pc/」、「smart/」および「mobile/」という名前にしておく
・レスポンスされるHTMLを生成するプログラムは、(1)に置いてある切り替え用のプログラムで読み込む(インクルードする)
・PC用ページは「/pc」以下のURLでアクセスする
・スマートフォン用ページは「/smart」以下のURLでアクセスする
・携帯電話用ページは「/mobile」以下のURLでアクセスする
・振り分けのは「ユーザエージェント」を検査して行うものとする



参考)ディレクトリ構成
├─public/ (公開ディレクトリ)
│  │
│  ├foo.php(振り分けプログラム1)
│  └bar.php(振り分けプログラム2)
│
└─template/ (非公開ディレクトリ))
    │
    ├─pc/
    ├─smart/
    └─mobile/
        │
        ├foo.php (携帯HTML生成用プログラム1)
        └bar.php (携帯HTML生成用プログラム2)
          ※この2つのプログラムはpc/、smart/ディレクトリにも存在する。



◆.htaccess
mod_rewriteを使用して、/pc、/smart、/mobileにアクセスしてきたリクエストを仕様の(1)にアクセスさせます。
例として、
---
RewriteRule /pc/([a-zA-Z\.]+)$ /$1
RewriteRule /smart/([a-zA-Z\.]+)$ /$1
RewriteRule /mobile/([a-zA-Z\.]+)$ /$1
---
といった感じでしょうか。

◆振り分けプログラムのすること
振り分けを厳密にするのは大変なので、今回はユーザエージェントを使用して振り分けすることにします。
たとえば、以下のようなプログラムになるでしょう。


  define( 'PC',       0 );
  define( 'SMART',    1 );
  define( 'MOBILE',   2 );
 
  switch( true ) {
    case preg_match( '/DoCoMo/i',    $_SERVER[ 'HTTP_USER_AGENT' ] ):
    case preg_match( '/KDDI/i',    $_SERVER[ 'HTTP_USER_AGENT' ] ):
    case preg_match( '/J\-PHONE|Vodafone|SoftBank/i',  
      $_SERVER[ 'HTTP_USER_AGENT' ] ):
      define( 'USER',   MOBILE );
      break;
    case preg_match( '/Android/i',  $_SERVER[ 'HTTP_USER_AGENT' ] ):
    case preg_match( '/iPhone/i',   $_SERVER[ 'HTTP_USER_AGENT' ] ):
    case preg_match( '/iPod/i',     $_SERVER[ 'HTTP_USER_AGENT' ] ):
    case preg_match( '/iPad/i',     $_SERVER[ 'HTTP_USER_AGENT' ] ):
      define( 'USER',   SMART );
      break;
    default:
      define( 'USER',   PC );
  }


これを振り分けプログラムで判別し、それにあったHTML生成プログラムをインクルードすればOKです。
たとえば、


  switch( USER ) {
    case MOBILE:
      include( '/template/mobile/' . basename( __FILE__ ) );
      break;
    case SMART:
      include( '/template/smart/' . basename( __FILE__ ) );
      break;
    default:
      include( '/template/pc/' . basename( __FILE__ ) );
      break;
  }


というような感じになるでしょう。
HTMLを変更するときは、必要となるtemplate/以下のファイルを修正するだけでOKです。

2012-05-23

【サービス】家計簿システム作ってみた。

久しぶりに専門である「お金計算」のプログラム作ってみました。

今回は慣らしということで簡単な「家計簿」のシステムにしてみました。
開発期間は設計から賞味2日位です。
以前にも同じようなシステムを作っていたのですが、そちらはすでに古いバージョンのデータベースやPHPを使っていたので保守性も悪く、長いプログラムになっていました。
ですので、全面的にプログラムを書き直したうえでデータベースのバージョンもあげています。
あと、プライバシーとセキュリティを考えると、「名前」や「住所」は入力してもらわないほうが良いと判断して、ユーザ登録の際の入力には「メールアドレス」「パスワード」「ニックネーム」のみお願いしています

「TagajoTown Household Account Book System (TgagajoTown家計簿システム)」
紹介サイトURL  : http://kakei.tagajo.tv/
システムの使い方: http://kakei.tagajo.tv/how_to_use.php

というわけで、以下システムの目的と特徴紹介です。

■システムの目的
この家計簿システムは「現金出納」に利用するための物です(世間一般の家計簿はみな現金出納だと思いますが)。
基本的に、
1)預金からお金を引き出し
2)買い物をし
3)あまったら預金へ戻す
という流れを記録することで、現金の流れ(キャッシュフロー)を把握します。
つまり、無駄なお金を使ってないか確認するのが目的のひとつです。

あと重要なのが3)の「あまったら預金へ戻す」ことです。
これは「貯蓄を増やす」こととイコールなので「ヘソクリを増やしたい」という方にもお勧めの方法です。
このシステムでは、摘要が自由に設定できるので「預金預入」の摘要を追加して、その金額が増えるように家計を管理していけば、このシステムの目的達成、ということになります。

■特徴1.加算式家計簿
自分が家計簿(お小遣い帳でもいいですが)をつけるときに気になることがあります。
それは、家計簿をつけるとき「摘要」に合致する出納を「事前に集計して」からでないと家計簿に記載できないのがイヤだったということです。
たとえば、「食費」の摘要にはその日買った「昼食代」や「おやつ代」などを一旦「集計」するというひと手間です。

今回、それをなくすために以下のような方針で入力画面を設計しました。
1)1日の出納入力は「摘要」ごとに行う。つまりある日の出納データ数は設定した摘要の数までとなる。
2)摘要に入力した出納の数値は、すでにデータがあるときは「データに加算」、無いときは「その数値のデータを作成」とする。
3)摘要のデータは削除できないようにする(このシステムでの「出納データ削除」とは「摘要の削除」を意味するため)
4)削除できないため、出納の数値から「減算」もできるように「マイナスの数値」を受け入れるようにする。
という感じにしました。
このような設計にすると、「買った金額」ごとに入力できます
買い物の後レシートをもらったとして「その商品ごと」に金額を入力(しかも同じ摘要へ何度も)を行えるようになります。

こうしたおかげで「日毎に増えるデータ数」が限定され、ディスクの容量も節約できるようになり、集計演算もまぁまぁのスピードで行えるようになりました。

■特徴2.摘要のカテゴリ分類
摘要を「カテゴリ分け」できるようになっています。
カテゴリ分けすると、システムトップの集計一覧表に「小計」が表示されるようになります。
たとえば「収入」カテゴリに「預金引出」と「給与(手渡)」の摘要があったとして、2つの摘要の合計が「小計」欄に表示されるようになります。

また、これら「カテゴリ」「摘要」ともに「登録、変更、削除」ができるようになっています。
ただし、出納データが登録された摘要やカテゴリを削除すると同時に出納データも削除されてしまうので注意が必要です。

■特徴3.グラフ表示
システムのトップページには、「カテゴリ・摘要の集計一覧表」が表示されますが、そのほかに「摘要ごと」の折れ線グラフが表示されます。
このグラフ自体はGoogle Chart APIを使用しています。

以上です。

2012-04-29

【電脳卸】全てのカテゴリをパースする。

電脳卸のWebAPIで「全てのカテゴリを返す」XMLファイルがあるので、それをパースしてみたよ。
PHPの組み込み関数にもXMLをパースする関数があるけど、自分で実装したくなったので自分で書いてみたよ。

APIから返されるXMLの構造が微妙に難しかったので、再帰関数とスタックでやってみた。

APIのURLは→「http://webservice.d-064.com/3.x/category.xml」です。

/**
  電脳卸用全てのカテゴリをパースして配列して、シリアライズ後保存する。
  パース結果の配列は以下のような仕様で出力される
  
  結果配列仕様:
    array( 'カテゴリID 1' => array( 'name' => 'カテゴリ名', 'parent' => '親カテゴリID' ) );
    array( 'カテゴリID 2' => array( 'name' => 'カテゴリ名', 'parent' => '親カテゴリID' ) );
    array( 'カテゴリID 3' => array( 'name' => 'カテゴリ名', 'parent' => '親カテゴリID' ) );
    ...
    array( 'カテゴリID n' => array( 'name' => 'カテゴリ名', 'parent' => '親カテゴリID' ) );
    ※一番上のカテゴリは親カテゴリIDが「-1」となっている。
  
  商品のカテゴリIDから親カテゴリ全てを取得するには、    
    function get_category_tree( $category_id, &$category_tree )
    {
      global $global_category_array;
      
      $id = $category_id;
      $name   = $global_category_array[ $id ][ 'name' ];
      $parent = $global_category_array[ $id ][ 'parent' ];
      
      $category_tree[ $id ] = 
        array( 'name'   => $name, 'parent' => $parent );
      
      if( $parent == '-1' ) {
        return ;
      } else {
        get_category_tree( $parent, $category_tree );
      }
    }
    
    $global_category_array = unserialize( file_get_contents( './category_array.dat' ) );
    $tree = array();
    get_category_tree( '1269', $tree );
    var_dump( $tree );
  といったプログラムで返せる。
*/
define( 'CATEGORY_XML_URL',         'http://webservice.d-064.com/3.x/category.xml' );
define( 'CATEGORY_XML_FILENAME',    dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'category.xml' );
define( 'CATEGORY_ARRAY_FILENAME',  dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'category_array.dat' );
define( 'CATEGORY_SRC_ENCODING',    'UTF-8' );
define( 'CATEGORY_DIST_ENCODING',   'SJIS' );
date_default_timezone_set( 'Asia/Tokyo' );

/*
  個々のカテゴリデータを取得する
  @return array [ 'category_id', 'category_depth', 'category_name' ]
*/
function get_id_depth_name()
{
  global $xmlp;
  return array(
    preg_replace( '/<[^>]+>(.*?)<\/[^>]+>/', '$1', trim( fgets( $xmlp ) ) ),        // カテゴリID
    preg_replace( '/<[^>]+>(.*?)<\/[^>]+>/', '$1', trim( fgets( $xmlp ) ) ),        // カテゴリ深さ
    preg_replace( '/<[^>]+>(.*?)<\/[^>]+>/', '$1', trim( fgets( $xmlp ) ) ) );      // カテゴリ名
}
 /**
  カテゴリXMLをパースする。再帰関数。
  @param $parent 親カテゴリID
*/
function parse( $parent = -1 ) 
{
  global $xmlp;
  global $global_category_array;
  global $global_parent_ids;        // 親IDのスタック
  
  $line = trim( fgets( $xmlp ) );
  switch( true ) {
    // 空行
    case ($line == ''):
      parse( $parent );
      break;
    
    // XML定義
    case preg_match( '/<\?xml(?:[^>]+)>/', $line ):
      parse( $parent );
      break;
    
    // 一番外側のタグ
    case ($line == ''):
      parse( $parent );
      break;
    case ($line == ''):
      fclose( $xmlp );
      return;
    
    // 1つのカテゴリ
    case ($line == ''):
      list( $id, $depth, $name ) = get_id_depth_name();
      $name = mb_convert_encoding( $name, CATEGORY_DIST_ENCODING, CATEGORY_SRC_ENCODING );
      // 親IDを退避
      $parent = $global_parent_ids[ 0 ];
      // スタックに自分を積む
      array_unshift( $global_parent_ids, $id );
      
      $global_category_array[ $id ][ 'name' ]    = $name;
      $global_category_array[ $id ][ 'parent' ]  = $parent;
      
      parse( $parent );
      break;
    case ($line == ''):
      // 自分のIDを捨てる
      array_shift( $global_parent_ids );
      parse( $global_parent_ids[ 0 ] );
      break;
      
    // サブカテゴリ
    case ($line == ''):
      parse( $global_parent_ids[ 0 ] );
      break;
    case ($line == ''):
      parse( $global_parent_ids[ 0 ] );
      break;
    
    // ファイルが終わる前に「/Categorys」が来るはずなので使わないかも。
    case feof( $xmlp ):
      fclose( $xmlp );
      return;
      
    // 何かあったときのための保険。そのまま終了。
    default:
      die( '[' . $line . '] Exception.' );
  }
}


// XMLファイルダウンロードと保存
file_put_contents( CATEGORY_XML_FILENAME, file_get_contents( CATEGORY_XML_URL ) );

// XMLファイルのポインタ取得
$xmlp = fopen( CATEGORY_XML_FILENAME, 'r' );
if( !$xmlp ) {
  die( 'XML File Dont Open.' );
}

// 親IDスタックと結果配列準備
$global_parent_ids      = array( -1 );
$global_category_array  = array();

// パース本体
parse();

// 結果配列をシリアライズして保存
file_put_contents( CATEGORY_ARRAY_FILENAME, serialize( $global_category_array ) );

exit( 0 );

こんな感じ。

2012-04-25

【Scheme】ForthをSchemeで実装してみた。


はい。今度はScheme(処理系はGaucheを使用)でForthを実装してみました。

プログラムは5つの部分に分かれていて、
1)スタック管理
2)標準入力からプログラムを読むリーダ
3)ForthプログラムをSchemeプログラムに変換するトランスレータ
4)ワードが登録される辞書
5)実行制御
という構成になっています。

とりあえず、各部分の説明をしていきます。

1)スタック管理
;;; スタック管理

(define *data-stack* '())

(define (push val)
  (set! *data-stack* (cons val *data-stack*)))

(define (pop)
  (cond
   ((null? *data-stack*)
    (raise "Stack UnderFlow."))
   (else 
    (let ((val (car *data-stack*)))
      (set! *data-stack* (cdr *data-stack*))
      val))))

スタック管理は面倒くさくないグローバル変数を使ったものにしました。
関数もPUSHとPOPのみです。POPにはスタックアンダーフローがでたら例外が投げられるようになっています。

2)標準入力からプログラムを読むリーダ
;;; 標準入力から読み込み、トークンに分割する。

(define (trim str)
  (begin
    (set! str (regexp-replace #/^\s+/ str ""))
    (set! str (regexp-replace #/\s+$/ str ""))
    str))

;; シンボルに使えない文字を変換する
(define (def-word-translate str)
  (begin
    (set! str (regexp-replace-all #/\;/ str "edef"))
    (set! str (regexp-replace-all #/\:/ str "sdef"))
    (set! str (regexp-replace-all #/\./ str "dot"))
    (set! str (regexp-replace-all #/\.([a-zA-Z])/ str "dot\\1"))
    (set! str (regexp-replace-all #/\s+/ str " "))
    str))

;; 半角スペースで分割
(define (tokenizer str)
  (token->symbol (string-split (def-word-translate str) " ")))

;; トークンをシンボルと数値のリストにする
(define (token->symbol tokens)
  (cond
   ((null? tokens) '())
   ((rxmatch #/[0-9\.]+/ (car tokens))
    (cons (string->number (car tokens))
   (token->symbol (cdr tokens))))
   (else
    (cons (string->symbol (car tokens))
   (token->symbol (cdr tokens))))))

;; 標準入力からプログラムを読み取り、トークンにして返す
(define (reader)
  (let ((line (read-line)))
    (cond
     ((eof-object? line) '())
     ((string=? "" (trim line))
      (reader))
     (else
      (tokenizer (trim line))))))

基本的には標準入力から入ってきた文字列をトークンに分割するだけなのですが、「;」や「.」がSchemeでは特別な意味を持っているので、変換してからトークンに分割しています。

3)ForthプログラムをSchemeプログラムに変換するトランスレータ
;;; ForthプログラムをSchemeに変換する
(define *program* '())

(define (translate-token token)
  (cond
   ((number? token)
    (list 'push token))
   ((search-dict token)
    (list (get-dict token)))
   (else
    (raise (format #f "Undefined [~A]." token)))))

(define (define-word tokens)
  (cond
   ((null? tokens)
    (raise "Syntax Error: [;] is missing."))
   ((eq? 'edef (car tokens)) '())
   (else
    (cons (translate-token (car tokens))
   (define-word (cdr tokens))))))

(define (translate tokens define-flag)
  (cond
   ((null? tokens) '())
   ((and define-flag)
    (if (eq? (car tokens) 'edef)
 (translate (cdr tokens) #f)
 (translate (cdr tokens) #t)))
   ((eq? (car tokens) 'sdef)
    (add-dict (cadr tokens) 
       (append '(lambda ()) (define-word (cddr tokens))))
    (translate (cdr tokens) #t))
   (else
    (cons (translate-token (car tokens))
   (translate (cdr tokens) #f)))))

トランスレータは今はこれだけです。これに「ループ処理」や「ローカル変数」などを実装するともっと長くなりそうです。
ワード定義もここでやっていて、
(append '(lambda ()) '(define-word ...))
とやれば、手軽にワードを生成することができますね。

4)ワードが登録される辞書
;;; 辞書用プログラム
(define *dictionaly* (make-hash-table 'equal?))

;; 辞書初期化
(define (init-dict)
  (begin
    (hash-table-put! *dictionaly* '+ 
       (lambda () (push (+ (pop) (pop)))))
    (hash-table-put! *dictionaly* '- 
       (lambda () (let ((a (pop))) (push (- (pop) a)))))
    (hash-table-put! *dictionaly* '* 
       (lambda () (let ((a (pop))) (push (* (pop) a)))))
    (hash-table-put! *dictionaly* '/ 
       (lambda () (let ((a (pop))) (push (/ (pop) a)))))
    (hash-table-put! *dictionaly* 'mod 
       (lambda () (let ((a (pop))) (push (mod (pop) a)))))
    (hash-table-put! *dictionaly* 'dot 
       (lambda () (print (pop))))
    (hash-table-put! *dictionaly* 'dots
       (lambda () (print (reverse *data-stack*))))
    (hash-table-put! *dictionaly* 'exit
       (lambda () (exit)))
    ))

;; 辞書に追加
(define (add-dict key val)
  (hash-table-put! *dictionaly* key val))

;; 辞書検索
(define (search-dict key)
  (hash-table-exists? *dictionaly* key))

;; 辞書から取得
(define (get-dict key)
  (if (search-dict key)
      (hash-table-get *dictionaly* key)
      (raise (format #f "Undefined [~A]" key))))

ワード辞書もグローバル変数としました。辞書からワードを取得するとき、ワードが存在しなければ例外が投げられます。
ワードは「init-dict」関数に追加していけば使える組み込みワードが増えます。今は四則演算と表示くらいしかないですがw

5)実行制御
;;; Forthエントリポイント

;; 処理系初期化
(define (init-forth)
  (begin
    (load "./dictionaly")
    (load "./reader")
    (load "./stack")
    (load "./translate")
    (init-dict)))

;; ForthプログラムをSchemeに変換した結果を実行する
(define (exec-forth prog ret)
  (cond
   ((null? prog) ret)
   (else
    (exec-forth (cdr prog)
  (eval (car prog) (interaction-environment))))))


(init-forth)
(let loop ()
  (display "> ")
  (flush)
  (display (exec-forth (translate (reader) #f) #f))
  (newline)
  (loop))

これは特に説明することもないです。
処理系の初期化関数とトランスレートしたForthプログラムの実行くらいです。
トランスレートしたプログラムはただevalしてるだけですね。

こんな感じです。

2012-04-07

【Lisp/Scheme】Pythonで実装されたSchemeインタプリタをPHPにコンバートしてみた。

関数型言語勉強の一環として、比較的簡単なSchemeのインタプリタをいつも使ってるPHPで実装してみることを考えたのですが、参考になる書籍がなかったのでネットで探したところ以下のページを見つけました。
((Pythonで) 書く (Lisp) インタプリタ) http://www.aoky.net/articles/peter_norvig/lispy.htm

このページのプログラムはPythonで書かれています。PythonとPHPはかなり違う言語なので、なぜかPythonの勉強もしてしまいましたw

評価器以外には特に苦労しなかったので、評価器以外を直接見たい場合はこの記事一番下からダウンロードしてください。

■評価器(Evaluator)
評価器は標準入力から入力されたS式(Lisp系言語のプログラムのこと)を読取り器から得るシンボルや定数の列を受け取って解釈しプログラムを実行するものです。
以下具体的に見ていきます。実際のPythonプログラムは上のページを見てください。
評価器は再帰関数になってて、Schemeのスペシャルフォーム(評価器が式の内容全てを評価しない式のこと)の処理と、普通の式を処理するかなり簡単なプログラムです。
が、これをPHPにコンバートするのに苦労しました。以下が自分の書いた評価器プログラムです。

・コンバートしたPHPプログラム
/**
  定数、シンボルまたはシンボルの配列を評価する。再帰関数。
  @param $symbols 定数、シンボル、またはシンボルの配列
  @param $env     環境オブジェクト。指定されない場合はトップレベル環境。
*/
public static function lisp_eval( $symbols, $env = null )
{
  // 環境が指定されていないときはトップレベルの環境を指定
  if( $env == null ) {
    global $global_environment;
    $env = $global_environment;
  }
  
  // シンボルの場合
  if( is_a( $symbols, 'Symbol' ) ) {
    return $env->get( $symbols->value );
  
  // 定数の場合
  } elseif( is_scalar( $symbols ) ) {
    return $symbols;
  
  // 以降リストの場合
  } elseif( $symbols[ 0 ]->value == 'quote' ) {     // (quote exp)
    list( $quote, $exp ) = $symbols;
    return $exp;
    
  } elseif( $symbols[ 0 ]->value == 'if' ) {        // (if test conseq alt)
    list( $if, $test, $conseq, $alt ) = $symbols;
    if( self::lisp_eval( $test, $env ) ) {
      return self::lisp_eval( $conseq, $env );
    } else {
      return self::lisp_eval( $alt, $env );
    }
    
  } elseif( $symbols[ 0 ]->value == 'define' ) {      // (define var (lambda exp))
    list( $define, $var, $exp ) = $symbols;
    $env->add( $var->value, self::lisp_eval( $exp, $env ) );
    
  } elseif( $symbols[ 0 ]->value == 'lambda' ) {      // (lambda vars exp)
    list( $lambda, $vars, $exp ) = $symbols;
    $str_lambda  = '$args = func_get_args();' . "\n";
    $str_lambda .= '$env  = array_pop( $args );' . "\n";
    $str_lambda .= '$vars = unserialize( stripslashes( ' . "'" . addslashes( serialize( $vars ) ) . "'" . ' ) ); ' . "\n";
    $str_lambda .= '$exp  = unserialize( stripslashes( ' . "'" . addslashes( serialize( $exp ) ) . "'" . ' ) ); ' . "\n";
    $str_lambda .= '$new_env = new Environment( &$env );' . "\n";
    $str_lambda .= '$new_env->zip( $vars, $args );' . "\n";
    $str_lambda .= 'return Evaluator::lisp_eval( $exp, $new_env );' . "\n";
    $lambda_function = create_function( '', $str_lambda );
    return $lambda_function;
  
  } elseif( $symbols[ 0 ]->value == 'begin' ) {       // (begin exps)
    each( $symbols );
    foreach( $symbols AS $symbol ) {
      $val = self::lisp_eval( $symbol->value, $env );
    }
    return $val;
    
  } else {                                            // 関数評価
    $exps = array();
    
    foreach( $symbols AS $symbol ) {
      $exps[] = self::lisp_eval( $symbol, $env );
    }
    $proc = array_shift( $exps );
    
    /*
      小細工
      関数が使う環境オブジェクトを関数の引数として渡す。
    */
    array_push( $exps, $env );
    $ret  = call_user_func_array( $proc, $exps );
    return $ret;
  }
}


Pythonの評価器には「S式をリスト構造に直した」リストと、環境オブジェクト(後述)が渡されるようですが、PHPにはリストのように扱える組み込みデータ構造が用意されていません。
この関数のためにいちいちリスト構造を実装するのも面倒なので、配列をリストの代わりにしました。
引数の「$symbols」がその配列です。この配列に対して「each」「array_shift」「list」などを使えば、案外簡単に配列をリストのように扱えます。

・環境オブジェクトのこと
環境オブジェクト(以下環境)はトップレベルやレキシカルな環境を実現するためにあります。評価器に渡される環境オブジェクトには現在自分がいる階層の変数、関数が見えています。
また、環境には1つ外側の環境のポインタを持っていて、「あるシンボルを参照したいときに自分のいる環境に無い場合はひとつ外側の環境を参照する」という再帰メソッドが定義されています。
自分のいる階層の環境には自由に関数やシンボルを登録することができますが、自分より下(よりレキシカルな)環境にはアクセスできません。

・関数定義と環境オブジェクトに関する小細工
評価器にはS式のリストと環境が渡されます。リスト内に関数定義があると、評価する際微妙な問題を起こしました。
Pythonのプログラムは普通にクロージャ内に直接実行時の環境を渡せるのですが、自分が使っているPHP(Ver5.1.4)のクロージャには現在の環境をそのままでは渡せません。
なので、関数実行時に「関数の引数とともに現在の環境を押し込む」というなんともチカラ技なプログラムになってしまいました。
そんな感じなので、クロージャでは「引数のチェック」なんてものはしてませんw

こんな感じです。


Pythonのプログラムページに載っている以下のプログラムも「遅い」ですけど、一応動作します。
lis.py> (define area (lambda (r) (* 3.141592653 (* r r))))
lis.py> (define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1)))))) lis.py> (define first car)
lis.py> (define rest cdr)
lis.py> (define count (lambda (item L) (if L (+ (equal? item (first L)) (count item (rest L))) 0)))


PHPのソース全部は以下からダウンロード可能です。
http://www.tagajo.tv/lisphp_1.0.zip

■使い方
シェルに以下のように入力してください。
$ php -f lisphp.php

2012-03-14

【Androidアプリ】微速度撮影カメラ作ってみた。

Androidスマートフォン(Infobar A01)を買いました。
このスマートフォンにはカメラもついています。そこで、Androidアプリ開発になれるためにほしかった「微速度カメラ」を作ることにしました。

この「微速度撮影機能(インターバル撮影ともいいます)」とは、設定した間隔で自動でシャッターを切ってくれる機能のことで撮影した画像ファイルをぱらぱら漫画の要領で映像に変換すると早回し編集をしなくても面白い映像を作ることができます。
テストで撮影した動画は以下の通りです。



・仕様
対応機種  :Infobar A01 (他にテストできる実機がないため)
対応OSVer :2.3.3(他にテストできる実機がないため)
画面の向き:横のみ
画像保存場所:SDカード内、「DCIM/lapse」以下に保存
画像ファイル名:「yyyymmdd_hhmmss.jpg」

・使い方
1)「設定変更」ボタンをクリック
2)「画像解像度」、「シャッター間隔」、「撮影時間」を設定して「設定する」ボタンをクリック。設定は保存され、次回起動時には前回起動時の設定に自動的に変更されます。
3)「設定する」ボタンをクリックすると、自動的に前の画面に戻りますので「撮影開始」ボタンクリックで撮影開始します。
4)撮影を途中でやめるときは「撮影終了」ボタンをクリックします。


・スクリーンショット
撮影画面:
 設定画面:

・機能
1)スマートフォンのカメラに対応した画像サイズを設定可能
2)シャッター間隔は5、10、15、30、45、60、90、120秒に設定可能
3)撮影時間は1、5、10、15、30、45、60、90、120、180、240分に設定可能

・ダウンロード
Gumroad 微速度撮影カメラアプリ
Androidマーケット 微速度撮影カメラアプリ

・プログラム作成時に躓いたところ
1)ActivityクラスがJavaアプレットのようにライフサイクルによりイベントが発生する形式なので、Cameraクラスのメソッドreleaseを実行した後にtakePictureを実行してしまったりした。
2)Activityクラスは画面の傾きにより縦と横に向きが変わるが、そのたびに「onDestroy」と「onCreate」が発生しめんどくさい。
3)「メソッドの引数」と「クラスのメンバ」が「同じ名前」のときに、無名クラスからその名前を使うときは、引数にfinal修飾子が必要なこと
4)無名クラスから、その無名クラスを定義しているクラスの参照したいときには「無名クラスを定義しているクラス名.this」と書くこと
5)ぱっと見、スタックトレースやエラーメッセージの見方がわからないこと
6)実装の都合からActivityが1つしかないので、AlertDialogすら出すのに苦労すること。
7)TableLayoutで使うTableRowには「行の結合」ができないこと。

2012-02-22

【Gumroad】プログラム販売をしてみることにした

Gunroadというサービスがあります。
このサービスは「ファイルをアップロードする」あるいは自分のサーバ上にファイルを置いてその「URLを登録するだけ」でプログラムを販売するページが作成できる、というものです。
サービスへ払う必要がある手数料は「商品金額の5%+$0.30」ということになっています。
詳細はインプレスのInternet Watchの記事↓を参照してみてください。
http://internet.watch.impress.co.jp/docs/special/20120215_512149.html

とりあえず、自分が作ったPHPの自作ライブラリを販売していってみることにしました。
使い方はファイルの最初に「コメント」として記載しておきました。ちょっとしたサンプルプログラムも載っています。

1つ目:PHPのMySQL接続&結果セットクラス
2つ目:PHPカレンダー作成クラス
3つ目:Linuxサーバで自動的にFTPダウンロードするシェルスクリプト

2つ目は使い方がわからなくて「2ドル」の価格設定としてしまいましたが、1つ目の方は「200円」にしています。
さて、どのくらい売れるでしょうか。

それでは。

2011-11-07

【PHP】カレンダーの配列を作るプログラム

ブログ等に貼り付ける「カレンダーの配列」を簡単に作るクラスを作ってみた。
2次元の配列になってて、1次元目は「何週目」かをあらわす添え字で、2次元目は「曜日」の添え字。詳しくはコメントを読んでみてください。

可変引数の「$current_date」を指定するとその日付だったときに「c」へ「true」がセットされます。
これを利用すれば、「今日」や「どの日付のページを表示しているか」など応用が利くかな?とおもいます。

・使用方法
1)以下のプログラムを適当なファイルに保存
2)利用したいプログラム上でincludeやrequireを行い読み込む
3)$cal_array = Calendar::get_calendar( '2011-11-07' ); などど記載する。

・注意事項
引数の「$date」「$current_date」ともにチェックしてないので、必ずコメントにかかれたフォーマットで渡してください。
フォーマットを守らなかったときのテストはしていませんw

・販売中
プログラムはGumroadで販売中です。
※使い方はプログラムのコメントに書いてあります。

いわゆる、コントロールブレイク処理というやつなんですが、面倒くさくて嫌いっちゃ嫌いですねw

2011-03-03

【AmazonAWS】商品に付いたBrowseNodesのパース方法

AmazonAWSのItemLookupのResponseGroupにBrowseNodesを入れると、そのItemが属してるカテゴリを引っ張ってこれる。
でもこのXMLが結構複雑でなかなか手をつけられないでいたのだけど、思いのほか簡単な方法で何とかなったのでそのときのメモ。ちなみにそのときのサイトは「多賀城屋書店」というサイトで携帯サイトだった。

今回は例として「涼宮ハルヒの驚愕」を使って解説してみる。



AWSのBrowseNodesは下の例のような<BrowseNode></BrowseNode>が「複数」あることと、一番上にある階層のカテゴリがXML入れ子の一番深いところにあるのが特徴。
下の例だと「本→Featured Categories→文庫総合→ライトノベル→男性向け→角川スニーカー文庫」というならびになる。


--XML例はじめ--

<BrowseNode>
  <BrowseNodeId>2220015051</BrowseNodeId>
  <Name>角川スニーカー文庫</Name>
  <Ancestors>
    <BrowseNode>
      <BrowseNodeId>2189055051</BrowseNodeId>
      <Name>男性向け</Name>
      <Ancestors>
        <BrowseNode>
          <BrowseNodeId>2189052051</BrowseNodeId>
          <Name>ライトノベル</Name>
          <Ancestors>
            <BrowseNode>
              <BrowseNodeId>2189048051</BrowseNodeId>
              <Name>文庫総合</Name>
              <Ancestors>
                <BrowseNode>
                  <BrowseNodeId>202188011</BrowseNodeId>
                  <Name>Featured Categories</Name>
                  <Ancestors>
                    <BrowseNode>
                      <BrowseNodeId>465392</BrowseNodeId>
                      <Name>本</Name>
                    </BrowseNode>
                  </Ancestors>
                </BrowseNode>
              </Ancestors>
            </BrowseNode>
          </Ancestors>
        </BrowseNode>
      </Ancestors>
    </BrowseNode>
  </Ancestors>
</BrowseNode>

--XML例終わり--


タグの解説をすると、
・<BrowseNodeId>はカテゴリIDで数字のデータ
・<Name>はカテゴリ名
・<Ancestors>は、現在のカテゴリより「階層が上」のカテゴリ
という感じ。

実際にXMLをパースするときはおそらくDOMやらを使うんだろうけど、携帯サイトでそんなことやったら結構な負荷だし自分で実装するのもめんどくさい。
そこで、正規表現で切り出そうと考えた。

上のXMLをよく見ると、「</BrowseNode>」がはじめて出てくるのが「一番上の階層の閉じタグ」、ということに気づいた。
であれば、
「/<BrowseNode>(.*?)</BrowseNode>/」
というような正規表現パターンで「階層の上半分を取ってこれる(上の例の青い字の部分を取り出せる)。

ここからもう一度正規表現で<BrowseNodeId>と<Name>を切り出せばOK。
結構簡単なことだったんだなぁ、と思ったw


---注意----
BrowseNodeには「Children」という商品が属してるカテゴリよりも下位の階層のカテゴリも返してくれる。
今回はそこまで必要じゃなかったので割愛。

2011-02-11

【シェルスクリプト】Linuxサーバで自動的にFTPダウンロードする方法

Linuxサーバで自動的にFTPを実行するようなシェルスクリプト作ってみた。
簡単な定型処理だけならPerlとかよりシェルスクリプトのほうが手軽でいいよね。

今回のスクリプトの仕様は以下の通り。

◆仕様
・bashのシェルスクリプトで作成する
・何らかのログファイルを毎日ダウンロードする(今回はWebアクセスログ的なもの)
・ログファイル名には日付が入っており、実行する日の前日分のファイルをダウンロードする
・ログファイルをダウンロードしたら、リモートホストのファイルは削除する

というような感じにした。
各変数の説明は以下の通り。

◆変数の説明
・CURRENT_DIR
  実行時のディレクトリ。
・DEST_DIR
  ダウンロードしたファイルを保存するディレクトリ。必要ないなら指定しなくても良い。
・DATE_OF_YESTERDAY
  「YYYY-MM-DD」というフォーマットの「前日分日付」
・REMOTE_HOST
  接続するホスト名。IPアドレスも可能
・REMOTE_USER
  リモートホストのユーザ名
・REMOTE_PASS
  リモートホストのパスワード
・REMOTE_PATH
  リモートホストの対象ファイルが置いてあるディレクトリ。
・REMOTE_FILE_YESTERDAY
  リモートホストの「前日分」のログファイル名

◆使い方
以下のスクリプトをEUCエンコードと改行コード「LF」で保存し、パーミッションは「755」または「705」で保存して、クーロンに実行したい時間を指定して登録すればOK。

Gumroadで販売中(200円) → https://gumroad.com/l/ptKl

以上。

2011-01-20

【MySQL】バックアップ&レストアツール作ってみた。

ちょっと前からブログのネタにしてたMySQLだけど、結局MySQL附属の「mysqldump」とかではうまくいかなかったんだよね。
で、しょうがないからMySQLのバックアップとレストアツールを作ってみたよ。

まぁ、大規模なデータベースのバックアップはちょっとムリかもしれないけど、数百MBくらいのテーブルなら何とかなるかもしれない。

●仕様
・最大1つのデータベースのみバックアップ・レストアできる。
・必ずしも元のデータに復元できないことに注意すること!!
※複数のデータベースをバックアップしたいときは、このプログラムを複数の場所に入れればOK。

●バックアップの処理内容
全てのテーブル名を取得し、データを全件SELECTしてbase64でエンコードし保存するだけ。
テーブル1つに対しファイル2つが出来る。「CREATE文が入るファイル」と「データが入るファイル」となる。
たとえば「hoge」というテーブルがあるのであれば、「hoge_create.dat」と「hoge_data.dat」というファイルが出来る。
各レコードを各1行に収めるために改行文字を「¥r¥n」などに変換していることに注意。

●レストア処理の内容
ファイル名から「CREATE文」か「データ」かを区別しCREATE文から処理を始める。フラグを設定すれば「DROP TABLE IF EXISTS文」も入れられる。
データはbase64デコードしINSERT文を作って入れるだけ。データ項目に「'(シングルクォート)」が入ってる場合は二重化して回避。

●使い方
▲ダウンロードする
http://www.tagajo.tv/backuptool_ver01.zip
↑からツールをダウンロードして、展開する。このときディレクトリ構造をいじらないこと。


▲設定を環境に合わせる
「ダウンロードする」で展開したファイルの中に「config.php」というファイルがあるので、その中身を変更する。
具体的には以下の部分のみを変更すればOK。

---引用開始---
 // バックアップするデータベースの設定
 define( 'DATABASE_HOST',  'xxxxxxxxxxxxx' );
 define( 'DATABASE_USER',  'xxxxxxx' );
 define( 'DATABASE_PASS',  'xxxxxxx' );
 define( 'DATABASE_NAME',  'xxxxxxx' );

 // レストアするデータベースの設定
 define( 'RESTORE_HOST',   'xxxxxxxxxxxxx' );
 define( 'RESTORE_USER',   'xxxxxxx' );
 define( 'RESTORE_PASS',   'xxxxxxx' );
 define( 'RESTORE_NAME',   'xxxxxxx' );

 ~中略~

 // MySQLのクライアント文字コード(お使いのデータ本体の文字コードへ変更してください)
 // SET NAMES *** に使用されます。
 define( 'DATA_CHARACTER_CODE',  'sjis' );
---引用終了---

※注意
「DATABASE_XXXX」はバックアップするデータベースの設定
「RESTORE_XXXX」はレストアするデータベースの設定
なのを間違わないこと。

▲バックアップするサーバへアップロードする
設定を環境に合わせたら、バックアップ対象のデータベースがあるサーバへアップロードする。
アップロードする場所はどこでもOKだけど、Webで公開されているディレクトリはやめること。
セキュリティなんぞこのプログラムは考慮してないので。
アップロードするときも、ディレクトリ構造をいじらないこと。
アップロード先の「dat」ディレクトリには「700」等の書き込みできるパーミッションを設定しておくこと。

▲実行方法
PHPで記述されているのでPHPのインタプリタで実行する。
以下、PHPが「/usr/local/bin/」の下にあるとする。

・バックアップ実行コマンド
$/usr/local/bin/php -f /アップロードしたディレクトリ/backup.php

・レストア実行コマンド
$/usr/local/bin/php -f /アップロードしたディレクトリ/restore.php

以上。

2011-01-11

【MySQL】異なるサーバ間でのデータ移行方法について

Kagoyaのサーバで運営していたサイトを、さくらインターネットのレンタルサーバへ移動したときの作業メモです。
MySQLを使用してサイトを運営しているのですが、基本的にMySQL附属のダンプツールは文字化けなどの問題がたくさんあってあまり使えないですね。
さらにKagoyaサーバのmysqldumpは文字コードの指定を受け付けないようになってるみたいだし。

しょうがないので自分でダンプツールとレストアツールを作成し移行してみることにしました。

---
○サイトの仕様
・データベース数:1つ
・データベース容量:100MB程度
・レコード総数:26万件程度

○テーブルの移行方法
・テーブル自体の構造はphpMyAdminで構造だけ取り出し、移行する

○ダンプツールの仕様
・テーブル1つごとにダンプファイル1つで、テーブル名をファイル名とする
・ファイル内容は1行ごとに1レコード
・文字コードはデータに合わせる
・1行ごとに1レコードなので改行文字は「\r」や「\n」と表記し無理やり1行に収める

○レストアツールの仕様
・ダンプファイルから1行を読み出しINSERT文を作成しテーブルへINSERTする
・文字化けしないように、PHPの「mysql_escape_string関数」をかませる
・mysql_escape_stringを使用すると、ダンプツールで変換した「\n」が「\\n」と変換されてしまうので「\n」へ戻す
---

といった感じにしました。

---
○問題点と回避方法
・ダンプツールで大容量のテーブルをファイルに吐き出そうとするとメモリが足りない、とPHPに怒られる
→mysql_query関数を使用していたため、メモリに結果をバッファしようとして大量のメモリを使用していたことが分かり、メモリを極力使用しない「mysql_unbufferd_query関数」を使用することで回避。

・CSVでデータを吐き出すとデータ内のカンマなどで不整合がおきメンドイ
→「mysql_fetch_assoc関数」を使用し、「連想配列」で結果を返してもらうことにする。それを「serialize関数」で直列化して保存しておけば、読み出し側で「unserialize関数」で元に戻せる。
---

これでデータが全てダンプファイルに落とされたので、移動先のサーバへ持っていってレストアツールでレストアすれば作業完了。文字化けもしないはず。
あと、オマケとしてツールのソースコードを以下に掲載しておきますね。



○ダンプツールのソース
---
  function nl_2_char( &$item, $key )
  {
    $item = preg_replace( '/\r\n/i', '\r\n', $item );
    $item = preg_replace( '/\r/i', '\r', $item );
    $item = preg_replace( '/\n/i', '\n', $item );
  }
 
  define( 'DATABASE_HOST',    'ホスト名' );
  define( 'DATABASE_USER',    'ユーザ名' );
  define( 'DATABASE_PASS',    'パスワード' );
  define( 'DATABASE_NAME',    'データベース名' );
 
  $con = @mysql_connect( DATABASE_HOST, DATABASE_USER, DATABASE_PASS, false );
  mysql_select_db( DATABASE_NAME );
 
  $tables = array( テーブル名の配列 );
 
  foreach( $tables AS $tab    le ) {
    $file_name = dirname( __FILE__ ) . "/dat/" . $table . '.dat';
    @unlink( $file_name );
   
    $strSql  = "SELECT * FROM " . $table;
    $resource = mysql_unbuffered_query( $strSql, $con );
    if( !$resource ) {
      print( "ERROR!" );
      exit();
    }
    $table_data = array();
   
    print( $table . " START!\n " );
   
    while( $row = mysql_fetch_assoc( $resource ) ) {
      array_walk( $row, 'nl_2_char' );
      $buff = serialize( $row ) . "\n";
      file_put_contents( $file_name, $buff, FILE_APPEND );
    }
    unset( $resource );
   
    print( $table . " END!\n " );
  }
?>
---

○レストアツールのソース
---
  define( 'DATA_PATH',    dirname( __FILE__ ) . '/dat/' );
 
  define( 'DATABASE_HOST',    'ホスト名' );
  define( 'DATABASE_USER',    'ユーザ名' );
  define( 'DATABASE_PASS',    'パスワード' );
  define( 'DATABASE_NAME',    'データベース名' );

  // ファイルネーム取得
  $file_names = array();
  $d = dir( DATA_PATH );
  while ( false !== ($entry = $d->read()) ) {
    if( preg_match( '/[a-z0-9\_]+/', $entry ) ) {
      $file_names[] = $entry;
    }
  }
  $d->close();
 
  $con = @mysql_connect( DATABASE_HOST, DATABASE_USER, DATABASE_PASS, false );
  mysql_select_db( DATABASE_NAME );
  mysql_query( 'SET NAMES sjis' );    // データベースの文字コードにあわせて変更すること!
 
  foreach( $file_names AS $file_name ) {
    $table_name = basename( $file_name, '.dat' );
   
    // $rows = file( DATA_PATH . $file_name );
    $fp = fopen( DATA_PATH . $file_name, 'r' );
    if( !$fp ) {
      die( "DONT OPEN!\n" );
    }
   
    while( !feof( $fp ) ) {
      $row = fgets( $fp );
      if( trim( $row ) == "" ) {
        continue;
      }
      $fields = unserialize( trim( $row ) );
     
      $srcSql  = "INSERT INTO %s (%s) VALUES (%s)";
      $field_names = array();
      $field_values = array();
      foreach( $fields AS $field_name => $value ) {
        $field_names[]  = $field_name;
        $value = mysql_escape_string( $value );
        $value = str_replace( '\n', 'n', $value );
        $value = str_replace( '\r', 'r', $value );
        $field_values[]  = sprintf( ' %s ', $value );
      }
      $field_list = implode( ", ", $field_names );
      $value_list = sprintf( "'%s'", implode( "', '", $field_values ) );
      $strSql = sprintf( $srcSql, $table_name, $field_list, $value_list );
     
      $ret = @mysql_query( $strSql, $con );
      if( !$ret ) {
        print( $strSql . "\n" );
        print( mysql_error() );
        exit();
      }
    }
  }
?>
---

2010-12-29

【MySQL】同一マシン上の異なるデータベース間でのテーブルコピーの方法

レンタルサーバーなどで複数のデータベースが作成できるMySQLを使用している場合、
 「データベース1つ作成ごとに課金」
という方式を取っている業者もあります(たとえばKagoyaとか)。

経済事情などでデータベースを1つに統合したいときもありますよね?
でもmysqldump等のMySQL附属ツールでは文字化けしてエクスポートが失敗することがよくあります(phpMyAdminでも失敗することがある)。
自分は携帯サイトを運営しているので、エクスポート時に「絵文字」やらが文字化けしてしまうことがものすごく多いです。

そういった方のために参考までに自分でやってうまくいった方法を紹介します。ただ他の環境でうまくいくかはよく分かりません。試せる環境がないので。

---
◆前提
1)同じサーバマシン内でデータベースを複数使用していること。
2)phpMyAdminを使用していること

◆手順
△テーブルの「構造のみ」をエクスポート
1)「phpMyAdmin」にログインして「移動したいテーブル」で「エクスポート」をクリック。
2)「構造」にチェックが入っているか確認する。
3)AUTO_INCREMENTを使用しているのであれば「AUTO_INCREMENT値を追加」もチェックする。
4)あわせて「データ」にチェックが入ってないかを確認する。
5)エクスポートを実行する。以下エクスポートした「構造SQL」を単に「構造」という。

△エクスポートした「構造」を「移動したいデータベース」で実行する
1)「phpMyAdmin」で移動したいデータベースに「SQL」または「インポート」で「構造」を実行する。
2)テーブルが作成されればOK。

△データを移動する
1)データを移動する方法は「SQL」でスクリプトを作る。
2)例としては

INSERT INTO dest_table1 SELECT * FROM src_db.src_table1;
INSERT INTO dest_table2 SELECT * FROM src_db.src_table2;
INSERT INTO dest_table3 SELECT * FROM src_db.src_table3;
INSERT INTO dest_table4 SELECT * FROM src_db.src_table4;
※destは移動先。srcは移動元

という風なSQLを「移動先データベース」で実行すればOKです。

---
データが文字化けせず移動されたか確認して終了です。
参考になれば幸いです。

2010-12-22

【Amazon】AWSを使ってすばやい表示をするサイトを作る

表題の通り、AmazonのWebAPIであるAWS(名前がころころ変わるからこれでいいのか分からないw)を使ってすばやい表示のサイトを作った時のメモ。

まず、ページを表示するたびにAPIへ接続しデータを取得するようなサイトは表示が「遅い」し、ページビュー数が増えてくるとAPI提供側から規制が掛かったりする。
そこで、すばやくするためにはどうしたらよいかを考えた。

考えた結果、以下の2つを目標とすることにした。
・表示を早くする
・APIへのアクセスを低減する


という2つの目的を同時に実現するために以下のことをした。

---
1.自サイトを置いているサーバにAPIの結果をキャッシュとして保持し、それをファイルに保存しておく

2.APIへアクセスする時のURLはほぼ一意(ユニーク)なので、URLをmd5やsha1等でハッシュした結果をキャッシュのファイル名とした

3.APIのデータはXMLで返されるので、キャッシュに保存するのは「XMLをパースした結果のシリアライズされたもの」とする。読み込み時にアンシリアライズすればデータ取得が容易になる
---

1.で保存したキャッシュが「存在するならばAPIへはアクセスしない」というプログラムにすれば「APIアクセス低減」と「表示の高速化」が実現する。
しかし、APIで返されるデータは日々更新されるので、いつかは古いデータとなってしまう。これを回避するには「キャッシュを更新」する必要がある。
しかし、更新間隔を狭めて頻繁にAPIにアクセスするようなプログラムにすると結局「遅い」し「規制に掛かる」可能性がある。
この問題には以下のように対処した

---
1.個々の商品詳細データは「ほぼ更新されない」という「前提にした」(実際は価格などが更新される)ので、キャッシュの更新はしないこととした

2.商品一覧データは売れ筋が上に来る(APIで異なる指定が可能だが)ので、サイト構成上重要だから1週間で更新されるようにした

3.更新自体はプログラムがキャッシュへアクセスするときに「ファイル作成日付を確認し」1週間より前なら「APIへアクセスしデータを取得する」、とした
---

これでもAPIが規制に掛かってしまってエラーが返されたら、それは保存せずに従前のキャッシュを返すようにもした。
これで表示に大体1ページ数秒掛かっていたのが、1秒以内で表示されるようになった。


---
データベースが使えるならそちらのほうがプログラムが簡単かもしれない。データベースに全てのデータを突っ込んでおけばメンドウはないかもしれないし。
自分はデータベースを使用する方がメンドウだ、と作ったときは感じたのでそうしたけどね。

あと、この方法は小規模のそれほどページビューが多くないサイトを想定してます。具体的には1日1万ページビューくらい。
なので、もっと大規模なサイトならそれにあわせたものにしないとだめでしょう。

2010-12-21

【Web】JavaScriptのデバッグプリント

JavaScriptでプログラムを作る機会があったので、そのとき知った小技をメモ。
そのとき作ったサイト:MHP3rdスキル逆引きサービス

・コンソールにデバッグプリントする方法
Firefoxしか使えないけど、結構便利なデバッグプリントの方法。
→デバッグプリントとは、変数の中身を何らかの方法で出力し内容を確かめること、というような意味。
普通はメッセージボックスを使って変数の中身を出しているけど、いちいちメッセージボックスを閉じるのがメンドイよね?
そこで、

----
console.log( 何らかの変数 );
----

と書くとFirefoxのJavaScriptコンソールに書き出せる。

JavaScriptにはデバッグ用の機能が少ないから微妙にめんどくさいね。
PHPみたいな「var_dump」や「print_r」みたいなものがほしいよ。

ZenBack

WebMoney ぷちカンパ