CDBIでハマる

相変わらず、今更CDBIかという、誠に恐縮でございますが。

[error] Couldn't render template "undef error - DBD::ODBC::st execute failed: [M
icrosoft][ODBC Microsoft Access Driver] パラメータが少なすぎます。2 を指定してく
ださい。 (SQL-07002)(DBD: st_execute/SQLExecute err=-1) [for Statement "SELECT d
ate_added, author_removed, serial_number_id, author_added, date_removed, imp_typ
e_id
FROM   Imp_type_issued
WHERE  imp_type_issued_id=?
" with ParamValues: 1='16'] at C:/xampp/perl/site/lib/DBIx/ContextualFetch.pm li
ne 52.
"

というエラーが出て、死ぬほど調べまわってあーでもないこーでもないした結果、データベース側のカラム名の打ち込みがプアだった罠。
date_removedがdate_ermovedになってた。
"パラメータが少なすぎます。"で調べると真っ先に「カラム名が間違っていないか確認しろ!」とさんざん出てくるのに、あらぬ方向を調べ続けて時間を無駄にした自戒を込めてメモ。

3つのを連動させる [Perl][Catalyst][Prototype][Ajax]

3つの<select>を使って、次のようなことをしたい。
最初のselectで地方名を選ぶと、2番目の<select>に県名が表示され、2番目の<select>で県名を選ぶと、3番目の<select>に区市町村名が表示される。これを画面遷移なしで行いたい。

地方名→県名の2段連動なら、prototype.jsのobserve_fieldを使う。catalystからprototypeを使うやり方はこちらが参考になりました。
http://blog.mizzy.org/articles/2005/09/10/htmlPrototype

で、具体的にはtemplateに下記のように書き加える。

<head>
〜中略〜
<script type="text/javascript" src="/prototype.js"></script>
〜中略〜
</head>
<body>
〜中略〜
<select id="Region" size=20 style="width:150px;" multiple>
       <option value="1">北海道
       <option value="2">東北
       <option value="3">関東
               :
               :
</select>
[% c.prototype.observe_field( 'Region', {
               update  => 'div_pref'
               url     => c.req.base _ 'list/pref',
               with    => "'body='+value",
}) %]
<div id="div_pref">
<select id="pref" size=20 style="width:150px;" multiple>
</select>
</div>
〜中略〜
</body>

これで、地方を選ぶと、list/prefアクションが呼ばれるので、下記のようにコントローラで地方名を受け取り、それを元に県名リストを作成して、置き換える<select>を返す。

my @regions     = split(/,/, $c->req->param('Region'));

テンプレートはこう。(<option>だけ渡せばいいような気がするけど、<select>丸ごと置き換えないとうまく行かなかったので、こうなってます)

<select id="pref" size=20 style="width:150px;" multiple>
[% FOREACH i IN results %]
<option value=[% i.pref_id %]>[% i.pref_name %]</option>
[% END %]
</select>

しかし、地方名→県名→区市町村名の3段連動となると、うまく動かなかった。単純に[% c.prototype.observe_field %]を増やすだけではだめなようだ。いろいろ調べてみたが、いまいちスパッと答えが見つからず。
仕組みとしては、おそらくDOMツリーが構築された後に、<div id="div_pref">の中身をAjaxで書き換えているが、observe_fieldが監視しているのは書き換えられる前のDOMツリーなので、反応しないんジャマイカAjaxの基礎ができてないので、用語とかいろいろ間違ってるかもしれないが。

で、試行錯誤した結果、次のようにすることで目的は達成できた。
2回目のobserve_field(県名の変化の検知)が、1回目のobserve_field(地方名の変化の検知)のときに発動するように、[% c.prototype.observe_field %]が出力するスクリプトをまねして、直にjavascriptを書いた。

<head>
〜中略〜
<script type="text/javascript" src="/prototype.js"></script>
〜中略〜
</head>
<body>
〜中略〜
<select id="region" size=20 style="width:150px;" multiple>
       <option value="1">北海道
       <option value="2">東北
       <option value="3">関東
               :
               :
</select>
<script type="text/javascript">
<!--
function Observe_pref(element, value) {
       new Ajax.Updater(
               'div_cities',
               '[% c.req.base %]list/cities',
               {
                       parameters: 'pref='+value,
                       asynchronous: 1,
                       onComplete: function(request) {
                               // ここに付け足したら4段目もいけるかな?
                       }
               }
       )
}
function Observe_region(element, value) {
       new Ajax.Updater(
               'div_pref',
               '[% c.req.base %]list/pref',
               {
                       parameters: 'region='+value,
                       asynchronous: 1,
                       onComplete: function(request) {
                               new Form.Element.EventObserver( 'pref',
                                       function( element, value ) {
                                               Observe_pref(element, value);
                                       }
                               );
                       }
               }
       )
}
new Form.Element.EventObserver( 'region',
       function( element, value ) {
               Observe_region(element, value);
       }
);
//-->
<div id="div_pref">
<select id="Pref" size=20 style="width:150px;" multiple>
</select>
</div>
<div id="div_cities">
<select id="City" size=20 style="width:150px;" multiple>
</select>
</div>〜中略〜
</body>


以下、参考にさせていただいたサイト。

続きを読む

Active Directoryと連携して、svn:authorに氏名を表示する(Windows)

属性を変更できるようにする

デフォルトでは、svn:logをはじめとした属性を後から変えることはできないようになっているので、まずここを変更する。
Subversionサーバのリポジトリの実体があるディレクトリに、hooksディレクトリがあるので、その中のpre-revprop-change.batを作成する。

rem  [1] REPOS-PATH   (the path to this repository)
rem  [2] REVISION     (the revision being tweaked)
rem  [3] USER         (the username of the person tweaking the property)
rem  [4] PROPNAME     (the property being set on the revision)
rem  [STDIN] PROPVAL  ** the property value is passed via STDIN.
if "%4"=="svn:author" (
    exit 0
)
exit 1

フックスクリプト(post-commit)で変更する

下記は、perlで行う場合の例。まずバッチファイルでperlスクリプトをキックする。

rem  [1] REPOS-PATH   (the path to this repository)
rem  [2] REVISION     (the revision being tweaked)

set REPOS=%1
set REV=%2

C:\Perl\bin\perl D:\testrepository\hooks\ldap.pl %REPOS% %REV%

exit 0

rem http://subversion.apache.org/faq.ja.html#hook-debugging
rem Subversion はフックスクリプトを起動する前に、全ての環境変数を取り除く。
rem その中には、Unixでは $PATHが、Windows では %PATH% が含まれる。
rem 結果、スクリプトでは、絶対パス名の記述された他のプログラムだけを実行可能だ。

フックで呼び出されたら、実際の処理を行う。

実際の処理部分は、下記の通り。
おおざっぱに言うと、svnlookコマンドでsvn:authorの値を取り出して、Active DirectoryLDAPで問い合わせをして、氏名を割り出し、svn propsetで当該のリビジョンに埋め込んでいる。

use strict;
use warnings;

use Encode;
use Net::LDAP;

my ($repos, $rev)       = @ARGV;

my $cn                  = "--USERNAME--";                                                       # LDAPアクセス用ID
my $pass                = "--PASSWORD--";                                               # LDAPアクセス用パスワード
my $ldap_server = 'example.com';                        # LDAPサーバー
my $ldap_base   = 'DC=example,DC=com';
my $ldap_bind   = "CN=$cn,OU=users,$ldap_base";
my $displayname;
my $url                 = 'http://example.com/svn/';    # SubversionリポジトリのURL

open my $file, '>', "logs/ldap_pl$rev.txt" or die $!;   # ログ記録開始

print $file '$repos: ' . "$repos\n";
print $file '$rev  : ' . "$rev\n";

my $command     = "svnlook author $repos -r $rev";
open my $dir, '-|', $command or die $!;
my $author_id;
while(my $line = <$dir>){
        chomp $line;
        $author_id = $line;
}
close($dir);

print $file '$author_id: ' . "$author_id\n";

my $ldap = Net::LDAP->new($ldap_server,);       #サーバー名

print $file "ldap: $ldap\n";

my $msg = $ldap->bind(
        $ldap_bind,
        password => $pass,
);      #ユーザ名とパスワードで認証

my $filter = "cn=$author_id";

print $file encode('cp932', decode('utf8', __LINE__ . ": " . "LDAP接続結果 = " . $msg->error . "\n"));      #ここまでのerrorログを取得
$msg = $ldap->search(
        base            => $ldap_base,
        filter          => $filter,
        attribute       => 'cn=lang-ja'
);      #search処理

for my $entry ($msg->all_entries) {
        $displayname = $entry->get_value('displayname');
}

print $file encode('cp932', decode('utf8', "$displayname\n"));
Encode::from_to($displayname, 'utf8', 'cp932');

$command        = "svn propset svn:author --revprop -r $rev $displayname($author_id) $url --username $cn --password $pass";
print $file "$command\n";
close $file;
system($command);

Subversionで外部定義(svn:externals)を利用したブランチをタグリリースする。

タグを作成するときに、外部定義(svn:externals)を含んでいると、参照先が更新されたときに外部定義部分だけ最新版になっちゃったりする。
外部定義にリビジョンを明示してあげればいいんだけど、外部定義のあるところをいちいち手動で書き換えるのはめんどくさい。
サーバ側のフックスクリプト(pre-commit, post-commit等)でなんとかできないか調べてみたけど難しそうだったので、クライアント側でタグをcommitする際にsvn:externalsに参照するリビジョンを埋め込むスクリプトを組んでみた。<使用方法>

  1. タグリリースするフォルダと同じ階層にスクリプトを置く
  2. Terminalでスクリプトを実行する
  3. タグの元になるフォルダ名を入力する
  4. タグ名を入力する
  5. コミットログを入力する

<スクリプトの内容>

  1. タグの元になるフォルダをsvn copyする
  2. コピーしたフォルダのリポジトリURLの中のbranchesをtagsに置換して、svn switchする(このときにリポジトリのHEADのリビジョン番号を取得)
  3. コピーしたフォルダに含まれるsvn:externalsを検索し、2で取得したリビジョン番号を埋め込む
  4. コピーしたフォルダをcommitする
#!/usr/local/bin/perl

use strict;
use warnings;
use Encode;

my $os  = 'linux';              # windowsで利用する場合はコメントアウトしてください。
#my $os = 'windows';    # linuxで利用する場合はコメントアウトしてください。

# 出力文字コードの選択
my $output_code;
if ($os eq 'windows'){
       $output_code    = 'cp932';
}
else {
       $output_code    = 'utf8';
}

# ブランチをコピーする
       opendir my $current_dir, './';
       print encode($output_code, decode('utf8', "フォルダ一覧:\n"));
       while (my $file = readdir($current_dir)){
               next if ($file =~ /^\./);
               if (-d $file){
                       print "$file\n";
               }
       }
       closedir($current_dir);

       # コピー元フォルダの入力
       my $src;
       while (1){
               print encode($output_code, decode('utf8', "タグリリースする元フォルダ名を入力してください:"));
               $src    = <STDIN>;
               chomp($src);
               if (-d $src){
                       last;
               }
               else {
                       print encode($output_code, decode('utf8', "指定のフォルダが存在しません。\n"));
               }
       }

       # タグ名の入力
       my $dst;
       print encode($output_code, decode('utf8', "タグ名を入力してください           :"));
       $dst    = <STDIN>;
       chomp($dst);

       # copyの前のupdate確認(とりあえずしてない)

       # svn copyの実施
       print encode($output_code, decode('utf8', "svn copyを行います。\n"));
       system("svn copy $src ./$dst");

       # 切り替え先URLの作成
       chdir($dst);
       open my $svninfo, '-|', 'svn info' or die $!;
       my $changed_url;
       while(my $line = <$svninfo>){
               chomp $line;
               if($line =~ /^URL:\s(.*)/){
                       $changed_url    = $1;
                       $changed_url    =~ s/branches/tags/;
                       print encode($output_code, decode('utf8', '次のURLにタグを作成します: ' . $changed_url . "\n"));
               }
       }
       close($svninfo);

       # svn switchの実施
       chdir("../");
       open my $svnswitch, '-|', "svn switch $changed_url $dst" or die $!;
       my $rev;
       while(my $line = <$svnswitch>){
               chomp $line;
               Encode::from_to($line, $output_code, 'utf8');
               if($line =~ /^リビジョン\s([\d]*)\sです。/){
                       $rev = $1;
               }
       }
       print encode($output_code, decode('utf8', 'svn:externalsを次のrevに固定します: ' . $rev . "\n"));
       close($svnswitch);

# externalsを書き換える
       # svn:externalsの取得
       chdir($dst);
       open my $svnpropget, '-|', 'svn propget svn:externals -R' or die $!;
       while(my $line = <$svnpropget>){
               chomp $line;
               if($line =~ /(.*)\s-\s(.*)\s(.*)/){
                       my $target      = $1;
                       my $param       = "$3 -r$rev $2";
                       # 何故か、リビジョン指定なしとリビジョン指定ありで、SRC/DSTの記述順が逆
                       # リビジョン指定なし: SRC DST
                       # リビジョン指定あり: DST -r rev SRC
#                       print '$target: ' . $target . "\n";
#                       print '$param : ' . $param  . "\n";
                       system("svn propset svn:externals \"$param\" $target");
               }
       }
       close($svnpropget);

# リポジトリにコミットする
       chdir("../");
       print encode($output_code, decode('utf8', "コミットログを入力してください: "));
       my $commit_log  = <STDIN>;
       chomp($commit_log);
       system("svn commit $dst -m $commit_log");

__END__

以下、いろいろ参考にさせてもらったところ。(結果的に関係なかったものも含む)

続きを読む

PARでハマるのこと

pp -o helloworld.exe helloworld.pl

とすると、こんなエラーが発生していた。

Can't locate loadable object for module IO in @INC (@INC contains: CODE(0x10b483
0) .) at C:/xampp/perl/lib/IO/Handle.pm line 262
Compilation failed in require at C:/xampp/perl/lib/IO/Handle.pm line 262.
BEGIN failed--compilation aborted at C:/xampp/perl/lib/IO/Handle.pm line 262.
Compilation failed in require at C:/xampp/perl/lib/IO/Seekable.pm line 101.
BEGIN failed--compilation aborted at C:/xampp/perl/lib/IO/Seekable.pm line 101.
Compilation failed in require at C:/xampp/perl/lib/IO/File.pm line 133.
BEGIN failed--compilation aborted at C:/xampp/perl/lib/IO/File.pm line 133.
Compilation failed in require at -e line 313.

