2010年6月17日木曜日

boost::spirit v2.3 ちららつきる

ちょいワケありで、簡単な定義を書いて wsdl を生成するコードをこさえました。
しかし、いざ wsdl を生成してみると、php の SoapServer や SoapClient で、動作させるのが辛そうな感じ。Delphi で、wsdl からクライアント用のインタフェイスを生成するのは、うまくいきました。肝心の ruby はどうか?というと、soap4r があり、いけそうにも思うのですが…。サーバとして常時稼働させるのには実績の面で心配がありまして、悩みまくった挙句、結論としては Soap は使わない…という事に…。
ここは、単純に Delphi + TIdHTTP の組み合わせで、リクエストを送って、レスポンスをゴリゴリ処理するという方式を採用する事にしました…。

 うーん…新しい spirit 書きやすいようで難しいです…。qi より lex 使った方が幸せなのかも?

def_grammar.hpp

#include <iostream>
#include <string>
#include <vector>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

namespace client {
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phoenix = boost::phoenix;

struct xsd_datatypes_ : qi::symbols<char,std::string> {
xsd_datatypes_() {
add
( "string", "xsd:string" )
( "bool", "xsd:boolean" )
( "float", "xsd:float" )
( "double", "xsd:double" )
( "short", "xsd:short" )
( "ushort", "xsd:unsignedShort")
( "int", "xsd:integer" )
( "uint", "xsd:unsignedInt" )
( "long", "xsd:long" )
( "ulong", "xsd:ulong" )
( "date", "xsd:date" )
("dateTime", "xsd:dateTime" )
( "binary", "xsd:base64Binary" )
;
}
} xsd_datatypes;

struct inout_ : qi::symbols<char,int> {
inout_() {
add
( "in", 0 )
( "out", 1 )
;
}
} inout;

struct wsdl_arg {
int inout_;
std::string type_;
std::string name_;
};
}

BOOST_FUSION_ADAPT_STRUCT(
client::wsdl_arg,
// メンバ変数の並び順ではなく、構文解析の順番に合わせる事が大切
(std::string, type_ )
( int, inout_ )
(std::string, name_ )
)


namespace client {

#define ASCII_NAME (ascii::char_("a-zA-Z") >> *ascii::char_("a-zA-Z0-9"))

template <typename Iterator>
struct wsdl_arg_parser : qi::grammar<Iterator, wsdl_arg(), ascii::space_type> {
wsdl_arg_parser()
: wsdl_arg_parser::base_type(expression)
{
ioval %= inout;
tname %= xsd_datatypes;
sname %= ASCII_NAME;
expression = tname >> '[' >> ioval >> ']' >> sname;
// 間に lexeme[ qi::blank ] とか、やりたいけど、やり方がわかんね(全部エラー)orz
}
qi::rule<Iterator, int(), ascii::space_type> ioval;
qi::rule<Iterator, std::string(), ascii::space_type> sname, tname;
qi::rule<Iterator, wsdl_arg(), ascii::space_type> expression;
};

struct wsdl_func {
std::string name_;
std::vector<wsdl_arg> args_;
};
}

BOOST_FUSION_ADAPT_STRUCT(
client::wsdl_func,
(std::string, name_ )
(std::vector<client::wsdl_arg>, args_ )
)

