2008/04/03


tumblrは閲覧一方な私ですが、うざったく感じていたのが画像を大きくして見たい場合や動画を再生したい場合に、いちいちマウスに手を伸ばさなきゃいけない事。
minibuffer入れてる人なんかはキーボードだけで操作したい派のはず。イライラの種ですね。

そんなめんどくさがりやさんの貴方にお勧めしたいのがコレ!
ENTERキーだけで画像を拡大縮小出来ます。また動画の再生停止も行えます。
音声の再生停止は出来ません!!!!><
動作にはminibufferが必要です。
j,kで移動してENTERで見る!って感じです。
ソースは汚いので見てはいけません。

動かなかったら「pray on tumblr」って事で...
playontumblr.user.js
Posted at by



2008/04/02


ただいま、JavaとCOMを使ったお仕事をしているんですが、どうも正常に動かないものがありまして、困っておりました。

どんな現象かといいますと、Microsoft AgentをAgentObjectsから起動した時に、ConnectedというBOOL型のプロパティをTRUEに設定してやるのですが、どうやらJavaから渡していた値trueが、VARIANT変換により1になっていた事が原因していました。

まぁ、1だしfalseじゃないなら動作するのが一般的なアプリでしょ...と思ってましたが、どっこいMSAgent殿は例外も出さずにConnectedをFALSEに設定して下さいました。

実際、COM上ではBOOLはVARIANT_TRUEもしくはVARIANT_FALSEを設定するのがお決まりで、VARIANT_TRUEは ((VARIANT_BOOL)0xffff)
と宣言されていました。C++上でのtrueとは値が異なる訳です。

まぁ、それは自分のミスとして...

きっとMSAgentのConnectedプロパティ処理では
if (VARIANT_TRUE == arg) {
  // Connected 設定時の処理...
}
というコードが書かれているのだと思うのですが、これは if (VARIANT_FALSE != arg) {
  // Connected 設定時の処理...
}
って書くべきなんじゃないかな...

ちなみに、Visual Studioのウォッチエリアに追加した、1の値を持つ問題のVARIANT変数は、VT_BOOL:Trueと表示されていました。
Posted at by



2008/04/01


以前、miyagawa氏作のXML::Atom::ServerをベースとしてXSをなるべく使わない「XML::Atom::Server::Lite」というのを作ってCodeReposに放置していたのですが、そろそろ放置しすぎな感もあったので発作的にエントリ。
「XML::Atom::Server::Lite」は「XML::Atom::Server」と構造をほぼ同じにしてあり、中で使用しているXML::Atomを自前のXSを使わないクラスで置き換えてしまおうというモジュールです。
LibXMLが使えないサーバには、もしかしたら有用かもしれません。サーバ用途だけならばLWPも使いませんし、簡単に低コストでAtom::Serverが実現出来ると思います。
「Digest::SHA」も「Digest::SHA::PurePerl」で置き換えています。
今日はこの「XML::Atom::Server::Lite」を使ってblosxomのAtom::Serverを作ってみました。
ソースは以下の通り。
#!/usr/bin/perl

package Blosxom::AtomPP;
use strict;
use base qw( XML::Atom::Server::Lite );
use File::Find;
use File::stat;

if (-e "/path/to/blosxom/config/file/config.cgi") {
    package blosxom;
    require '/path/to/blosxom/config/file/config.cgi';
}

my %Passwords = (
    yourname => 'yourpassword',
);
my $postsWanted = 10;

sub password_for_user {
    my $server = shift;
    my($username) = @_;
    $Passwords{$username};
}

sub getFilenameFromPostId {
  my $postid = shift @_;
  unless ( $postid =~ /$blosxom::datadir/ ) {
    if ( $postid =~ m!^/! ) {
      #Post ID now has a preceeding /
      $postid = $blosxom::datadir . $postid;
    } else {
      $postid = "$blosxom::datadir/$postid";
    }
  }
  return $postid;
}

sub getRandomFilename {
  my $time = time;
  $time .= int( rand(10) );
  $time .= int( rand(10) );
  return $time;
}

sub decode_html {
    my $str = shift @_;
    $str =~ s/&#([0-9]+);/chr($1)/ge;
    $str =~ s/&#[xX]([0-9A-Fa-f]+);/chr(hex $1)/ge;
    $str =~ s!\x0D|\x0A!<br />\n!g;
    $str =~ s/\&gt;/>/g;
    $str =~ s/\&lt;/</g;
    $str =~ s/\&quot;/"/g;
    $str =~ s/\&amp;/\&/g;
    return $str;
}