環境変数Perl5libがセットされていなかった。

Perl5lib=C:\xampp\perl\lib

これをセットしたら解決した。

以下、いろいろ参考にさせてもらったところ。(結果的に関係なかったものも含む)

続きを読む

Adobe AIRとCatalystでログオン画面のスクリーンセーバーを作ってみた

伝言板になるスクリーンセーバーを作ってみた。主な材料は以下の通り。

Adobe AIR スクリーンセーバー表示用
Catalyst 伝言板データの送信、受信用
Access 伝言板データの保存用
  • Windowsスクリーンセーバーを作る。AIRアプリの実行ファイル(.exe)の拡張子を.scrにすることで作成可能
  • 伝言板のデータを更新する。伝言内容を別途Webアプリで供給し、AIRアプリでAjaxを使って受信する。
  • 動きのあるスクリーンセーバーにする。AIRアプリをAjaxで動かす。
  • AIRアプリ(HTMLベース)で他サイトからデータを受信する。XMLHttpRequestを使用する。
  • スクリーンセーバーにするため、マウス操作/キー入力等で終了する。window.addEventListenerでイベントを拾い、window.nativeWindow.closeで閉じる。(終了処理これでいいんだろうか。適当すぎるかもしれない)
  • ログオン画面のスクリーンセーバーとして登録する。下記レジストリの編集が必要。HKEY_USERS\.Default\Control Panel\Desktop\SCRNSAVE.EXE