namespace client {

template <typename Iterator>
struct wsdl_func_parser : qi::grammar<Iterator, wsdl_func(), ascii::space_type> {

#define SETUP_AT( idx ) (at_c<idx>(qi::_val) = qi::_1)
#define PUSH_BACK_AT( idx ) push_back( at_c<idx>(qi::_val), qi::_1 )
#define PUSH_BACK_ARG arg[ PUSH_BACK_AT(1) ]

wsdl_func_parser()
: wsdl_func_parser::base_type(expression, "expression")
{
using phoenix::push_back;
using phoenix::at_c;

using phoenix::construct;
using phoenix::val;

fname %= ASCII_NAME;
// SETUP_AT(0) しなくて入りそうなもんだけど、ここでは明示的に指定しないと
// いけない。client::wsdl_arg_parser は、何故指定しなくても大丈夫なのか?
// どんな魔術を使っているのだろうと…気になる
expression = qi::lit("function") >> fname[ SETUP_AT(0) ] >> '('
>> ( PUSH_BACK_ARG >> *(',' >> PUSH_BACK_ARG) )
// spirit の qi::eol の扱いがよくわからん(>_<)
// と思ったが、ascii::space_type が スペース・タブ・改行なのだろう・・・
>> ')' >> ';'; // >> qi::eol;

// エラーハンドリングを入れてみたけど動作しない・・・(;_;)
expression.name( "expression" );
fname.name( "fname" );

qi::on_error<qi::fail>(
expression
, std::cerr
<< val("Error! Expecting ")
<< qi::_4 // what failed?
<< val(" here: \"")
<< construct<std::string>(qi::_3, qi::_2) // iterators to error-pos, end
<< val("\"")
<< std::endl
);

}
qi::rule<Iterator, std::string(), ascii::space_type> fname;
qi::rule<Iterator, wsdl_func(), ascii::space_type> expression;
wsdl_arg_parser<Iterator> arg;
};

template <typename Iterator>
struct wsdl_def_grammar : qi::grammar<Iterator, std::vector<wsdl_func>(), ascii::space_type> {
wsdl_def_grammar()
: wsdl_def_grammar::base_type( expression )
{
using phoenix::push_back;

expression = +func[ push_back( qi::_val, qi::_1 ) ];
}
qi::rule<Iterator, std::vector<wsdl_func>(), ascii::space_type> expression;
wsdl_func_parser<Iterator> func;
};

}



wsdl_data.inc

//--------------------------------------------------------------
// PART: definitions
// def_header % 'ServiceName'
const char xml_header[] = "<?xml version=\"1.0\"?>\n";
const char def_header[] =
"<definitions \n" \
" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n" \
" xmlns:http=\"http://schemas.xmlsoap.org/wsdl/http/\"\n" \
" xmlns:mime=\"http://schemas.xmlsoap.org/wsdl/mime/\"\n" \
" xmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\"\n" \
" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"\n" \
" xmlns:ns1=\"urn:%1%\"\n" \
" xmlns:wsdl=\"http://schemas.xmlsoap.org/wsdl/\"\n" \
" targetNamespace=\"urn:%1%\"\n" \
" xmlns=\"http://schemas.xmlsoap.org/wsdl/\"\n" \
">\n";
// types
// message ...
// porttype ...
// operation1 ...
// binding
// operation2 ...
// service
const char def_footer[] = "</definitions>";

//--------------------------------------------------------------
// PART: types
// types_part % 'ServiceName'
const char types_part[] =
"<types>\n" \
" <xsd:schema targetNamespace=\"urn:%1%\" attributeFormDefault=\"qualified\" elementFormDefault=\"qualified\">\n" \
" <xsd:import namespace=\"http://schemas.xmlsoap.org/soap/encoding/\"/>\n" \
" </xsd:schema>\n" \
"</types>\n";

//--------------------------------------------------------------
// PART: message
// message_header % 'FunctionName' % 'In' | 'Out'
const char message_header[] = "<message name=\"%1%%2%\">\n";
// message_content % 'ArgName' % 'xsd:base64Binary' | 'xsd:string' | 'xsd:int' | 'xsd:unsignedInt' | ...
// ...
const char message_content[] = " <part name=\"%1%\" type=\"%2%\"/>\n";
const char message_footer[] = "</message>\n";

//--------------------------------------------------------------
// PART: portType
// portType_header % 'FunctionName'
const char portType_header[] = "<portType name=\"%1%Soap\">\n";
// operation ...
const char portType_footer[] = "</portType>\n";

//--------------------------------------------------------------
// PART: operation1
// operation_portType % 'FunctionName'
const char operation_portType[] =
"<operation name=\"%1%\">\n" \
" <input message=\"ns1:%1%In\"/>\n" \
" <output message=\"ns1:%1%Out\"/>\n" \
"</operation>\n";

//--------------------------------------------------------------
// PART: binding
// binding_header % 'ServiceName'
const char binding_header[] =
"<binding name=\"%1%Soap\" type=\"ns1:%1%Soap\">\n" \
"<soap:binding transport=\"http://schemas.xmlsoap.org/soap/http\" style=\"rpc\"/>\n";
// operation2 ....
const char binding_footer[] = "</binding>\n";

