はじめてのReVIEW〜InDesignへの取り込み

たいへん遅ればせながらReVIEWを導入しました。著者や編集者が簡易に編集可能なReVIEW記法*1を基に、XHTMLEPUBXMLTeX、PDFなどを自動生成するフレームワークです。
昨年発行された『電子書籍で生き残る技術−紙との差、規格の差を乗り越える−』を読んでからReVIEWに大きな関心を持ちました。先月、海上忍さんのコラム「1つのソースでEPUBとPDFを生成できる「ReVIEW」を試す」を拝見して、ヤラねば! と思いつつずっと宿題だったんです。先日たまたま「ReVIEW の使い方 - A Day in Serenity @ Kenji」を読んでようやく重い腰をあげることとなりました。<重すぎるだろ...
でだ。「ReVIEWクイックスタートガイド」と前述のリンクなどを参考にしてちょろっとやってみたら、簡単すぎて拍子抜けしました。もう、書くことない。


(インストール上で注意点があるとすれば)Mac OS Xの環境ですと、rubyは1.8系なので、gemがそもそも入っていないはずです*2TeXやPDFを生成するためには、TeX環境が必要です。TeXやPDFを生成しないなら、TeX環境はいりません。
海上忍さんのコラム中で紹介されている達人出版会高橋征義さんがご用意くださっているサンプルを使って、実際にEPUBを作ってみます。

$ review-epubmaker config.yml

これだけで、ディレクトリ内にEPUBが生成されました。これをAdobe Digital Editionsで見ると、こんな感じです。


自前で作ってみた

あんまりあっけないので、簡単なものをひとつ自前で作ってみました。ディレクトリ階層はこんな感じです。

gingatetsudono_yoru.zip 直

ファイル・フォルダ名 説明
_cover.html EPUB用のカバーHTML
base.css CSSEPUBやHTMLに付加するスタイルシート
ch01.re〜ch09.re 章分けしたReVIEWファイル(ソースコンテンツ)
CHAPS 章構成を記述したファイル(名前は固定)
config.yml EPUBやPDFを生成する時の設定YAML
images 画像用ディレクトリ(名前は固定)
 ch01-fig01.jpg〜ch09-fig09.jpg 画像ファイル(ファイル名には章ファイルと同じプレフィックスが必要)
 cover.jpg カバー用画像
sty PDFやTeX生成時のマクロ
 jumoline.sty TeXマクロ
 samplemacro.sty TeXマクロ

EPUBに書き出してみると、こんな感じです。gingatetsudono_yoru.epub 直

iPhoneiBooksで見ると、こんな感じ。*3
   


ReVIEWフォーマット自体は、いくら簡易フォーマットとはいえ人間がタイプするものですから、当然タイプミスが含まれてしまいます。HTMLでさくっと確認できればより正確ですね*4

$ review-compile --target html ch02.re > ch02.html


確認するだけならこれでも悪くないんですが、デフォルトでは見出しにセクション番号が付いてしまいます。番号が不要ならば「--level=0」を指定しましょう。また、特定のスタイルシートを適用したいなら、「--stylesheet=hoge.css」を付けます。他のオプションの説明は「--help」で表示されます。

$ review-compile --target html --stylesheet=base.css --level=0 ch02.re > ch02.html

とすればこうなりました。

InDesignへの取り込み

ReVIEWファイルからは、XMLを生成できます。XMLを生成すれは、InDesignへの取り込みはごく簡単なことでしょう。

$ review-compile --target idgxml --level=0 ch01.re > ch01.xml

とすると、xmlが生成されます。

<?xml version="1.0" encoding="UTF-8"?>
<doc xmlns:aid="http://ns.adobe.com/AdobeInDesign/4.0/"><title aid:pstyle="h2">一 午後の授業</title><?dtp level="2" section="一 午後の授業"?>
<p>「ではみなさん、さういふふうに川だと云はれたり、乳の流れたあとだと云はれたりしてゐた、このぼんやりと白いものが何かご承知ですか。」</p>
<p> 先生は、黒板に吊した大きな黒い星座の圖の、上から下へ白くけぶつた銀河帶のやうなところを指しながら、みんなに問ひをかけました。</p>
 ...(中略)
<p> そして教室中はしばらく机の蓋をあけたりしめたり本を重ねたりする音がいつぱいでしたが、まもなくみんなはきちんと立つて禮をすると教室を出ました。</p>
<img>
<Image href="file://images/ch01-fig01.jpg"  />
</img>
</doc>

ここで注目したいのは、このXMLInDesignに流し込むことを前提としていることです。それはxmlnsを見てもわかりますし、InDesign特有の改行の扱いが最適化されています。余計なインデントもありません。実際にInDesignに流し込んでみると、こんな感じになります。