メモ:参考になった記事

作業当初、Aptanaで新規AIRプロジェクトの作成に失敗する(ウィザードが起動しない)
上記記事が近いかと思ったが、解決せず。結局Aptanaアンインストール→再インストールで直った。

AIRアプリは以下のような物。html+javascriptで記述。

<html>
       <head>
       <title>Application Sandbox sample</title>
       <link href="sample.css" rel="stylesheet" type="text/css"/>

       <!-- Uncomment the following line to add introspection.  When running the application hit F12 to bring up the introspector -->
       <!-- <script type="text/javascript" src="AIRIntrospector.js"></script> -->

       <!-- Uncomment the following line to use the AIR source viewer -->
       <!-- <script type="text/javascript" src="AIRSourceViewer.js"></script> -->

       <!-- Uncomment the following line to use the AIR Localizer -->
       <!-- <script type="text/javascript" src="AIRLocalizer.js"></script> -->

       <!-- Uncomment the following line to use the AIR Menu Builder -->
       <!-- <script type="text/javascript" src="AIRMenuBuilder.js"></script> -->

       <script type="text/javascript" src="AIRAliases.js"></script>
       <script type="text/javascript">
var objYf1=30; // 文字列の初期配置(x座標)
var objXf1=50; // 文字列の初期配置(Y座標)
var moveYf1=4; // 1度にスクロールする縦幅
var moveXf1=2; // 1度にスクロールする横幅
var speedf1=50; // スクロールする間隔(スピード)(ミリ秒単位)
var received_data=new Array(2);