sub getPost {
    my $filename  = shift @_;
    my $start     = shift @_ || $blosxom::datadir;
    my $extension = shift @_ || $blosxom::file_extension;

    if ( -e $filename ) {
        open POST, "$filename";
        my @post = <POST>;
        close POST;
        my %struct;
        $struct{'postid'}      = $filename;
        $struct{'dateCreated'} = File::stat::stat($filename)->mtime;
        my $title              = shift @post;
        $title                 =~ s!\x0D|\x0A!!g;
        $struct{'title'}       = $title;
        foreach (@post) {
            my $line = $_;
            if ( !$struct{'subject'} && $line =~ /^meta-tags: (.*)$/ ) {
                $line = $1;
                $line =~ s!\x0D|\x0A!!g;
                my @subjects = split(/\s*,\s*/, $line);
                $struct{'subject'} = \@subjects;
            } else {
                $struct{'description'} .= $line;
            }
        }
        my @cats;
        $filename =~ s/$start(\/.*)\/.*$extension$/$1/ or $filename = "/";
        push @cats, $filename;
        $struct{'categories'} = \@cats;
        return \%struct;
    }
    else {
        return 0;
    }
}

sub newPost {
    my $struct    = shift @_;
    my $start     = shift @_ || $blosxom::datadir;
    my $extension = shift @_ || $blosxom::file_extension;

    my $filename;

    if ( defined( $struct->{'postid'} ) ) {
        $filename = getFilenameFromPostId( $struct->{'postid'} );
    }
    else {
        $filename = lc( $struct->{'title'} );
        $filename =~ s/\W+/_/g;
        $filename =~ s/_+$//;
        $filename = getRandomFilename() unless $filename =~ m/[a-z]/;
        if ( $struct->{'categories'} ) {
            $filename =
              "$start/$struct->{'categories'}[0]/$filename.$extension";
        }
        else {
            $filename = "$start/$filename.$extension";
        }
    }

    chomp $struct->{'title'};
    chomp $struct->{'description'};

    unless ( -e $filename ) {
        open POST, ">$filename.old" or die "Can't Open File $filename: $!";
        print POST decode_html($struct->{'title'})."\n";
        print POST decode_html($struct->{'description'})."\n";
        close POST;
        my $files = chmod 0664, $filename;
    }
    return $filename;
}

sub getEntry {
    my $filename = shift;
    my $post = getPost( $filename );
    return unless $post;

    my $entry = XML::Atom::Server::Lite::Entry->new;
    my $extension = $blosxom::file_extension;

    $entry->title($post->{title});

    my $url = $blosxom::url.$post->{'postid'};
    $url =~ s/$extension$/htm/;
    $entry->link({
            type=>'text/html',
            rel=>'alternate',
            href=>$url,
    });

    $entry->content({
            mode=>'xml',
            type=>'xhtml',
            body=>$post->{description},
    });

    for my $cat (@{$post->{categories}}) {
        $entry->category({
                term=>$cat,
                label=>$cat,
        });
    }
    $entry->subject(@{$post->{subject}});
    return $entry;
}

sub handle_request {
    my $server = shift;
    my $method = $server->request_method;
    $server->authenticate or return;
    if ($method eq 'POST') {
        return $server->new_post;
    }
    $server->response_code(200);
    $server->response_content_type('application/x.atom+xml');

    my $start       = $blosxom::datadir;
    my $extension   = $blosxom::file_extension;
    my $url = $server->path_info;
    if ($url =~ /$blosxom::file_extension$/) {
         my $entry = getEntry( "$start/$url.old" );
        return $entry->as_xml;
    } else {
        my %posts;
        File::Find::find(
            sub {
                $File::Find::name =~ /$extension$/
                  ? $posts{$File::Find::name} =
                  File::stat::stat($File::Find::name)->mtime
                  : 0;
            },
            $start
        );
        my @postList = sort { $posts{$b} <=> $posts{$a} } keys %posts;
        if ( $#postList > $postsWanted ) {
            @postList = @postList[ 0 .. ( $postsWanted - 1 ) ];
        }
        my $feed = XML::Atom::Server::Lite::Feed->new;
        $feed->title($blosxom::blog_title);
        $feed->link({
            rel => 'self',
            type => 'application/atom+xml',
            title => $blosxom::blog_title,
            href => $server->uri,
        });
        $feed->link({
            rel => 'alternate',
            type => 'text/html',
            title => $blosxom::blog_title,
            href => $server->uri,
        });
        $feed->link({
            rel => 'service.post',
            type => 'application/x.atom+xml',
            title => $blosxom::blog_title,
            href => $server->uri,
        });
        for my $filename (@postList) {
            my $entry = getEntry( $filename );
            $feed->add_entry( $entry ) if $entry;
        }
        return $feed->as_xml;
    }
}

my %esc = (
    "\a" => "\\a",
    "\b" => "\\b",
    "\t" => "\\t",
    "\n" => "\\n",
    "\f" => "\\f",
    "\r" => "\\r",
    "\e" => "\\e",
);