たまたま縦書きなので、写真も回転してくれちゃっています。これはXML読み込みの仕様、後処理はスクリプトで。


この流し込み例はXMLを利用したものですが、ReVIEWフォーマットからInDesignタグに直接マッピングしてもいいかもしれません。事実、ぼくはReVIEWフォーマットの前身にあたるASCIIのEWBフォーマットで何度も原稿をいただいて、InDesignタグ(あるいはXPressタグ)に変換していました。

まとめ

ReVIEWフォーマットを利用する利点を3つ上げます。

  • コンテンツをテキストベースで保持・管理できる
  • 書き出されたタグそのものがきれい
  • フリー!

コンテンツの保持者は著者であり、出版社の編集者です。書籍の改版時や部分的な転用時、あるいは電子出版への展開などに、コンテンツの持ち主が扱いやすいテキストであることはとても重要なことです。コンテンツをInDesignドキュメントとして持っていたらどうでしょう? ほとんどの著者や編集者ではInDesignを扱えません。組版の専業者に別途ご相談ということになります。たいてい、InDesignドキュメントはコンテンツと組み指定が分ち難く一体になっています。電子出版のために、それを分離するには多大な手間と時間とコストがかかっています。
また、InDesignが書出すEPUBやHTMLは人が読める形になっていません。通常、構造化もされていません。InDesign CS5.5からは新たにアーティクルやタグなどの概念が持ち込まれましたが、それはオッカムの剃刀です。InDesignドキュメントでコンテンツを保持するのは、InDesignのアプリケーションやバージョンに縛られる結果になります。このへんのことは以前「InDesignは電子書籍の中間ファイルたりうるか?」に書かせていただきました。現在、InDesignのアップグレードは1年と短くなっており、完全にイノベーションのジレンマに陥っています。InDesignはコンテンツのハブではなく、商業印刷物のための入れ物デバイスだと考えます。
そして、ReVIEWはオープンソースフレームワークです。誰もが無償でこの環境を享受できます。よくわかんない権利関係に悩まされることもありません。操作同様、シンプルに選択できる選択肢です。


最後に、ReVIEW開発者のみなさまと、使用方法などをレポートしてくださったみなさまに感謝いたします。ぼくの初めてのサンプルはいろいろ間違いがあるかもしれませんが、ご指摘いただければ修正させていただきます。

*1:ReVIEWフォーマットは ASCIIのEWBを基本としながら、一部に RD や各種 Wiki の文法をとりいれて簡素化したものです。詳しくはhttps://github.com/kmuto/review/blob/master/doc/format.rdoc を参照

*2:ぼくは現在、ruby 1.9.2p136を使っているので、デフォルト環境ってものがよくわかっていないんですが... 1.9系ならふつーにgem install reviewだけでいけちゃいます

*3:ちょっと書き方が悪いせいかiBooksでは章の最初にゴミが出てしまったので、Sigilで保存し直しました。

*4:実運用上では、とても重要なことです。所詮、人間がタイプするものなどハナから間違いが存在してあたりまえです。正しい記法でなければ、正しく処理できません。ほら、hatena記法にも「確認する」ホタンが付いているでしょ

TSVからInDesignのXML流し込み

昨日のエントリ「TSV(タブ区切りテキスト)からXMLを簡易生成」を実際にInDesign上で使ってみます。
CSVTSVを指定して、内部的にXMLに変換し、ノードごとにInDesignXML流し込みを行います。動画で見るとこんな感じ:

とりあえず、CS4でもCS5でも動作しました。テストしてみたい方はテストキットを用意しました。
data_tsv2xml.zip 直

////////////////////////////////////////////エラー終了
function my_error(mess) {
	if (mess !== "") {alert (mess)}
	exit();
}

////////////////////////////////////////////ファイル・フォルダ選択ダイアログ。ファイルオブジェクトを返す。
function chooseF(my_prompt, my_kind) {
	var my_path;
	if (my_kind === "Folder") {
		my_path = Folder.selectDialog (my_prompt);
	} else {
		var my_regex = new RegExp('\\' + my_kind + '$');
		my_path =  File.openDialog (my_prompt, function(file){return(file.name.match(my_regex) || file instanceof Folder) ? true : false}, false );
	}
	return my_path
}

////////////////////////////////////////////ファイルの内容を読み込んで返す 
function read_file(my_read_file_path) {
	var my_file_obj = new File(my_read_file_path);
	if (!(my_file_obj.exists)) {my_error("ファイルがありません\n" + my_read_file_path)};
	if(my_file_obj.open("r")) {
		var tmp_str = my_file_obj.read();
		my_file_obj.close();
	} else {
		my_error("ファイルが開けません\n" + my_read_file_path);
	}
	 tmp_str = tmp_str.replace(/[\r\n]+$/, '');//最後の行末の改行を削除
	return tmp_str;
}