//              window.addEventListener("mousemove", appClose, false);
               window.addEventListener("click", appClose, false);
               window.addEventListener("keydown", appClose, false);

tf1=0;
function getWinf1(){
       if(document.all){  // ブラウザの横幅、縦幅を取得
               winHeight=document.body.clientHeight;
               winWidth=document.body.clientWidth;
       }
       else {
               winHeight=window.innerHeight;
               winWidth=window.innerWidth;
       }
}
function inif1(){
       if(document.getElementById){
               obj=document.getElementById("ff1");
               obj.style.width=obj.offsetWidth+"px";
                       // スクロール文字の幅を取得→widthを指定
               flyf1();
       }
}
function flyf1(){
       getWinf1();
       if(objYf1>=winHeight-obj.offsetHeight || objYf1<=0){
                       // 端まで進んで向きを変える
               moveYf1*=-1;
       }
       if(objXf1>=winWidth-obj.offsetWidth || objXf1<=0){
               moveXf1*=-1;
       }
       objXf1=objXf1+moveXf1;
       objYf1=objYf1+moveYf1;
       obj.style.top=objYf1+"px"; // 文字列の縦横方向の配置
       obj.style.left=objXf1+"px";
       clearTimeout(tf1);
       tf1=setTimeout(flyf1,speedf1); // スクロールする間隔
}
       function createhttprequest(){
               var request=null;
               if("XMLHttpRequest" in window){
                       request= new XMLHttpRequest();
               }
               else if("ActiveXObject" in window){
                       try{
                               request=new ActiveXobject("Msxml2.XMLHTTP");
                       }
                       catch(e){
                               try{
                                       request=new ActiveXObject("Microsoft.XMLHTTP");
                               }
                               catch(e){
                               }
                       }
               }
               return request;
       }

       var request;
       var xmlData;
       var main;


       function requestsource(url){
               request=createhttprequest();
               request.open("GET",url,true);
               request.onreadystatechange=sourceget;
               request.send(null);
       }

       function sourceget(){
               if (request.readyState == 4 && request.status == 200){
                       received_data=request.responseText.split('|');
                       $("age").innerHTML=received_data[0]+"日";
                       $("date").innerHTML="起算日:"+received_data[1];
//                      $("result").innerHTML=request.responseXML.getElementsByTagName('ff1');
               }
               else{
               }
       }

           function setDisplayState() {
                       window.nativeWindow.stage.displayState =
                               runtime.flash.display.StageDisplayState.FULL_SCREEN_INTERACTIVE;
                       }

       function appInit() {
               setDisplayState();
               requestsource("http://foo/bar");
               inif1();
       }

       function $ (tagId) {
               return document.getElementById(tagId);
       }

       function appClose() {
               window.nativeWindow.close();
       }