//--------------------------------------------------------------
// PART: operation1
// operation_binding % 'ServiceName' % 'FunctionName'
const char operation_binding[] =
"<operation name=\"%2%\">\n" \
" <soap:operation soapAction=\"#%2%\" style=\"rpc\"/>\n" \
" <input>\n" \
" <soap:body use=\"encoded\" namespace=\"urn:%1%\" encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"/>\n" \
" </input>\n" \
" <output>\n" \
" <soap:body use=\"encoded\" namespace=\"urn:%1%\" encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"/>\n" \
" </output>\n" \
"</operation>\n";

//--------------------------------------------------------------
// PART: service
// service_part % 'ServiceName' % 'ServiceURL'
// ServiceURL sample => 'http://hoge:8080/fuga.php'
const char service_part[] =
"<service name=\"%1%\">\n" \
" <port name=\"%1%Soap\" binding=\"ns1:%1%Soap\">\n" \
" <soap:address location=\"%2%\"/>\n" \
" </port>\n" \
"</service>\n";



def2wsdl.cpp

#include <iostream>
#include <string>
#include <vector>
#include <fstream>

#include <boost/format.hpp>
#include <boost/foreach.hpp>

#include "def_grammar.hpp"
#include "wsdl_data.inc"

void show_usage() {
std::cout << "def2wsdl [def filename] [output filename] [service name] [url]" << std::endl;
std::cout << "======================================" << std::endl;
std::cout << " >def2wsdl mysvcdef.txt mysvc.wsdl mysvc http://localhost/mysvc/mysvc.wsdl" << std::endl;
std::cout << "--------------------------------------" << std::endl;
std::cout << " type list " << std::endl;
std::cout << " string => xsd:string" << std::endl;
std::cout << " bool => xsd:boolean" << std::endl;
std::cout << " float => xsd:float" << std::endl;
std::cout << " double => xsd:double" << std::endl;
std::cout << " short => xsd:short" << std::endl;
std::cout << " ushort => xsd:unsignedShort" << std::endl;
std::cout << " int => xsd:integer" << std::endl;
std::cout << " uint => xsd:unsignedInt" << std::endl;
std::cout << " long => xsd:long" << std::endl;
std::cout << " ulong => xsd:ulong" << std::endl;
std::cout << " date => xsd:date" << std::endl;
std::cout << " dateTime => xsd:dateTime" << std::endl;
std::cout << " binary => xsd:base64Binary" << std::endl;
std::cout << "--------------------------------------" << std::endl;
std::cout << " def file sampe " << std::endl;
std::cout << "--------------------------------------" << std::endl;
std::cout << " function foo ( " << std::endl;
std::cout << " int [in] arg1, " << std::endl;
std::cout << " string [out] arg2 " << std::endl;
std::cout << " );" << std::endl;
std::cout << " function bar ( " << std::endl;
std::cout << " string [out] arg2 " << std::endl;
std::cout << " );" << std::endl;
}

namespace {
int arg_is_in( const client::wsdl_arg& arg ) {
return (arg.inout_ == 0) ? 1 : 0;
}
}

void output_wsdl(
std::ostream& os,
const std::string& svcname,
const std::string& svcurl,
const std::vector<client::wsdl_func>& funcs
) {
os << xml_header;
os << boost::format( def_header ) % svcname;
// PART: type
os << boost::format( types_part ) % svcname;
// PART: message
BOOST_FOREACH( const client::wsdl_func& func, funcs ) {
os << boost::format( message_header ) % func.name_ % "In";
// sort して range かけた方がスマートか?
// ま、arg の指定順序も壊せないので filter が欲しいところなのかも
BOOST_FOREACH( const client::wsdl_arg& arg, func.args_ ) {
if( !arg.inout_ ) {
os << boost::format( message_content ) % arg.name_ % arg.type_;
}
}
os << message_footer;
os << boost::format( message_header ) % func.name_ % "Out";
BOOST_FOREACH( const client::wsdl_arg& arg, func.args_ ) {
if( arg.inout_ ) {
os << boost::format( message_content ) % arg.name_ % arg.type_;
}
}
os << message_footer;
}
// PART: portType
os << boost::format( portType_header ) % svcname;
BOOST_FOREACH( const client::wsdl_func& func, funcs ) {
// PART: operation1
os << boost::format( operation_portType ) % func.name_;
}
os << portType_footer;
// PART: binding
os << boost::format( binding_header ) % svcname;
BOOST_FOREACH( const client::wsdl_func& func, funcs ) {
// PART: operation2
os << boost::format( operation_binding ) % svcname % func.name_;
}
os << binding_footer;
os << boost::format( service_part ) % svcname % svcurl;
os << def_footer;
}