////////////////////////////////////////////データをファイルに書き込む 。書き込んだファイルオブジェクトを返す
function write_file(my_write_file_path, my_data) {
	var my_file_obj = new File(my_write_file_path);
	my_file_obj.encoding = "UTF-8";//★この行がないとShift-JISで書き出される
	//if (!(my_file_obj.exists)) {myerror("ファイルがありません\n" + my_write_file_path)};
	if(my_file_obj.open("w")) {
		my_file_obj.write(my_data);
		my_file_obj.close();
		return my_file_obj;
	} else {
		my_error("ファイルが開けません\n" + my_write_file_path);
	}
}

////////////////////////////////////////////TSVから簡易XMLを生成 
function tsv2xml(my_tsv) {
	//XMLのノード名とアイテム名
	var my_nodeName = "record";//ノード名
	var my_imteName = "item"//item名(追番がつきます)
	
	//XMLの生成
	var my_data = my_tsv.split("\n");//文字列を行ごとに分割
	var my_xml = new XML("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n</root>");//XMLのrootオブジェクトの生成
	for (var i in my_data) {//行ごと
		my_xml.appendChild(<{my_nodeName}></{my_nodeName}>);//ノードの作成
		var tmp_line = my_data[i].replace(/[\r\n]+$/, '');//行末改行の削除
		var tmp_items = tmp_line.split("\t");//tabで分割
		for (var ii in tmp_items) {//カラムごとの処理
			my_xml[my_nodeName][i].appendChild(<{my_imteName}{ii}>{tmp_items[ii]}</{my_imteName}{ii}>);
		}
	}
	return my_xml;
}

////////////////////////////////////////////XMLコンテンツの修正 
function modify_xml(my_xml, my_ext) {
	for (var i in my_xml.record) {
		my_xml.record[i].item2[0] = my_xml.record[i].item2[0].toString() + "円";//item2の文字列に「円」を加える
		my_xml.record[i].appendChild(<item0img />);//画像用の空要素をひとつ作る
		var tmp_str = my_xml.record[i].item0[0].toString();//item0の文字列を取り出しておく
		my_xml.record[i].item0img[0].@href = "file:///" +  tmp_str + my_ext;//画像のフルパスを生成して属性値にする
	}
	return my_xml;
}

////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////メイン 
function main(){
var my_filepath = chooseF("TSV(タブ区切りテキスト)ファイルを選んでください", ".tsv");
var my_template_indd = chooseF("ひな形のInDesignファイルを選んでください", ".indd");
var my_img_folder = chooseF("画像フォルダを選んでください", "Folder");//

var my_data = read_file(my_filepath);//ファイルの読み込み
var my_xml = tsv2xml(my_data);//TSVからXMLへ変換
my_xml = modify_xml(my_xml, ".psd");//XMLコンテンツの修正

//以下InDesign内の処理
app.open(my_template_indd);//ひな形のドキュメントを開く
for (var i in my_xml.record) {
	var my_doc = app.documents[0];
	var tmp_xml_path = write_file('' + my_img_folder + "/tmp.xml", my_xml.record[i].toXMLString());//レコードをファイルに書き込み##ああ、importXML()はなんでXMLを直接読まないかね?
	my_doc.importXML(tmp_xml_path);
	var tmp_save_path = new File('' + my_template_indd.parent + '/' + my_xml.record[i].item0[0].toString() + '.indd');
	my_doc.save(tmp_save_path);//別名で保存
}
my_doc.close();//ドキュメントを閉じる
File('' + my_img_folder + "/tmp.xml").remove();//tmp.xmlを削除
}
main();

まあ、これだけだったら、データ結合でもいいんだが...<みもふたもない^^

TSV(タブ区切りテキスト)からXMLを簡易生成

TSVからXMLを生成するアプリケーションはたくさんありますけれど、JavaScript内で完結させるために書いてみました。メモ。
こんなTSVがあったとします。

JavaScript内からこのファイルを読み込んで、内部的にXMLオブジェクトを生成します(エラー処理の関係でInDesignで使用するのを前提にしています)。

////////////////////////////////////////////エラー終了
function my_error(mess) {
	if (mess !== "") {alert (mess)}
	exit();
}

////////////////////////////////////////////ファイル・フォルダ選択ダイアログ。パス文字列を返す。
function chooseF(my_prompt, my_kind) {
	var my_path = '';
	if (my_kind === "Folder") {
		my_path = my_path + Folder.selectDialog (my_prompt);
	} else {
		var my_regex = new RegExp('\\' + my_kind + '$');
		my_path = my_path + File.openDialog (my_prompt, function(file){return(file.name.match(my_regex) || file instanceof Folder) ? true : false}, false );
	}
	return my_path
}