sub qquote {
  local($_) = shift;
  s/([\\\"\@\$])/\\$1/g;
  my $bytes; { use bytes; $bytes = length }
  s/([^\x00-\x7f])/'\x{'.sprintf("%x",ord($1)).'}'/ge if $bytes > length;
  return qq("$_") unless
    /[^ !"\#\$%&'()*+,\-.\/0-9:;<=>?\@A-Z[\\\]^_`a-z{|}~]/;  # fast exit

  my $high = shift || "";
  s/([\a\b\t\n\f\r\e])/$esc{$1}/g;

  if (ord('^')==94)  { # ascii
    # no need for 3 digits in escape for these
    s/([\0-\037])(?!\d)/'\\'.sprintf('%o',ord($1))/eg;
    s/([\0-\037\177])/'\\'.sprintf('%03o',ord($1))/eg;
    # all but last branch below not supported --BEHAVIOR SUBJECT TO CHANGE--
    if ($high eq "iso8859") {
      s/([\200-\240])/'\\'.sprintf('%o',ord($1))/eg;
    } elsif ($high eq "utf8") {
#     use utf8;
#     $str =~ s/([^\040-\176])/sprintf "\\x{%04x}", ord($1)/ge;
    } elsif ($high eq "8bit") {
        # leave it as it is
    } else {
      s/([\200-\377])/'\\'.sprintf('%03o',ord($1))/eg;
      s/([^\040-\176])/sprintf "\\x{%04x}", ord($1)/ge;
    }
  }
  else { # ebcdic
      s{([^ !"\#\$%&'()*+,\-.\/0-9:;<=>?\@A-Z[\\\]^_`a-z{|}~])(?!\d)}
       {my $v = ord($1); '\\'.sprintf(($v <= 037 ? '%o' : '%03o'), $v)}eg;
      s{([^ !"\#\$%&'()*+,\-.\/0-9:;<=>?\@A-Z[\\\]^_`a-z{|}~])}
       {'\\'.sprintf('%03o',ord($1))}eg;
  }

  return qq("$_");
}

sub get_content_node {
    my $tree = shift;
    if ($tree->{children} && (@{$tree->{children}} eq 1)
            && $tree->{children}->[0]->{name} eq 'content') {
        $tree = $tree->{children}->[0];
        for my $item (@{$tree->{children}}) {
            if ($item->{name} eq 'div' && $item->{attributes}
                && $item->{attributes}->{xmlns} =~ 'http://www.w3.org/1999/xhtml') {
                $tree = $item;
                for my $nobr (@{$tree->{children}}) {
                    return $nobr if $nobr->{name} ne 'br' && $nobr->{type} eq 'tag';
                }
                last;
            }
        }
    }
    $tree;
}

sub to_xml {
    my $tree = shift;
    my $xml;
    if ($tree->{name}) {
        $xml .= "<".$tree->{name};
        for my $attr (keys %{$tree->{attributes}}) {
            $xml .= " $attr=".qquote($tree->{attributes}->{$attr});
        }
    } elsif ($tree->{content}) {
        $xml .= $tree->{content};
    }
    if ($tree->{children}) {
        $xml .= ">" if $tree->{name};
        foreach my $item (@{$tree->{children}}) {
            $xml .= to_xml($item);
        }
        $xml .= "</".$tree->{name}.">" if $tree->{name};
    } elsif ($tree->{name}) {
        $xml .= "/>";
    }
    $xml;
}
sub new_post {
    my $server = shift;
    my $entry = $server->atom_body or return;

    my $struct = {};

    my $url = $server->path_info || $entry->link;
    $url =~ s/htm$/$blosxom::file_extension/;
    $struct->{postid} = $url;
    $struct->{title} = $entry->title;
    my $tree_parser = XML::Parser::Lite::Tree::instance();
    my $tree = $tree_parser->parse( $entry->content->body );
    my $data = to_xml(get_content_node($tree));
    $struct->{description} = $data;
    $struct->{categories} = $entry->{category};

    my $filename = newPost( $struct );
    my $created = getEntry( $filename );
    $server->response_code(201);
    $server->response_content_type('application/x.atom+xml');
    $server->response_header(Location => $server->uri.$struct->{postid});
    return $created;
}

package main;
my $server = Blosxom::AtomPP->new;
$server->run;
厳密な「service.post」や「alternate」ではありませんが「XML::Atom::Server::Lite」の検証としてはこの程度で十分かなと思ってます。ポストも出来ます(認証はWSSE)。
Zoundry Ravenというブログツールでは動作を確認しました。
zoundry-raven

おそらくですが
  • 「XML::Atom::Server::Lite」を「XML::Atom::Server」
  • 「XML::Atom::Server::Lite::Feed」を「XML::Atom::Feed」に
  • 「XML::Atom::Server::Lite::Entry」を「XML::Atom::Entry」に
戻せば、そのまま動くかと思います。
なお、「XML::Atom::Server::Lite」はSOAPには対応していません。馬力のある方は適当に弄って下さい。

これでblosxomにXMLRPCとAtomのインタフェースが揃った事になるのかな?
Posted at by