私達の中には、キーボードとマウスによる入力を当然のものと思っている人がいます。つまりキーボードで文字を入力すると、その文字はウィンドウに表示され、一連の文字を入力して Return キーを押すと、ローカルあるいはネットワーク接続されたリモートで何らかのアクションが実行されます。キーボードやマウスを使った入力に、これ以上望むことが何かあるのでしょうか?しかし、キーボードやマウスがない場合や、キーボードやマウスを使用できない場合、そして 1 つのウィンドウでキー入力を行い、その入力によって別のデスクトップ上の別のウィンドウで何かをさせたい場合はどうすればよいのでしょう?あるいは、ウィンドウを作成してリサイズし、そのウィンドウ内でブラウザーを開いて、ある URL にナビゲートし、その Web ページ内のリンクを Tab キーで次々に移動し、最終的に 1 つのリンクをクリックするというすべての動作を、キーボードもマウスも使わずに音声認識ソフトウェアを利用して声で操作したいという場合はどうすればよいのでしょう?こうした動作のためにはキーボードとマウスのエミュレーションが必要になります。
システム・コールを利用して、これらすべてのタスクを実行することは確かに可能ですが、それではおそらく不便です。そうしようとすると、大部分の開発者が通常使用するレベルよりも下位レベルのコーディングが必要となるからです。音声認識ソフトウェアでは記録されている音声パターンを容易に利用できるように、他のライブラリーを利用することで、Linux
デスクトップのウィンドウ機能を利用できるようになります。そこに xdotool が登場します。
xdotool (「参考文献」のリンクを参照)
はウィンドウ環境への命令の送信を補助する関数のライブラリーです。xdotool
は入力されるストリングをリモート・ウィンドウに送信することや、そのウィンドウをリサイズすること、個々のキー入力やキー入力の組み合わせを送信すること、さらにはマウスの右ボタンをダブルクリックすることもできます。しかもリモート・アプリケーションは、それらが間接的に操作されていること (あるいは音声で間接的に操作されていること) を認識しません。xdotool の Web サイトには、make を使用してインストールするための具体的な説明があります。
xdotool コマンドの送信方法は単純です。構文としては、単純に xdotool の後にオプションと引数を続けます。
xdotool search --name 'mytest' |
この例では、xdotool は mytest というタイトルを持つウィンドウを検索し、それらのウィンドウの ID 番号を返します。この ID 番号を記録しておくと、後で使用することができます。何らかの基準を持たずにウィンドウを検索すると、意味のない数が延々と続く長いリストが作成される羽目になるので注意してください。
キーやキー入力の組み合わせに使用する名前に関するヒントは xdotool の Web サイトにあります。1 つの小さな落とし穴として、tab は直感的に思えますが、私のシステムでは Tab でないと動作しません。
ターミナル・ウィンドウから 1 度に 1 つずつ xdotool コマンドを発行することは可能ですが、xdotool
のインスタンスが終了するたびに内部変数が一部失われるため、1 度 xdotool を呼び出した後、いくつかのオプションと引数を連結した方がより効率的です。
おそらく、以上で説明した内容についての感覚をつかむ最も良い方法は、実際の例を見てみることです。
リスト 1 は、1 つのターミナル・ウィンドウ (work と呼ぶことにします) を使用して別のターミナル・ウィンドウ
(mytest) を開き、この mytest にキー入力を送信するテストを示しています。コマンドは PHP
のスクリプトで作成されていますが、これはあくまでも、何が起きているかがわかるようにプロセスの処理速度を遅くするためです。PHP 以外にも、Perl や Python、その他皆さんがお気に入りのスクリプト・エンジンを使用することができます。このスクリプトはいくつものキー入力を定義し、現在実行中のターミナル・ウィンドウに「Trying A (A を試しています)」と表示し (ここで、A はキーを表します)、そのキーを別のターミナル・ウィンドウにも送信します。アクティビティーは両方のターミナル・ウィンドウに同時に表示されます。コードの実行が速すぎて何が起きているかを理解できない場合には、定義されている WAIT 定数の値を大きくしてください。
リスト 1. 2 つのターミナル・ウィンドウを同時に使用する
<?php
// script to test xdotool functionality
define('WAIT',200000);
$charrarray = setca();
$execcmd = "xdotool search --name 'mytest'";
exec($execcmd,$out);
$windowid = $out[0];
if ($windowid) {
echo "Found window ID ".$windowid."\n";
} else {
exec('konsole -p LocalTabTitleFormat=mytest');
$execcmd = "xdotool search --name 'mytest'";
exec($execcmd,$out);
$windowid = $out[0];
// die("Cannot find the window\n");
}
// now activate the window
$execcmd = "xdotool windowactivate --sync $windowid";
exec($execcmd,$out);
// test keys
foreach ($charrarray as $k=>$char) {
echo "Trying $char \n";
send_key($k);
}
// functions
function send_string($string) {
$execcmd = "xdotool type $string";
exec($execcmd,$out);
usleep(WAIT);
}
function send_key($key) {
$execcmd = "xdotool key $key";
exec($execcmd,$out);
usleep(WAIT);
}
function send_return() {
$execcmd = "xdotool key Return";
exec($execcmd,$out);
usleep(WAIT);
}
function setca() {
$ca = array(
"Up" => 'up',
"Down" => 'down',
"ampersand" => '&',
"apostrophe" => '\'',
"Left" => 'L',
"Right" => 'R',
"asciicircum" => '^',
"asciitilde" => '~',
"asterisk" => '*',
"at" => '@',
"backslash" => '\\',
"bar" => '|',
"braceleft" => '{',
"braceright" => '}',
"bracketleft" => '[',
"bracketright" => ']',
"colon" => ':',
"comma" => ',',
"dollar" => '$',
"equal" => '=',
"exclam" => '!',
"grave" => '`',
"greater" => '>',
"less" => '<',
"minus" => '-',
"numbersign" => '#',
"parenleft" => '(',
"parenright" => ')',
"percent" => '%',
"period" => '.',
"plus" => '+',
"question" => '?',
"quotedbl" => '"',
"semicolon" => ';',
"space" => ' ',
"BackSpace" => 'bs',
"Tab" => '\t',
"underscore" => '_',
"slash" => '/',
"eacute" => 'eac',
"ccedilla" => 'cced',
"udiaeresis" => 'uum',
"idiaeresis" => 'ïdi',
"Home" => '<',
"End" => '>',
"Return" => '\n'
);
return $ca;
}
?>
|
このスクリプトでは、まずグローバル・スコープを持つ WAIT
定数を定義します。この定数によって関数の実行速度が遅くなり、何が起きているかがわかるようになります。続いて、テスト対象のキー入力を配列に追加します。もちろん、完全なリストはリスト
1 よりもはるかに長くなります。このサンプルには、多くの標準的な文字に加え、いくつか見慣れない文字の組み合わせが含まれています。次に xdotool は mytest というタイトルのウィンドウを検索します。見つからない場合には、PHP はウィンドウのタイトルを指定するパラメーターを持つ konsole 命令を実行し、そのウィンドウを作成します。
exec('konsole -p LocalTabTitleFormat=mytest');
|
新しいウィンドウを作成した後、xdotool による検索を再び行い、そのウィンドウの ID を番号として持つグローバル変数を設定します。こうすることで、その後のコマンドは確実に適切な宛先に送信されるようになります。ウィンドウが用意されると、スクリプトはそのウィンドウをアクティブにし、テスト対象である文字列の配列に対してループ処理を開始します。スクリプトは各ループで、特定の 1 文字を試しているという内容を作業コンソールに出力した後、その文字をターゲット・ウィンドウに表示します。
xdotool の引数とオプションの組み合わせが適切であることを確認できると、xdotool のチェーニング機能とスクリプト作成機能を利用することで、省略形式のメリットが得られるようになります。完全に診断を制御する必要があるけれども、何を使えばよいかがわからない場合、この PHP によるベースが役に立ちます。
リスト 1 はキーの名前をテストできるという意味で生産的ですが、インターネットから情報を取得できるようになると、さらに生産的になります。リスト 2 の例は、新しいウィンドウを開いて Lynx テキスト・ブラウザーのインスタンスを起動し、www.ibm.com という Web サイトにナビゲートします。
リスト 2. Lynx を自動化する
<?php
// script to test xdotool functionality
// first get the window to launch lynx
define('WAIT',200000);
$winname = 'mylynx';
$execcmd = "xdotool search --name $winname";
exec($execcmd,$out);
$windowid = $out[0];
if ($windowid) {
echo "Found window ID ".$windowid."\n";
} else {
exec("konsole -p LocalTabTitleFormat=$winname");
exec($execcmd,$out);
$windowid = $out[0];
// die("Cannot find the window\n");
}
// now activate the window
$execcmd = "xdotool windowactivate --sync $windowid";
exec($execcmd,$out);
$execcmd = "xdotool getactivewindow windowsize --sync 750 750";
exec($execcmd,$out);
// start sending stuff
send_string("lynx");
send_return(); // opens lynx browser
send_key("g"); // get URL prompt
send_string("www.ibm.com");
send_return(); // send a URL
function send_string($string) {
$execcmd = "xdotool type $string";
exec($execcmd,$out);
usleep(WAIT);
}
function send_key($key) {
$execcmd = "xdotool key $key";
exec($execcmd,$out);
usleep(WAIT);
}
function send_return() {
$execcmd = "xdotool key Return";
exec($execcmd,$out);
usleep(WAIT);
}
?>
|
リスト 2 のコードはリスト 1 と似ています。少し処理を遅らせるための待機期間を定義した後、(上記の mytest との混同を避けるために) mylynx
というウィンドウを検索し、そのウィンドウの ID を後で使用できるように保存し、そのウィンドウをアクティブにしてリサイズし、そのウィンドウの中で Lynx
のインスタンスを初期化しています。続いて、URL が次に来ることを Lynx に伝える g
のキー入力をそのウィンドウに送信し、その後に www.ibm.com というストリングと Return キーを続けて送信しています。これで www.ibm.com の Web サイトが開き、さらなるコマンド (Tab や
Return など) を受信できるようになり、この Web サイトをナビゲーションできるようになります。
なぜ自動化の対象とするアプリケーションとして Lynx を選んだのでしょう。他のブラウザーやアプリケーションを自動化しようとすると、自動化されたキー入力の一部が無視される場合があります。この問題の根源はクロスサイト・スクリプティングに対する防御にあります。Lynx は、例えば Mozilla Firefox に比べ、エミュレートされたキー入力を若干多く許容するようです。皆さんの好みは私とは異なるかもしれません。
すべてが動作するようになり、間接的なキー入力を使用した自動操作がいかに容易であるかを理解できると、他の機器から入力することはできないのだろうか、という考えが浮かんできます。それを実現する
1 つの方法が音声です。適切な学習を行った音声モデルを使用した音声認識プロセスは、言葉による命令を理解することができ、xdotool
を使用してコマンドを実行することができます。xdotool と VoxForge
音声モデルを使用した既製のアプリケーションが必要な場合には、Ubuntu に含まれている kiku
(詳細な情報へのリンクは「参考文献」を参照) について調べてみてください。
リスト 3 のサンプル・コードはダイアログ・マネージャーで xdotool を使用する方法を示しています。
リスト 3. ダイアログ・マッパーで xdotool を使用する
<?php
...
function process($major,$minor) {
global $globalcontext;
switch ($major) {
case 'CONTEXT':
switch ($minor) {
case 'SOFTWARE':
$globalcontext = $minor;
echo "Set global context to software\n";
break;
default:
echo "Defaulted out $minor\n";
break;
}
break;
case 'BROWSER':
$ooc = ($globalcontext == 'SOFTWARE') ? true : false ;
if ($ooc) {
switch ($minor) {
case 'OPEN':
echo "Open browser\n";
browser_open();
break;
case 'CLOSE':
echo "Close browser\n";
browser_close();
break;
case 'LOCATION':
echo "Go to URL\n";
browser_location();
break;
default:
echo "Defaulted out $minor\n";
break;
}
} else {
// OOC
echo "Recognized but out of context\n";
}
break;
default:
echo "Defaulted out $major\n";
break;
}
}
function browser_open() {
global $windowid;
$winname = 'mylynx';
$execcmd = "xdotool search --name $winname";
exec($execcmd,$out);
$windowid = $out[0];
if ($windowid) {
echo "Found window ID ".$windowid."\n";
} else {
exec("konsole -p LocalTabTitleFormat=$winname");
$execcmd = "xdotool search --name $winname";
exec($execcmd,$out);
$windowid = $out[0];
echo "Using windowid $windowid";
// die("Cannot find the window\n");
}
// now activate the window
$execcmd = "xdotool windowactivate --sync $windowid";
exec($execcmd,$out);
$execcmd = "xdotool getactivewindow windowsize --sync 800 800";
exec($execcmd,$out);
// start sending stuff
send_string("lynx");
send_return(); // opens lynx browser
}
...
?>
|
リスト 3 は 2 つの関数を示しています。最初の関数は process()
であり、この関数は 2 つの引数を想定しています。これらの引数は音声認識プロセスから返されるストリングです。これらのストリングを取得するプロセスの詳細は VoxForge
または Sphinx のチュートリアルを参照してください (「参考文献」のリンクを参照)。この場合、想定されるストリングは CONTEXT SOFTWARE と BROWSER OPEN という命令が含まれる文法から抽出されます。
このプロセスを追い、CONTEXT SOFTWARE
と読み上げられたことを音声認識プロセスが認識した場合に何が起こるかを考えてみてください。メジャー・ストリングは CONTEXT であり、マイナー・ストリングは SOFTWARE
です。Process() 関数はコンテキストが格納されているグローバル変数へのアクセスを宣言し、そして switch
文の中でコンテキスト変数を SOFWARE
に設定します。この変数はグローバルです。そのため、後でメジャー・ストリングとマイナー・ストリングのセットとしての BROWSER
OPEN を音声認識プロセスが認識すると switch 文でそれを処理しますが、switch
文では最初にそのコンテキストが適切かどうかを検証します。なぜコンテキストを使用するかというと、音声認識プロセスから不適切な結果が返された場合、その不適切な結果を破棄するためです。例えば、コンテキストが
SOCCER の場合、SOCCER のコンテキストでブラウザーを開くことは適切でないと考えられる場合には、ブラウザーは開きません。
コンテキストが適切な場合には、BROWSER OPEN コマンドによって browser_open()
関数が呼び出されます。この browser_open() 関数のコードがリスト 2 に使用されたものと基本的に同じであることは、すぐに理解できると思います。
コンテキスト管理が複雑になればなるほど、そのコンテキスト管理構造を忠実に再現するルールとして他の場所で定義されたルールに従う switch
文を作り出す手法が必要になります。これらのルールを定義する方法には何通りもの方法があります。1 つの方法は、Speech Recognition Grammar
Specification と Semantic Interpretation for Speech Recognition による構造を使用する方法です
(詳細については「参考文献」のリンクを参照)。それよりも少し単純な方法として、必要なコード・フラグメント (xdotool 命令を含む) を XML 構造に格納する方法があります。
XML を使用してコンテキストとコード・フラグメントを格納する
リスト 4 の XML
ファイルには、架空のダイアログ・マネージャーのビルディング・ブロックの詳細が記述されています。フラットで編集可能な XML
ファイルのメリットとして、すべてのコンテキストと関数を同じファイルの中に表現することができます。コンテキストと関数はダイアログ・マネージャーの複雑な switch
文のコードから分離されているため、コンテキスト構造を容易に見たり編集したりすることができます。このデータには、マウス・カーソルがどこにあっても左クリックを実行する 1 つの
xdotool 命令が含まれています。このコマンドはすべてのコンテキストで有効であるため、このコマンドには ctxt 属性がありません。
リスト 4. XML ストア
<?xml version="1.0" encoding="UTF-8"?>
<snips>
<context>
<func>click_left</func>
<func ctxt="software">browser_open</func>
<func ctxt="software">browser_location</func>
<func ctxt="software">browser_close</func>
<func ctxt="hardware">cpu_temperature</func>
<func ctxt="hardware">fan_speed</func>
</context>
<snip fn="click_left">
<![CDATA[
function click_left() {
exec('xdotool click 1');
}
]]>
</snip>
<snip fn="cpu_temperature">
<![CDATA[
function cpu_temperature() {
$g = 0;
}
]]>
</snip>
<snip fn="fan_speed">
<![CDATA[
function fan_speed() {
$g = 1;
}
]]>
</snip>
<snip fn="browser_open">
<![CDATA[
function browser_open() {
$f = 0;
}
]]>
</snip>
<snip fn="browser_location">
<![CDATA[
function browser_location() {
$f = 1;
}
]]>
</snip>
<snip fn="browser_close">
<![CDATA[
function browser_close() {
$f = 2;
}
]]>
</snip>
</snips>
|
このコードでは、ルート要素は snips です。この要素には以下の 2 つのタイプの子があります。
- コンテキスト情報を持つ context 要素
- コード・フラグメントを含む、いくつかの
snip要素
context 要素には func 要素が含まれており、各 func
要素には、要素の値としての関数の名前と、その関数が有効となるコンテキストを含む ctxt
属性があります。つまり、コンテキストが software に設定された場合には、関数 fan_speed は有効になりません。しかし関数 click_left にはコンテキストがないため、関数 click_left はどこででも有効です。コード・スニペットは CDATA セグメントに格納されます。こうすることで、XML パーサーは必ず CDATA セクションの構文解析をスキップし、内部に含まれるコードとは無関係に CDATA セクションを受け付けます。
ここまで来れば、あとはダイアログ・データを拡張して独自のスクリプトにするためのスクリプトを作成するだけです。それがリスト 5 の PHP コードです。
リスト 5. ダイアログ・マネージャー生成プログラム
<?php
// pull DM structure from an xml file
$xml = simplexml_load_file('snipstor.xml');
// get the contexts
// generate the main switch
echo "function process(\$major,\$minor) {\n";
echo "global \$globalcontext;\n";
echo " switch (\$major) {\n";
$majtmp = "";
$mintmp = "";
foreach ($xml->context->func as $mjmn) {
list($major,$minor) = explode("_",$mjmn);
//echo "$major,$minor\n";
if ($major != $majtmp) {
if ($majtmp != "") echo " default:
echo \"Failed \$minor\";
break;
}\n } else {
echo 'OOC';\n }\n";
echo " case '$major':\n";
if ($minor != $mintmp) {
$test = ($mjmn['ctxt']) ? "\$globalcontext == '".$mjmn['ctxt']."'" : 'true' ;
echo " if ($test) {\n";
echo " switch (\$minor) {\n";
$mintmp = $minor;
}
echo " case '$minor':
$mjmn();
break;\n";
$majtmp = $major;
} else {
echo " case '$minor':
break;\n";
}
}
echo " default:
echo \"Failed \$minor\";
break;
}\n } else {
echo 'OOC';\n }\n";
echo " default:
echo \"Failed \$major\";
break;
}\n";
echo "}\n";
// generate the code snippets
echo "// functions\n";
foreach ($xml->snip as $snipfn) {
echo trim($snipfn);
echo "\n";
}
?>
|
リスト 5 のコードでは、ダイアログ・マネージャーに追加することができる PHP
コードを作成しています。まず、snipstor.xml というファイルに格納されたリスト 4
のダイアログ・マネージャーの構造を SimpleXML 変数の中に読み込みます。次に、context
要素の中にある関数名を読み取り、これらの名前からメジャー・コンポーネントとマイナー・コンポーネントを抽出し、それらのコンポーネントを使用して、ダイアログ・マネージャーのフローを制御する
switch 文を作成します。このスクリプトはコードを作成する中で、そのコマンドが有効となるコンテキストを (ctxt
属性から) 検索します。そして switch case 文に適用される可能性があるどんなコンテキストでも扱えるように if
条件文を挿入します。コンテキストがある場合には、そのコンテキストは実行時に評価される式として挿入されます。コンテキストがない場合には、CDATA に含まれている文が必ずプログラムによって実行されるように、単純に true を挿入します (つまりそのコマンドはすべてのコンテキストで有効です)。最後に、このスクリプトは switch case 文に必要なコード・ライブラリーを出力します。
リスト 4 に含まれるデータに対してリスト 5 のコードを実行した結果をリスト 6 に示します。
リスト 6. 結果として生成されたダイアログ・マネージャーの抜粋
function process($major,$minor) {
global $globalcontext;
switch ($major) {
case 'click':
if (true) {
switch ($minor) {
case 'left':
click_left();
break;
default:
echo "Failed $minor";
break;
}
} else {
echo 'OOC';
}
case 'browser':
if ($globalcontext == 'software') {
switch ($minor) {
case 'open':
browser_open();
break;
case 'location':
break;
case 'close':
break;
default:
echo "Failed $minor";
break;
}
} else {
echo 'OOC';
}
case 'cpu':
if ($globalcontext == 'hardware') {
switch ($minor) {
case 'temperature':
cpu_temperature();
break;
default:
echo "Failed $minor";
break;
}
} else {
echo 'OOC';
}
case 'fan':
if ($globalcontext == 'hardware') {
switch ($minor) {
case 'speed':
fan_speed();
break;
default:
echo "Failed $minor";
break;
}
} else {
echo 'OOC';
}
default:
echo "Failed $major";
break;
}
}
// functions
function click_left() {
exec('xdotool click 1');
}
function cpu_temperature() {
$g = 0;
}
function fan_speed() {
$g = 1;
}
function browser_open() {
$f = 0;
}
function browser_location() {
$f = 1;
}
function browser_close() {
$f = 2;
}
|
この出力は、出発点であったリスト 3 と似ています。CDATA
の実際の内容は有効なコードですが、単純にするために省略してあります。OOC とは単に Out of Context (コンテキスト外)
を省略しただけです。このメッセージが表示されるのは、音声認識プロセスが有効な音声を認識したものの、その音声は snipstor.xml
ファイルの構造に従うと意味をなさない場合です。この例をもっと意味のあるものにするには、browser_open() 関数の CDATA セクションをリスト 3 の browser_open() 関数のコードで置き換えます。
この記事からわかるように、xdotool
はウィンドウ・システムを呼び出すための便利なライブラリーです。音声認識プロセスと組み合わせると、ダイアログ・マネージャーによる制御の下で、音声を使って xdotool
コマンドを実行することができます。そして最後に、コンテキストが複雑な場合、ダイアログ・マネージャーは非常に複雑で扱いにくいものになる可能性があるため、xdotool 命令を含むコード・フラグメントを便宜上 XML の CDATA セクションに格納して使用することで、一貫性のある効果的な方法でダイアログ・マネージャーを生成することができます。
学ぶために
- VoxForge と
Carnegie Mellon の CMU Sphinx: 音声認識モデルを構築する方法について学んでください。
- 「ママ、見て!キーボードがないよ!一定の文法を使用した音声入力および応答」(Colin
Beckingham 著、developerWorks、2010年11月): 文法を使用した音声認識についての記事を読んでください。
- 「Querying a database using open
source voice control software」(Colin Beckingham 著、linux.com、2008年5月): 音声/スピーチで動作するオープンソース・ソフトウェアの概要を学んでください。
- 「XML
でデータを処理する」(Chris Herborth 著、developerWorks、2010年1月): CDATA 要素について、また XML ファイルと併せてマークアップ付きデータを配布する場合に CDATA 要素を効果的に使用する方法を学んでください。
- 著者の
Colin Beckingham が developerWorks に寄稿した他の記事 (2009年3月から現在まで): XML、音声認識、XHTML、PHP、SMIL、その他の技術が解説されています。
- New to XML: XML を学ぶために必要なリソースが豊富に用意されています。
- developerWorks の XML ゾーン:
DTD、スキーマ、XSLT など、XML の領域でのスキルを磨くためのリソースが豊富に用意されています。XML の技術文書一覧には、広範な話題を網羅した技術記事やヒント、チュートリアル、技術標準、IBM Redbooks が豊富に用意されています。
- IBM XML certification: XML および関連技術において IBM 認定技術者になる方法を参照してください。
- developerWorks の
Technical events and webcasts: 最新情報を入手してください。
- developerWorks on Twitter: 今すぐ Twitter に参加して developerWorks のツイートをフォローしてください。
- developerWorks podcasts: ソフトウェア開発者のための興味深いインタビューや議論を聞いてください。
- developerWorks on-demand
demos: 初心者のための製品インストール方法やセットアップのデモから、上級開発者のための高度な機能に至るまで、多様な話題が解説されています。
製品や技術を入手するために
xdotool: xdotool をダウンロードし、xdotool の設定方法や、xdotool を使用してキーボード入力やマウス・アクティビティーをシミュレートする方法、ウィンドウの移動方法やリサイズ方法などを学んでください。kiku: kiku 音声認識プロセスとダイアログ・マネージャーについて学ぶとともに、音声認識プロセスを使用してオペレーティング・システムを制御する方法についても学んでください。- Lynx ソース・ディストリビューション・ディレクトリー: Lynx Web ブラウザーをダウンロードし、ユーザー・ガイドとヘルプ・ページで Lynx Web ブラウザーについて学んでください。
- IBM 製品の評価版: IBM
製品の評価版をダウンロードするか、あるいは IBM SOA
Sandbox のオンライン試用版で、DB2、Lotus、Rational、Tivoli、および WebSphere が提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。
議論するために
- XML
ゾーンのディスカッション・フォーラム: これらのフォーラムでは XML に関連する議論が行われています。
- developerWorks コミュニティー: 開発者向けのブログ、フォーラム、グループ、ウィキなどを利用しながら、他の developerWorks ユーザーとやり取りしてください。