////////////////////////////////////////////ファイルの内容を読み込んで返す 
function read_file(my_read_file_path) {
	var my_file_obj = new File(my_read_file_path);
	if (!(my_file_obj.exists)) {myerror("ファイルがありません\n" + my_read_file_path)};
	if(my_file_obj.open("r")) {
		var tmp_str = my_file_obj.read();
		my_file_obj.close();
	} else {
		myerror("ファイルが開けません\n" + my_read_file_path);
	}
	 tmp_str = tmp_str.replace(/[\r\n]+$/, '');//最後の行末の改行を削除
	return tmp_str;
}

////////////////////////////////////////////TSVから簡易XMLを生成 
function tsv2xml(my_tsv) {
	//XMLのノード名とアイテム名
	var my_nodeName = "record";//ノード名
	var my_imteName = "item"//item名(追番がつきます)
	
	//XMLの生成
	var my_data = my_tsv.split("\n");//文字列を行ごとに分割
	var my_xml = new XML("<root>\n</root>");//XMLのrootオブジェクトの生成
	for (var i in my_data) {//行ごと
		my_xml.appendChild(<{my_nodeName}></{my_nodeName}>);//ノードの作成
		var tmp_line = my_data[i].replace(/[\r\n]+$/, '');//行末改行の削除
		var tmp_items = tmp_line.split("\t");//tabで分割
		for (var ii in tmp_items) {//カラムごとの処理
			my_xml[my_nodeName][i].appendChild(<{my_imteName}{ii}>{tmp_items[ii]}</{my_imteName}{ii}>);
		}
	}
	return my_xml;
}


////////////////////////////////////////////メイン 
var my_filepath = chooseF("TSV(タブ区切りテキスト)ファイルを選んでください", ".tsv");
var my_data = read_file(my_filepath);//ファイルの読み込み
var my_xml = tsv2xml(my_data);//TSVからXMLへ変換

my_xml.toXMLString();

XMLはこんな感じになっています。あとはお好きに...

ファイルを書き出すときはFile.encodingを指定しないとShift-JISになる

下記のようなXMLがあったとします。

このXMLJavaScriptで読み込んで、ごにょごにょして新しいXMLファイルに書き出そうと思いました。元ファイルはXML宣言も付いてるし、何も考えずに書き出しました。そしたらShift-JISになってんの。

で、これをそのままInDesignのドキュメントに喰わそうとするとエラーになります。XML宣言でencoding指定のないXMLはISO10646と解釈されるからじゃないかな? たぶん。っていうか、InDesignはどこまでS-JIS大好きなんだよ、Shift-JIS爆発しろ!
てなわけで、ファイルを書き出す時はちゃんとFile.encoding指定をしないと、無用な失望を味わったりします。
XMLに限らず通常のテキストでも同じですね*1XML世界でもJavaScript世界でもUnicodeなんで、つい自分が日本人であることを忘れてしまいます(それはない)。ご興味のある方はこんな感じでテストしてみてください。

////////////////////////////////////////////ファイルの内容を読み込んで返す 
function read_file(my_file) {
	var tmp_str = "";
	if (my_file.reflect.name == "String") {my_file = new File(my_file)};//ファイルパス文字列だった
	if (fh = my_file.open("r")) {//ファイルを読み込みモードで開く
		tmp_str = my_file.read();
		my_file.close();
	}
	return tmp_str;
}

////////////////////////////////////////////データをファイルに書き込む 。書き込んだファイルオブジェクトを返す
function write_file(my_write_file_path, my_data) {
	var my_file_obj = new File(my_write_file_path);
	my_file_obj.encoding = "UTF-8";//★この行がないとShift-JISで書き出される
	//if (!(my_file_obj.exists)) {myerror("ファイルがありません\n" + my_write_file_path)};
	if(my_file_obj.open("w")) {
		my_file_obj.write(my_data);
		my_file_obj.close();
		return my_file_obj;
	} else {
		myerror("ファイルが開けません\n" + my_write_file_path);
	}
}

var my_file = File.openDialog("XMLファイルを指定してください");
var my_folder = my_file.parent;
var tmp_str = read_file(my_file);//ファイルの読み込み
var my_xml = new XML(tmp_str);//XMLオブジェクトの生成
//このへんでごにょごにょする
write_file('' + my_folder + '/new.xml', my_xml.toXMLString());//ファイル書き込み

*1:だけどInDesignはいまだにUTF-8のテキストが読めません

選択したテキストをタグ付けする