int main( int argc, char* argv[] ) {
if( argc != 5 ) {
show_usage();
return 1;
}

std::ifstream ifs( argv[1], std::ios::in );
if( !ifs ) {
std::cerr << "fail to open: " << argv[1] << std::endl;
return 2;
}

std::string source_code;

ifs.unsetf(std::ios::skipws); // No white space skipping!
std::copy(
std::istream_iterator<char>(ifs),
std::istream_iterator<char>(),
std::back_inserter(source_code)
);

typedef client::wsdl_def_grammar<std::string::const_iterator> wsdl_parser;

wsdl_parser g;
std::vector<client::wsdl_func> fncs;

bool res = phrase_parse(
source_code.begin(), source_code.end(),
g, boost::spirit::ascii::space, fncs
);


if( res ) {
std::cout << "Success to parse!" << std::endl;
std::ofstream ofs( argv[2], std::ios::out | std::ios::trunc );
output_wsdl( ofs, argv[3], argv[4], fncs );
} else {
std::cerr << "Fail to parse: " << argv[1] << std::endl;
}

return 0;
}

2010年6月16日水曜日

c++ コード中の __ ???

boost_1_43_0/boost/spirit/home/qi/numeric/int.hpp を見ていて



namespace boost { namespace spirit { namespace qi
{
using spirit::short_;
using spirit::short__type;
using spirit::int_;
using spirit::int__type;
using spirit::long_;
using spirit::long__type;



なんて、記述を見つけた…。ダブル・アンダースコアって、リーガルだったっけか???
よくわかんなくなってきた…。実質、リーガルと見なした方が良いのか?

久々にspamassassin 日本語パッチ用ルール更新

ここんところ、SPF をまじめに設定しているスパムメールが増えてきたので、久しぶりにルールの更新を行いました。spamassassin 日本語パッチ版用のルール
 

2010年6月15日火曜日

ATLで自動生成されたuriのリンク切れで脱力

Soap のための wsdl 定義をいろいろ弄っていて、ふと気がついた。以下は、ATLが自動生成したWSDLコードの先頭である。

<?xml version="1.0"?>
<!-- ATL Server generated Web Service Description -->
<definitions
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:s0="urn:HogeService"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:atls="http://tempuri.org/vc/atl/server/"
targetNamespace="urn:HogeService"
xmlns="http://schemas.xmlsoap.org/wsdl/"
>

おんやー?xmlns:atls="http://tempuri.org/vc/atl/server/" なんじゃこりゃ?
自分のモジュールでは使ってないし、リンクが切れている…。
xmlns におけるリンク切れって、どうなんでしょ?必要になるまで、評価が先延ばしされるのであれば問題無いのだが…。
一応、http://tempuri.org/ ってのは、生きてるのねん…。なんなんだよ…。

2010年6月11日金曜日

jqModal with ajax and drag

ネタ元:[jQuery] jqDnR and jqModal - draggable area not working when using ajax

ま、ネタというよりも、俺もハマったんですけどネ…。

 jqModal のサイトに、ダイアログをドラッグするには、jqDnR を使わないとダメなように書いてあるんで、みんな、ついつい jqDnR を使って実装しようとするんですわ。ところが、ネタ元にあるように


$('#hoge').jqm({ajax:'/fuga',target:$('#content'),trigger:'#btn'}).jqDrag('.jqDrag');

なんてコードを書いて、jqDrag クラス要素がドラッグ可能になった所で ajax により要素が書き換えられて、ドラッグできない~、なんて事になるんですわ。

 そもそも jQuery には、draggable という plugin があるので、こちらを使った方がストレスもないんだよね?

$('#hoge').jqm({ajax:'/fuga',target:$('#content'),trigger:'#btn'}).draggable({opacity:0.8,handle:'#hoge-title'});

こんなんで十分です。

2010年6月10日木曜日

record_select の google chrome 対応