</script>
<style type="text/css">
<!--
body {
       color: #fff;
       background-color: #000;
}
#ff1 {
       position: absolute;
       top: 30px;
       left: 50px;
}
-->
</style>

</head>

<body onload="appInit();">
<div id="ff1">
       <table border=0 height=200px>
               <tr><td><font size=+2>
無災害+記録表<br>
<center><div id="age"></div></center><br>
<img src=icons/180px-Smiley.svg.png><br>
<div id="date"></div>
</font></td></tr>
</table>
</div>
</body>
</html>

Excelでなぞの外部ファイルへのリンクが残っていた件

Excel 2002でファイルを開くときに、下記ダイアログが開くファイルがあった。

http://foo/bar.xlsを開くことができません。インターネット サーバーまたはプロキシ サーバーが見つかりませんでした。

Excelの検索機能で該当しそうなセルを探してみたが見つからない。
オートシェイプにリンクが設定されているかもしれないと思い、別名で保存(html形式)して、秀丸でソースを確認したところ、なるエレメント(?)の中に、当該のPathを発見。
再度、Excelで該当のファイル(.xls)を開き、[挿入]-[名前]-[定義]で、「名前の定義」ダイアログを出し、中に残っていた不要な定義を消すと、を消すことができた。

参考:
New White - Excelにて別ファイルへのリンクを削除する方法
Excelのオートシェイプ中の文字を検索する方法 - 三日坊主と呼ばせない!日記
PCreview - Is there any way for me to easily remove the SupBook reference via the Excel GUI or (even better) programmatically?