InDesign上でXMLタグを付ける意味*1がなんだかいまひとつよくわかっていないんですけれど^^ 勉強部屋に書いたのでメモ。よくわかっていないので、storyじゃなくてもいいのか、とか、重複していても不都合はないか、とか、まるで検証していません。
選択したテキストにxmlタグ - InD-Board

var my_doc = app.activeDocument;
var my_textFrame = my_doc.selection[0].parentTextFrames[0];
var my_txt = my_doc.selection[0];

var my_root = my_doc.xmlElements[0];//Root要素

var my_textFrame_element = my_textFrame.associatedXMLElement;
if (my_textFrame_element === null) {
	my_textFrame_element = my_root.xmlElements.add("textFrame");
	my_textFrame_element.markup(my_textFrame);
}

var my_txt_element = my_txt.associatedXMLElements[1];
my_txt_element = my_textFrame_element.xmlElements.add("item");
my_txt_element.markup(my_txt);

*1:たくさんあるのならなんらかのシステマチックな方法が必要だし、ちょっとならタグパネルからやってもたいして手間は変わらないし...

特定のXMLElementから関連するpageitemオブジェクトを得る

特定のXMLElementから関連するpageitemオブジェクトを得たいのだけれど、XMLElementは必ずしもPageItemと関連しているとは限らない。逆にPageItemにはassociatedXMLElementプロパティがあるので、関連づけられたXMLElementオブジェクトを知ることができる。
ここではドキュメント中のallPageItemsについて、associatedXMLElementを持っていれば、IDをキーにしてハッシュとした。

//ドキュメントのすべてのpageitemでXMLElementを持つものを収集してハッシュで返す
function collectPageItem() {
	var my_collection = new Array();
	var my_doc = app.activeDocument;
	for (var i = 0; i < my_doc.allPageItems.length; i++) {
		var tmp_key = my_doc.allPageItems[i].associatedXMLElement;
		if (tmp_key != null) {my_collection["id" + tmp_key.id] = my_doc.allPageItems[i];}
	}
	return my_collection;
}

//XMLElementからpageitemオブジェクトを返す
function getPageItem(my_element, my_collection) {
	var tmp_key = "id" + my_element.id;
	return my_collection[tmp_key];
}

var my_doc = app.activeDocument;
var my_root = my_doc.xmlElements.item("records");//root要素
var my_element = my_root.xmlElements[1];//ターゲットelement

var my_collection = collectPageItem();
var my_obj = getPageItem(my_element, my_collection);
my_obj.select();//選択してみる

InDesign上で日本語校正支援「proofreader」

なにをするスクリプトか?

InDesign上のテキストの日本語校正支援をします。「ら抜き言葉」や「誤変換」「固有名詞」などの間違いを注釈で指摘します。エンジンには「Yahoo! 校正支援Webサービス」を利用しています。校正精度はYahoo!のエンジンに依存します。
簡単な説明は下記のムービーをご覧ください。

高解像度版movie(2.7MB)はこちら。

動作環境

このスクリプトが正常に動作する環境は以下の通りです。Windows環境でも動作する可能性がありますが、動作確認はしていません。テスト歓迎。

  • Mac Pro Quad 3GHz
  • Mac OS X 10.5.5
  • InDesign CS3_J(5.0.4)
  • インターネット接続環境
  • Yahoo!アプリケーションID(無料)が必要です。下記サイトから取得できます。

校正支援Webサービス

ダウンロード

http://www.seuzo.jp/st/scripts_InDesignCS3/index.html#proofreader
こちらからのパッケージ版では、Yahoo!アプリケーションIDは必要ありません。

インストール

スクリプト本体(proofreader.jsx)を
/Applications/Adobe InDesign CS3/Scripts/Scripts Panel/
または
~/Library/Preferences/Adobe InDesign/Version 5.0-J/Scripts/Scripts Panel/
にコピーしてください。エイリアスを入れておくだけでもかまいません。スクリプトパレットから使用します。

使用方法

  1. 「ウインドウ」メニューから「スクリプティング」ー「スクリプト」を選択し、スクリプトパレットを出します。
  2. 校正したいテキストを選択します。
  3. スクリプトパレットから、スクリプト「proofreader.jsx」をダブルクリックします。
    • 初めて起動する場合のみ、Yahoo!アプリケーションIDを入力するダイアログが開きます。取得したIDを入力してください。IDはスクリプトと同じ階層にある「yahooID.txt」に記録され、次回の起動以降に使用されます。
  4. 設定ダイアログが出現します。校正すべき項目をチェックしてください。
  5. 処理が終わるとダイアログが出現して、何カ所校正があったかレポートします。
  6. 注釈パレットで校正内容を確認してください。
  • 【Tips】コードの18行目付近にある下記の行にYahoo!アプリケーションIDを直接書き込むと、ファイルアクセスがなくなり、若干早く処理できるようになります。
YAHOO_ID = "ここに入力するのだ";
  • 校正にまちがいを見つけたら...

Yahoo! JAPANへ校正結果の間違いを報告する

既知の不具合、またはToDo、あるいは仕様

  • 校正後の単語に対して、2重のチェックはできません。たとえば、「出鱈目」という単語は「当て字」でもあり、「鱈」が表外漢字でもあります。しかし、指摘されるのは最初の「当て字」のみです。必要ならば、訂正後にもう一度チェックをしなおしてみてください。
  • Yahoo!サーバーがエラーを返したとき、その内容を精査して見合った警告をしたいけれど、どんな時にどんなエラーを返すのか、テスト不足。<-なんだかよくわからないエラーが出た人は、とりあえずコメントを付けてください。
  • 1行が数千字とかの文章だと、おそらくエラーになります。やったことないですけれど。
  • こういうのって本来はInCopy上で動くと都合がいいのかもしれない。だけど、InCopy持っていないしなぁ。短期雇用していただけるなら、開発してもOKです。

免責事項

  • 変換品質は、「Yahoo! 校正支援Webサービス」に依存します。正確な校正品質を保証するものではありません。
  • このツールを使用する上でデータの破損などのあらゆる不具合・不利益については一切の責任を負いかねますのでご了解ください。
  • このツールはすべてのMacintoshMac OS上で動作をするという確認をとっていませんし、事実上出来ません。したがって、動作を保証するものではありません。

履歴

  • 2008-10-24 ver.0.1 とりあえず版
  • 2008-10-25 ver.0.2 ネットワークに繋がっていなかったとき、エラーで終了するようにした。最初に設定ダイアログを出して、指摘範囲を指定できるようにした。

ソースコード

ブログの仕様やブラウザ環境などにより、「\」が正しくないことがあります。エディタなどで調整してください。

/*
proofreader.jsx
(c)2008 www.seuzo.jp
InDesign内で文字校正をします。
Yahoo!デベロッパーネットワークのアプリケーションIDが必要です。
http://developer.yahoo.co.jp/jlp/KouseiService/V1/kousei.html

2008-10-24	ver.0.1	とりあえず
2008-10-25	ver.0.2	ネットワークに繋がっていなかったとき、エラーで終了するようにした。最初に設定ダイアログを出して、指摘範囲を指定できるようにした。
*/

#target "InDesign-5.0"

////////////////////////////////////////////設定
const YAHOO_DOMAIN = "jlp.yahooapis.jp";
const YAHOO_PATH = "/KouseiService/V1/kousei?";
//Yahoo!アプリケーションIDの取得
YAHOO_ID = "";//ここにアプリケーションIDを直接入力すれば、ファイルを探さない。
const DISPLAY_DIALOG = true;//設定ダイアログを表示するかどうか


////////////////////////////////////////////エラー処理 
function myerror(mess) { 
  if (arguments.length > 0) { alert(mess); }
  exit();
}

////////////////////////////////////////////ファイルを読み込んで文字列を返す
function read_file(my_file) {
	var tmp_str = "";
	if (my_file.reflect.name == "String") {my_file = new File(my_file)};//ファイルパス文字列だった
	if (fh = my_file.open("r")) {//ファイルを読み込みモードで開く
		tmp_str = my_file.read();
		my_file.close();
	}
	return tmp_str;
}

////////////////////////////////////////////データをファイルに書き込む 
function write_file(my_write_file_path, my_data) {
	var my_write_file_path;
	var my_file_obj = new File(my_write_file_path);
	//if (!(my_file_obj.exists)) {myerror("ファイルがありません\n" + my_write_file_path)};
	if(my_file_obj.open("w")) {
		my_file_obj.write(my_data);
		my_file_obj.close();
	} else {
		myerror("ファイルが開けません\n" + my_write_file_path);
	}
}

////////////////////////////////////////////yahooID設定の読み書き
function get_yahoo_ID() {
	var yahooID_file = app.activeScript.parent + "/yahooID.txt";
	var yahoo_ID = read_file(yahooID_file);
	if ((yahoo_ID == null) || (yahoo_ID == "")) {
		yahoo_ID = prompt("Yahoo!アプリケーションIDを入力してください。\n持っていない場合は下記URLから取得してください(無料)\nhttp://developer.yahoo.co.jp/jlp/KouseiService/V1/kousei.html", "");
		if ((yahoo_ID == null) || (yahoo_ID == "")) {myerror("キャンセルしました")}
		write_file(yahooID_file, yahoo_ID);
	}
	return yahoo_ID
}

////////////////ダイアログ
function show_dialog(){
	var my_dialog = app.dialogs.add({name:"日本語校正支援:proofreader", canCancel:true});
	with(my_dialog) {
		with(dialogColumns.add()) {
			// プロンプト
			staticTexts.add({staticLabel:"選択行の校正を実行して、注釈を添付します。"});
			with (borderPanels.add()) {
				with(dialogRows.add()){staticTexts.add({staticLabel:"▼レベル1 - 基本:表記の間違いや不適切な表現に関する指摘"});}
				with(dialogRows.add()){var check_01 = checkboxControls.add({staticLabel:"誤字・誤変換     例)人事異同→人事異動", checkedState:true});}
				with(dialogRows.add()){var check_02 = checkboxControls.add({staticLabel:"言葉の誤用      例)煙に巻く→けむに巻く", checkedState:true});}
				with(dialogRows.add()){var check_03 = checkboxControls.add({staticLabel:"使用注意       例)外人墓地→外国人墓地", checkedState:true});}
				with(dialogRows.add()){var check_04 = checkboxControls.add({staticLabel:"不快語        例)がんをつける→にらむ", checkedState:true});}
				with(dialogRows.add()){var check_05 = checkboxControls.add({staticLabel:"機種依存・拡張文字  例)○付き数字、一部の旧字体など", checkedState:true});}
				with(dialogRows.add()){var check_06 = checkboxControls.add({staticLabel:"外国地名表記の間違い 例)モルジブ→モルディブ", checkedState:true});}
				with(dialogRows.add()){var check_07 = checkboxControls.add({staticLabel:"固有名詞表記の間違い 例)ヤフーブログ→Yahoo!ブログ", checkedState:true});}
				with(dialogRows.add()){var check_08 = checkboxControls.add({staticLabel:"人名表記の間違い   例)ベートーヴェン→ベートーベン", checkedState:true});}
				with(dialogRows.add()){var check_09 = checkboxControls.add({staticLabel:"ら抜き言葉      例)食べれる→食べられる", checkedState:true});}
			}
			with (borderPanels.add()) {
				with(dialogRows.add()){staticTexts.add({staticLabel:"▼レベル2 - 難読:わかりやすい表記にするための指摘"});}
				with(dialogRows.add()){var check_10 = checkboxControls.add({staticLabel:"当て字        例)出鱈目、振り仮名", checkedState:true});}
				with(dialogRows.add()){var check_11 = checkboxControls.add({staticLabel:"表外漢字あり     例)灯籠→灯●", checkedState:true});}
				with(dialogRows.add()){var check_12 = checkboxControls.add({staticLabel:"用字         例)曖昧→あいまい", checkedState:true});}
				with(dialogRows.add()){var check_13 = checkboxControls.add({staticLabel:"商標など用語言い換え 例)セロテープ→セロハンテープ", checkedState:true});}
			}
			with (borderPanels.add()) {
				with(dialogRows.add()){staticTexts.add({staticLabel:"▼レベル3 - 品質:文章をよりよくするための指摘"});}
				with(dialogRows.add()){var check_14 = checkboxControls.add({staticLabel:"二重否定       例)聞かなくはない", checkedState:true});}
				with(dialogRows.add()){var check_15 = checkboxControls.add({staticLabel:"助詞不足の可能性あり 例)学校行く", checkedState:true});}
				with(dialogRows.add()){var check_16 = checkboxControls.add({staticLabel:"冗長表現       例)ことができます", checkedState:true});}
				with(dialogRows.add()){var check_17 = checkboxControls.add({staticLabel:"略語         例)ADSL→非対称デジタル加入者線(ADSL)", checkedState:true});}
			}
			with (borderPanels.add()) {
				with(dialogRows.add()){staticTexts.add({staticLabel:"           (c)2008 市川せうぞー http://www.seuzo.jp/"});}
			}
		}
	}


	if (my_dialog.show() == true) {
		check_01 = check_01.checkedState;
		check_02 = check_02.checkedState;
		check_03 = check_03.checkedState;
		check_04 = check_04.checkedState;
		check_05 = check_05.checkedState;
		check_06 = check_06.checkedState;
		check_07 = check_07.checkedState;
		check_08 = check_08.checkedState;
		check_09 = check_09.checkedState;
		check_10 = check_10.checkedState;
		check_11 = check_11.checkedState;
		check_12 = check_12.checkedState;
		check_13 = check_13.checkedState;
		check_14 = check_14.checkedState;
		check_15 = check_15.checkedState;
		check_16 = check_16.checkedState;
		check_17 = check_17.checkedState;
		//正常にダイアログを片付ける
		my_dialog.destroy();
		return  [false, check_01, check_02, check_03, check_04, check_05, check_06, check_07, check_08, check_09, check_10, check_11, check_12, check_13, check_14, check_15, check_16, check_17];
	} else {
		// ユーザが「キャンセル」をクリックしたので、メモリからダイアログボックスを削除
		my_dialog.destroy();
		myerror();
	}
}

////////////////////////////////////////////yahooへのリクエストと返事
function get_yahoo_respons(post_str) {
	var post_str;
	var reply = "";//サーバーからの返事
	var conn = new Socket;
	if (conn.open ( YAHOO_DOMAIN + ':80', 'UTF-8' ) ) {
		conn.write ('GET ' + YAHOO_PATH +'appid=' + YAHOO_ID + NO_FILTER + '&sentence=' + post_str + " HTTP/1.0\n"
			+ 'Host: ' + YAHOO_DOMAIN + "\n"
			+ 'User-Agent: ' + 'InDesign/5.0.3 ' +'(Macintosh; U; Intel Mac OS X 10_5_5; ja-jp)' + "\n"
            + "\n");
		reply = conn.read(999999);
        conn.close();
	} else {
		myerror("インターネットに接続されていません。");
	}
	return reply;
}



////////////////////////////////////////////実行
//InDesignで選択しているもののチェック
if (app.documents.length == 0) {myerror("ドキュメントが開かれていません")}
var my_doc = app.activeDocument;
if (my_doc.selection.length == 0) {myerror("テキストを選択してください")}
var my_selection = my_doc.selection[0];
var my_class =my_selection.constructor.name;
my_class = "Text, TextColumn, Story, Paragraph, Line, Word, Character, TextStyleRange".match(my_class);
if (my_class == null) {myerror("テキストを選択してください")}
var my_paragraphs = my_selection.paragraphs;//選択している段落

if (YAHOO_ID == "") {YAHOO_ID = get_yahoo_ID()}//Yahoo!アプリケーションIDの読み込み

//ダイアログ処理
NO_FILTER = "";//指摘除外フィルタ(指摘番号をコンマで区切って指定。詳細は、http://developer.yahoo.co.jp/jlp/KouseiService/V1/kousei.html)
if (DISPLAY_DIALOG) {
	var ans_dialog = show_dialog();
	var no_filter_count = 1;//除外数のカウント
	for (var i = 1; i < ans_dialog.length; i++) {//0番地はダミー
		if (ans_dialog[i] == false) {
			NO_FILTER += "," + i;//フィルタ番号を加算
			no_filter_count++;
		}
	}
	if (ans_dialog.length == no_filter_count) {myerror("ダイアログのすべてのチェックが外れています")}
	if (NO_FILTER != "") {NO_FILTER = NO_FILTER.replace (/^,/, '&no_filter=')}
}

//各段落の処理
var my_regex = new RegExp(/^[ \s\r\n]*$/);//空行をみつけるための正規表現
var my_counter = 0;//かうんたっく
for(var i = (my_paragraphs.length -1); i >= 0; i--) {//段落を後ろから処理
	var my_contents = my_paragraphs[i].contents;
	if (my_regex.test(my_contents)){continue;}//空行なら次の段落へ
	var post_str = encodeURI(my_contents);//エンコード
	var my_reply = get_yahoo_respons(post_str);//Yahooへの問い合わせ
	my_reply = my_reply.split("\n\n", 2)[1];//レスポンスヘッダの削除
	
	var my_xml = new XML(my_reply);//XMLオブジェクトの生成
	var my_ns_uri = my_xml.namespace();
	var my_ns = new Namespace(my_ns_uri);
	setDefaultXMLNamespace(my_ns);//デフォルトネームスペースを設定する
	if (my_xml.localName().toString() == "Error"){//エラー要素があったら中止
		myerror("サーバーがエラーを返しました:" + my_xml.xpath("/Error/Message")[0].toString());
	}
	for( var ii = (my_xml.Result.length() - 1); ii >= 0; ii--) {//my_xmlのエレメントを後ろから処理
		var my_StartPos = parseInt(my_xml.Result[ii].StartPos[0].toString());//スタートポイント、文字位置
		var my_Surface = "【誤】" + my_xml.Result[ii].Surface[0].toString() + "\n";
		var my_ShitekiWord = "【正】" + my_xml.Result[ii].ShitekiWord[0].toString() + "\n";
		var my_ShitekiInfo = "<" + my_xml.Result[ii].ShitekiInfo[0].toString() + ">\n";
		
		var my_note = my_paragraphs[i].insertionPoints[my_StartPos].notes.add();//注釈の作成
		my_note.texts[0].contents = my_ShitekiInfo + my_Surface + my_ShitekiWord;//注釈の中身を書き換え
		my_counter++;
	}
}
myerror("校正の結果:\n" + my_counter + "箇所の注釈をつけました");