 規格がどうだ・・・とか、厳密な話をしていても、微妙な違いは起こるものです。自分の開発環境がノートパソコンだからかどうかは、わかりませんが、少なくとも Firefox と Google chrome では、javascript 内における onkeypress イベントで、ハンドリングされないキーが存在します。
 あと、意図せずに、ESC キーを押してしまった場合、フォーカスを当てなおさないと選択リストが表示されません。これらを考慮に入れた上で、record_select.js を修正してみました。


...

/**
* all the behavior to respond to a text field as a search box
*/
_respond_to_text_field: function(text_field) {
// attach the events to start this party
text_field.observe('focus', this.open.bind(this));

// the autosearch event - needs to happen slightly late (keyup is later than keypress)
text_field.observe('keyup', function() {
if (!this.is_open()) return;
this.container.down('.text-input').value = text_field.value;
}.bind(this));

// keyboard navigation, if available
if (this.onkeypress) {
text_field.observe('keypress', this.onkeypress.bind(this));
}
if (this.onkeydown) {
text_field.observe('keydown', this.onkeydown.bind(this));
}
},

_use_iframe_mask: function() {
return this.container.insertAdjacentHTML ? true : false;
}
});

/**
* Adds keyboard navigation to RecordSelect objects
*/
Object.extend(RecordSelect.Abstract.prototype, {
current: null,

/**
* keyboard navigation - where to intercept the keys is up to the concrete class
* some browser does not handle KEY_XXXX. so separate to keydown event
*/
onkeypress: function(ev) {
var elem;
switch (ev.keyCode) {
case Event.KEY_SPACE:
case Event.KEY_RETURN:
if (this.current) this.current.down('a').onclick();
break;
case Event.KEY_RIGHT:
if( !this.is_open() ) this.open();
elem = this.container.down('li.pagination.next');
if (elem) elem.down('a').onclick();
break;
case Event.KEY_LEFT:
if( !this.is_open() ) this.open();
elem = this.container.down('li.pagination.previous');
if (elem) elem.down('a').onclick();
break;
case Event.KEY_ESC:
break;
case Event.KEY_TAB:
return;
default:
if( !this.is_open() ) this.open();
return;
}
Event.stop(ev); // so "enter" doesn't submit the form, among other things(?)
},
/**
* keyboard navigation - where to intercept the keys is up to the concrete class
*/
onkeydown: function(ev) {
var elem;
switch (ev.keyCode) {
var elem;
switch (ev.keyCode) {
case Event.KEY_UP:
if( !this.is_open() ) this.open();
if (this.current && this.current.up('.record-select')) elem = this.current.previous();
if (!elem) elem = this.container.getElementsBySelector('ol li.record').last();
this.highlight(elem);
break;
case Event.KEY_DOWN:
if( !this.is_open() ) this.open();
if (this.current && this.current.up('.record-select')) elem = this.current.next();
if (!elem) elem = this.container.getElementsBySelector('ol li.record').first();
this.highlight(elem);
break;
case Event.KEY_RIGHT:
if( !this.is_open() ) this.open();
elem = this.container.down('li.pagination.next');
if (elem) elem.down('a').onclick();
break;
case Event.KEY_LEFT:
if( !this.is_open() ) this.open();
elem = this.container.down('li.pagination.previous');
if (elem) elem.down('a').onclick();
break;
case Event.KEY_TAB:
case Event.KEY_ESC:
this.close();
break;
default:
return;
}
},

/**
* moves the highlight to a new object
*/
highlight: function(obj) {
if (this.current) this.current.removeClassName('current');
this.current = $(obj);
obj.addClassName('current');
}
});


追記:修正部分が足りてませんでした。追加しました。
2010/06/11 追記:keypress イベントにもリスト再表示のコードを挿入しました。
2010/07/29 追記:tab イベント時の処理がまずかったので、修正。元のコードの方がすっきりしているので、この改造は本末転倒だったかも…

ControllerHelper の汚染に対応

 何が起こっているのか、よくわかんないのだが、helper は、モジュールで作成されており、コントローラからコントローラを利用すると、モジュールによる汚染があるのでは無いか?と推察しています。
 どういう現象が起こるかと言うと、Employee と Customer に name というフィールドが存在している場合に、CustomersHelper 内で activescaffold の Field override を使った場合に、意図せず Employee の name まで挙動が波及してしまう事があります。


def name_column(record)
"<a href='mailto:" + record[:email_address].to_s + "'>" + record[:name].to_s + "</a>";
end


 Oh!No! Employee の name まで波及してるやんけーーーーー!!!

こんな時は、慌てず

def name_column(record)
if record.is_a? Customer
"<a href='mailto:" + record[:email_address].to_s + "'>" + record[:name].to_s + "</a>";
else
if record[:name] != nil
record[:name].to_s
else
''
end
end
end


です。うーん・・・どうなんでしょ・・・これ・・・