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;
}

0 件のコメント: