Hatena::ブログ(Diary)

名もないテクノ手 このページをアンテナに追加 RSSフィード

EPUB版『InDesign者のための正規表現入門』

InDesignのTips一覧

2011-09-26

[][][]ルビの検索置換

とあるブログに「InDesignではルビの検索置換ができない」と書いてあった。まあ、確かにできない。誰かが書いているでしょうけれど、JavaScriptだとこんな感じに書けると思います。

var my_find_re = /けん さく も じ れつ/g;
var my_change_str ="ち かん も じ れつ";

var my_doc = app.documents[0];
var my_story = my_doc.stories;
for (var i = 0; i < my_story.length; i++) {
    var my_style = my_story[i].textStyleRanges;
    for (var ii = 0; ii<my_style.length; ii++) {
        my_style[ii].rubyString = my_style[ii].rubyString.replace(my_find_re, my_change_str);
    }
}

▼こういうルビを持ったテキストが...

f:id:seuzo:20110926110753p:image


▼こうなりました

f:id:seuzo:20110926110745p:image

だかしかし、大変遅いです。書籍だと実用に耐えられない遅さかもしんない。

親文字がわかっているなら、まず親文字を検索してそのルビで検索置換すればいいのかな。

var my_find_p = "検索文字列";//親文字
var my_find_ruby = /けん さく も じ れつ/g;
var my_change_str ="ち かん も じ れつ";

////////////////////////////////////////////正規表現検索
/*
my_range	検索置換の範囲
my_find	検索オブジェクト ex.) {findWhat:"(わたく?し|私)"}
*/
function my_RegexFindChange(my_range, my_find) {
	//検索の初期化
	app.findGrepPreferences = NothingEnum.nothing;
	app.changeGrepPreferences = NothingEnum.nothing;
	//検索オプション
	app.findChangeGrepOptions.includeLockedLayersForFind = false;//ロックされたレイヤーをふくめるかどうか
	app.findChangeGrepOptions.includeLockedStoriesForFind = false;//ロックされたストーリーを含めるかどうか
	app.findChangeGrepOptions.includeHiddenLayers = false;//非表示レイヤーを含めるかどうか
	app.findChangeGrepOptions.includeMasterPages = false;//マスターページを含めるかどうか
	app.findChangeGrepOptions.includeFootnotes = false;//脚注を含めるかどうか
	app.findChangeGrepOptions.kanaSensitive = true;//カナを区別するかどうか
	app.findChangeGrepOptions.widthSensitive = true;//全角半角を区別するかどうか

	app.findGrepPreferences.properties = my_find;//検索の設定

    return my_range.findGrep();//検索のみの場合:マッチしたオブジェクトを返す
}

var my_doc = app.documents[0];
var my_texts = my_RegexFindChange(my_doc, {findWhat:my_find_p});
for (var i = 0; i < my_texts.length; i++) {
    my_texts[i].rubyString = my_texts[i].rubyString.replace(my_find_ruby, my_change_str);
}

そこそこ速くなりました。


でもまあ、もっと大量かつ適切に置換する必要がある場合はIDMLとかIDMSにしてRubyString属性を変えればいいのかな。完全互換ではないからよっぽどの必要性がなければやらないでしょうけれど^^

2011-08-03

[][][][]作業中のカレントページを、単ページでPDF書出す「export_currentpage_byPDF 1.0」

何をするスクリプトか?

作業中のカレントページを、単ページでPDF書出しします。

一度初期設定をすれば、しばらくの間、設定レスで使い続けられます。

D


同梱ファイル(2Files)

Readme.txt このファイルです。とにかく最初によんでください。

export_currentpage_byPDF.jsx スクリプト本体です。


使用条件

このスクリプトが正常に動作する環境は以下の通りです。Windows環境でも動作する可能性がありますが未検証です。


ダウンロード

http://www.seuzo.jp/st/scripts_InDesignCS5/index.html#export_currentpage_byPDF


インストール

スクリプト本体(export_currentpage_byPDF.jsx)を

~/Library/Preferences/Adobe InDesign/Version 7.0-J/ja_JP/Scripts/Scripts Panel/

にコピーしてください。エイリアスを入れておくだけでもかまいません。

スクリプトパレットから使用します。


使用方法

(0)「ウインドウ」メニューから「スクリプティング」ー「スクリプト」を選択し、スクリプトパレットを出します。

(1)書出したいページを表示します。どのページがカレントページかわからない時は、レイアウトウインドウ左下のページ欄を確認してください。

(2)スクリプトパレットから、スクリプト「export_currentpage_byPDF.jsx」をダブルクリックします。

(3)最初の起動時に「保存フォルダ」「使用する書出しプリセット」を設定するダイアログが出ます。

(4)書出しを確認するダイアログが出ます。ページ数を確認して「はい」をクリックしてください。

  • 最後に起動してから3日間は設定が有効です。起動する度に初期設定は自動更新されます。
  • 初期設定をやりなおしたい時は、このスクリプトと同じ階層にある「export_currentpage_byPDF_config.txt」を削除してください。
  • 保存先に同じ名前のPDFがある場合、上書きするかどうかを選択できます。

特別な設定

ソースコード中の13行目あたりから書かれている設定を変更すると、スクリプトの振る舞いカスタマイズできます。

////////////////////////////////////////////設定
var my_expiration_date = 3;//有効期限(日)。初期設定をしなおさないで済む期間
var my_config_filename = 'export_currentpage_byPDF_config.txt';//設定ファイル名
var my_confirmation = true;//処理前に確認ダイアログを表示するかどうか
var my_digitnum = 3;//ノンブルが数値だったら、桁を揃える。2=>002, 45=>045, 123=>123

既知の不具合、またはToDo



免責事項

  • 本アプリケーションはInDesignにおける作業効率支援なのであって、処理結果を保証するものではありません。かならず確認をされることをおすすめします。
  • このツールを使用する上でデータの破損などのあらゆる不具合・不利益については一切の責任を負いかねますのでご了解ください。
  • このツールはすべてのMacintoshMac OS上で動作をするという確認をとっていませんし、事実上出来ません。したがって、動作を保証するものではありません。

ライセンス

GNU GPLv3

http://sourceforge.jp/projects/opensource/wiki/licenses%252FGNU_General_Public_License_version_3.0


履 歴

2011-08-02 ver.0.1 AppleScriptで使っていたものをJavaScriptで書き直し


/*
export_currentpage_byPDF.jsx
(c)2011 seuzo

作業中のカレントページを、単ページでPDF書出しします。
一度初期設定をすれば、しばらくの間、設定レスで使い続けられます。

2011-08-02	ver1.0	AppleScriptで使っていたものをJavaScriptで書き直し


*/

#target "InDesign"
////////////////////////////////////////////設定
var my_expiration_date = 3;//有効期限(日)。初期設定をしなおさないで済む期間
var my_config_filename = 'export_currentpage_byPDF_config.txt';//設定ファイル名
var my_confirmation = true;//処理前に確認ダイアログを表示するかどうか
var my_digitnum = 3;//ノンブルが数値だったら、桁を揃える。2=>002, 45=>045, 123=>123
var my_script_version = '1.0';//このスクリプトのバージョン
var my_date_record = '';//最終設定日(あとで設定)
var my_save_folder_path = '';//PDF保存フォルダ(あとで設定)
var my_pdf_presets = '';//PDFプリセット名(あとで設定)


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

////////////////////////////////////////////カレントスクリプトのフルパスを得る 
function get_my_script_path() {
	try {
		return  app.activeScript;
	} catch (e) {
		return File (e.fileName);//ESTKから実行した時も正しくパスを返す
	}
}

////////////////////////////////////////////OSのファイルセパレータを得る 
function get_separator() {
    if (Folder.fs ==="Macintosh") {
        return "/";
    } else {
        return "\\";
    }
}

////////////////////////////////////////////ファイル・フォルダ選択ダイアログ。パス文字列を返す。
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;
}

////////////////////////////////////////////データをファイルに書き込む 。書き込んだファイルオブジェクトを返す
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);
	}
}

////////////////////////////////////////////ポッップアップダイアログ
function popupDialog(myTitle, myPrompt, myList){
	var myDialog, mySelecteditem, ans;
	myDialog = app.dialogs.add({name:myTitle,canCancel:true});
	with(myDialog){
		with(dialogColumns.add()){
			staticTexts.add({staticLabel:myPrompt});// プロンプト
		}
		with(dialogColumns.add()){
			mySelecteditem = dropdowns.add({stringList:myList, selectedIndex:0});// ポップアップメニュー
		}
	}
	// ダイアログボックスを表示
	if(myDialog.show() === true){
		ans = mySelecteditem.selectedIndex;
		myDialog.destroy();//正常にダイアログを片付ける
		return ans//選択したアイテムの番号を返す
	} else {
		// ユーザが「キャンセル」をクリックしたので、メモリからダイアログボックスを削除
		myDialog.destroy();
		exit();//スクリプト終了
	}
}

////////////////////////////////////////////数値のゼロパディング。str=数値、digitnum=桁数
function set_zaropadding(str, digitnum) {
    var str = str.toString();
    if (str.search(/^[0-9]+$/) === -1) {//数字じゃない
        return str;//そのまま返す
    }
    if (str.length >= digitnum) {//指定桁数以上ある
        return str;//そのまま返す
    }
     var zero_str = '';
     for (var i = 0; i < my_digitnum; i++) {
         zero_str+= "0";
     }
    return (zero_str + str).slice( 0 - digitnum);
}

////////////////////////////////////////////初期設定&設定ファイル書き込み
function write_setting(my_write_file_path) {
    var tmp_data = my_script_version + "\r";//my_script_versionはグローバル変数を期待
    tmp_data += new Date().toString() + "\r";//現在の日付
    tmp_data += chooseF("PDFを保存するフォルダを選んでください", "Folder").toString() + "\r";
    var my_pdf_presets_list = app.pdfExportPresets.everyItem().name;//すべてのプリセット名リスト
    var my_pdf_presets = popupDialog("プリセット選択", "PDF書出しプリセットを選んでください", my_pdf_presets_list);
    tmp_data += my_pdf_presets_list[my_pdf_presets];
    write_file(my_write_file_path, tmp_data);
    return true;//書き込み済みであることのフラグ
}



////////////////////////////////////////////メイン 
if (app.documents.length === 0) {myerror("ドキュメントが開かれていません")}
var my_doc = app.documents[0];
//名前やパスなどの調査
var my_doc_name = (my_doc.name).replace (/\.ind[d0-9]?$/i, "");//拡張子を除いたファイル名
var my_page = app.layoutWindows[0].activePage;
var my_page_no = my_page.name;//ページ名(ノンブル文字列)
var my_separator = get_separator();//パスの区切り文字
var my_activescript_path = get_my_script_path();//このスクリプトのパス
var my_activescript_folder = File(my_activescript_path).parent;//このスクリプトのコンテナフォルダ
var my_config_path = my_activescript_folder + my_separator + my_config_filename;//設定ファイルのフルパス

//設定ファイルの読み書き
var f_modified = false;//ファイルを書き込んだかどうかのフラグ(余計な読み書き回数を減らすために)
//設定ファイルがなかったら
if (File(my_config_path).exists === false) {
    f_modified = write_setting(my_config_path);//設定して書き込み
}
//設定ファイルの読み出し
var my_data_list = read_file(my_config_path);
my_data_list = my_data_list.split(/[\r\n]+/);
var my_date_record = new Date(my_data_list[1]);//最終設定日
var my_save_folder_path = my_data_list[2];//PDF保存フォルダ
var my_pdf_presets = my_data_list[3];//PDFプリセット名
//設定ファイルが古かったら
var my_date_now = new Date();
if (my_date_now - my_date_record > my_expiration_date * 86400000) {
     f_modified = write_setting(my_config_path);//設定して書き込み
    var my_data_list = read_file(my_config_path);//そして読み込み
    my_data_list = my_data_list.split(/[\r\n]+/);
    var my_date_record = new Date(my_data_list[1]);//最終設定日
    var my_save_folder_path = my_data_list[2];//PDF保存フォルダ
    var my_pdf_presets = my_data_list[3];//PDFプリセット名
} 
//今回、新規設定を書き込みしていなければ、現在の設定を上書きして最新にしておく
if ( f_modified === false) {
    var tmp_data = my_script_version + "\r";
    tmp_data+= my_date_now.toString() + "\r";
    tmp_data+= my_save_folder_path + "\r";
    tmp_data+= my_pdf_presets;
    write_file(my_config_path, tmp_data);
}

var my_pdf_file_path = my_save_folder_path + my_separator + my_doc_name + '_' + set_zaropadding(my_page_no, my_digitnum) + '.pdf';//書き出されるPDFのフルパス。ノンブルは指定桁数でzero paddingする。
//確認
if (my_confirmation) {
    var my_button = confirm(my_page_no + "ページを書出しますか\r書出しプリセット:" + my_pdf_presets + "\r書出しパス:" + my_pdf_file_path, false, undefined);
    if (my_button === false){exit()};//終了
}

//PDF書出し
//すでに同名のファイルがあった時
if (File(my_pdf_file_path).exists) {
    var my_button = confirm("同名のPDFファイルがあります:\r"+my_pdf_file_path+"\r上書きしますか?", true, undefined);
    if (my_button === false){exit()};//終了
}
app.pdfExportPreferences.pageRange = my_page_no;
try{
    my_doc.exportFile(ExportFormat.PDF_TYPE, File(my_pdf_file_path), false, my_pdf_presets);//書出し
}catch(e){
    myerror("Error: "+e.message+" (Line "+ e.line+" in this script code.)");//エラー
}

2011-07-09

[][][][]「round_num 0.5」の特別な設定例

round_num」をこっそりver.0.5にアップしてエントリーを上書きしました。ver.0.4をリリースしてから間がないですし、たいていのユーザーにとってはほとんど変わらないことでしたので。

で、だ。ver.0.5はどこが違うかっていうと、変更したグリフに特定の文字スタイルを適用できるようにしました。この機能を使うためには、ソースコードの設定をちょっと書き換える必要があります。

たとえば、下図のようなテキストがあったとします。

f:id:seuzo:20110709000129p:image

この時、「手順+数字」の時だけ数字のグリフを変更し、かつそのグリフに指定の文字スタイルを適用します。

f:id:seuzo:20110709000215p:image

具体的には、ソースコードの13行目あたりから始まる設定:

var my_regex_str = "[0-9,.]*[0-9]+"; //正規表現のデフォルト
var my_char_style_str = ""; //数字に適用する文字スタイル。空文字列ならなにも適用しない。

この部分を

var my_regex_str = "(?<=手順)[0-9]+"; //正規表現のデフォルト
var my_char_style_str = "みどり"; //数字に適用する文字スタイル。空文字列ならなにも適用しない。

のように変更します*1。使ったあとに元に戻す必要がありますので、元の設定をコピーしてコメントアウトしておくといいでしょう。

動画で見るとこんな感じです。

D

*1:この正規表現が何を表すか、よくわからない人は変更しない方がいいと思います。ここに書かれた正規表現は、InDesignの検索フィールドに入ります。ですから、InDesignの拡張正規表現に対応しています。ただし、あまり無茶な書き方をするとエラーになったりするかもしれません。ほどほどに。

2011-06-25

[][][]丸数字などを簡単に変換できる「round_num 0.5」

今回、milligrammeさんによって「round_num 0.3」を合成フォントでも使えるように書き換えていただきました。感謝します。

このコードですと、別ファイルから実行になってしまいますし、少し挙動を変えたかった部分もありますので、本体の方に取り込ませていただきました。InDesign CS5で動作確認をいたしましたが、おそらくCS4でも動作すると思います。

追記:2011-07-08T17:38:24+0900)少し手直しをして、ver.0.5にしました。ver.0.4をリリースしてからあまり日にちも経っていないので、このエントリーを書き換えました。たいていのユーザーは特に変わりないです。


何をするスクリプトか?

選択したテキスト中の数字を丸数字などに変換します。

選べるオプションは7つ。

  • 丸数字にする ①
  • 白抜き丸数字にする ❷
  • 四角内数字にする
  • 四角(ラウンド)内数字にする
  • 黒四角内白抜き数字にする
  • 黒四角(ラウンド)内白抜き数字にする
  • 括弧内数字にする

D


同梱ファイル(2Files)

Readme.txt このファイルです。とにかく最初によんでください。

round_num.jsx スクリプト本体です。


使用条件

このスクリプトが正常に動作する環境は以下の通りです。Windows環境でも動作する可能性がありますが未検証です。


ダウンロード

http://www.seuzo.jp/st/scripts_InDesignCS5/index.html#round_num


インストール

スクリプト本体(round_num.jsx)を

~/Library/Preferences/Adobe InDesign/Version 7.0-J/ja_JP/Scripts/Scripts Panel/

にコピーしてください。エイリアスを入れておくだけでもかまいません。

スクリプトパレットから使用します。


使用方法

(0)「ウインドウ」メニューから「スクリプティング」ー「スクリプト」を選択し、スクリプトパレットを出します。

(1)変換したい数字を含むテキストを選択します。

(2)スクリプトパレットから、スクリプト「round_num.jsx」をダブルクリックします。

(3)変換したい字種を選んで、OKボタンをクリックしてください


特別な設定

ソースコード中の13行目あたりに書かれている設定を変更すると、通常とは少し違う振る舞いをします。

var my_report = true;//処理の最後にレポートダイアログを表示するかどうか
var my_regex_str = "[0-9,.]*[0-9]+"; //正規表現のデフォルト
var my_char_style_str = ""; //数字に適用する文字スタイル。空文字列ならなにも適用しない。

使い方例はhttp://d.hatena.ne.jp/seuzo/20110709/ を参照してください。


既知の不具合、またはToDo

OTFでGIDが同じ値を持つフォントしか置換しません。例えば、Osakaフォント(TrueType)には「①」「❶」などの字形を持ちますが、数字の「1」のCID/GIDは「18」ではなく、「47」なので置換対象になりません。

変換できる数字は「0」〜「100」までと「00」〜「09」までです。これ以上の大きな数は無視されます。

数字列中に「,」(カンマ)や「.」(ピリオド)を含む数字列は変換しません。「1.0」は「1」と解釈しません。位取りのカンマでないとしても「1,2,3」という数字列は無視します。どうしても置換したい場合は、それぞれ数字のみを選択して実行してください。

合成フォントが使われている場合、丸数字などに対して合成フォントの漢字部分のフォントを適用します。


免責事項

  • 本アプリケーションはInDesignにおける作業効率支援なのであって、処理結果を保証するものではありません。かならず確認をされることをおすすめします。
  • このツールを使用する上でデータの破損などのあらゆる不具合・不利益については一切の責任を負いかねますのでご了解ください。
  • このツールはすべてのMacintoshMac OS上で動作をするという確認をとっていませんし、事実上出来ません。したがって、動作を保証するものではありません。

ライセンス

GNU GPLv3

http://sourceforge.jp/projects/opensource/wiki/licenses%252FGNU_General_Public_License_version_3.0


履 歴

2008-09-19 ver.0.1 とりあえず

2008-09-20 ver.0.2 2桁以上の選択文字がストーリーの最後にある時、処理が失敗するのを修正した。文字列「111」の最初の「11」を選択しているとき、「⑪⑪」などと変換されてしまうのを修正した。http://d.hatena.ne.jp/seuzo/20080919/1221832112

2009-05-26 ver.0.3 InDesign CS4対応版。選択しているテキストが数字だけでなく、他の文字列を選択していても、選択文字列中の数字列を変換できるようにした。http://d.hatena.ne.jp/seuzo/20090527/1243353039

2011-06-24 ver.0.4 合成フォントの対応について、milligrammeさんに修正していただきました。 https://github.com/milligramme/round_num/commit/92dc8fdfe7662d386fc928ab8e4ef0316ef2af4d ありがとうございます。合成フォントを選択している時は、フォントを漢字フォントに変更します。InDesign CS5にて動作確認。表組みやセルを選択していても置換できるようにした。

2011-07-08 ver.0.5 変更後のグリフに文字スタイルを適用できる設定を追加


ソースコード

/*
round_num.jsx
(c)2008-2011 www.seuzo.jp
選択したテキスト中の数字を丸数字などに変換します。

2008-09-19	ver.0.1	とりあえず
2008-09-20	ver.0.2	2桁以上の選択文字がストーリーの最後にある時、処理が失敗するのを修正した。文字列「111」の最初の「11」を選択しているとき、「&#9322;&#9322;」と変換されてしまうのを修正した。
2009-05-26	ver.0.3	InDesign CS4対応版。選択しているテキストが数字だけでなく、他の文字列を選択していても、選択文字列中の数字列を変換できるようにした。
2011-06-24  ver.0.4 合成フォントの対応について、milligrammeさんに修正していただきました。 https://github.com/milligramme/round_num/commit/92dc8fdfe7662d386fc928ab8e4ef0316ef2af4d ありがとうございます。合成フォントを選択している時は、フォントを漢字フォントに変更します。InDesign CS5にて動作確認。表組みやセルを選択していても置換できるようにした。http://d.hatena.ne.jp/seuzo/20110625/1308965805
2011-07-08  ver.0.5 変更後のグリフに文字スタイルを適用できる設定を追加
*/

////////////////////////////////////////////設定
#target "InDesign"
var my_report = true;//処理の最後にレポートダイアログを表示するかどうか
var my_regex_str = "[0-9,.]*[0-9]+"; //正規表現のデフォルト
var my_char_style_str = ""; //数字に適用する文字スタイル。空文字列ならなにも適用しない。


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


////////////////////////////////////////////ラジオダイアログ
/*
myTitle	ダイアログ(バー)のタイトル
myPrompt	メッセージ
myList	ラジオボタンに展開するリスト

result	選択したリスト番号
*/
function radioDialog(my_title, my_prompt, my_list){
	var my_dialog = app.dialogs.add({name:my_title, canCancel:true});
	with(my_dialog) {
		with(dialogColumns.add()) {
			// プロンプト
			staticTexts.add({staticLabel:my_prompt});
			with (borderPanels.add()) {
				var my_radio_group = radiobuttonGroups.add();
				with (my_radio_group) {
					for (var i = 0; i < my_list.length; i++){
						if (i === 0) {
							radiobuttonControls.add({staticLabel:my_list[i], checkedState:true});
						} else {
						radiobuttonControls.add({staticLabel:my_list[i]});
						}
					}
				}
			}
		}
	}


	if (my_dialog.show() === true) {
		var ans = my_radio_group.selectedButton;
		//正常にダイアログを片付ける
		my_dialog.destroy();
		//選択したアイテムの番号を返す
		return ans;
	} else {
		// ユーザが「キャンセル」をクリックしたので、メモリからダイアログボックスを削除
		my_dialog.destroy();
	}
}

////////////////////////////////////////////文字スタイルchar_style_strが存在するかどうか? 存在すれば、文字スタイルオブジェクトを返す
function exists_char_style(char_style_str) {
    var my_charStyles = app.documents[0].allCharacterStyles;
    for (var i = 1; i < my_charStyles.length; i++) {//0番地は、「[なし]」
        if (my_charStyles[i].name === char_style_str) {return my_charStyles[i];}
    }
    return false;
}


////////////////////////////////////////////正規表現検索
//正規表現で検索して、ヒットオブジェクトを(お尻から)返すだけ
function my_regex(my_range_obj, my_find_str) {
        //検索の初期化
        app.findGrepPreferences = NothingEnum.nothing;
        app.changeGrepPreferences = NothingEnum.nothing;
        //検索オプション
        app.findChangeGrepOptions.includeLockedLayersForFind = false;//ロックされたレイヤーをふくめるかどうか
        app.findChangeGrepOptions.includeLockedStoriesForFind = false;//ロックされたストーリーを含めるかどうか
        app.findChangeGrepOptions.includeHiddenLayers = false;//非表示レイヤーを含めるかどうか
        app.findChangeGrepOptions.includeMasterPages = false;//マスターページを含めるかどうか
        app.findChangeGrepOptions.includeFootnotes = false;//脚注を含めるかどうか
        app.findChangeGrepOptions.kanaSensitive = true;//カナを区別するかどうか
        app.findChangeGrepOptions.widthSensitive = true;//全角半角を区別するかどうか

        app.findGrepPreferences.findWhat = my_find_str;//検索文字の設定
        //app.changeGrepPreferences.changeTo = my_change_str;//置換文字の設定
        return my_range_obj.findGrep(true);//検索の実行(reverse)
}

////////////////////////////////////////////字形検索置換
function change_glyph(my_range_obj, find_font, find_gid, change_font, change_gid) {
	var my_doc = app.activeDocument;
	app.findGlyphPreferences = NothingEnum.nothing;
	app.changeGlyphPreferences = NothingEnum.nothing;
	app.findGlyphPreferences.appliedFont = find_font;
	app.changeGlyphPreferences.appliedFont = change_font;
	app.findGlyphPreferences.glyphID = find_gid;
	app.changeGlyphPreferences.glyphID = change_gid;
	var my_result = my_range_obj.changeGlyph ();
	return my_result;
}



////////////////////////////////////////////以下メイン実行
////////////////まずは選択しているもののチェック
if (app.documents.length === 0) {myerror("ドキュメントが開かれていません")}
var my_doc = app.documents[0];
if (my_doc.selection.length === 0) {myerror("テキストを選択してください")}
var my_selection = my_doc.selection[0];
var my_class =my_selection.reflect.name;
my_class = "Text, TextColumn, Story, Paragraph, Line, Word, Character, TextStyleRange, Table, Cell".match(my_class);
if (my_class === null) {myerror("テキストを選択してください")}
//文字スタイルの存在をチェック
if (my_char_style_str !== "") {
    my_char_style_obj = exists_char_style(my_char_style_str);
    if (my_char_style_obj === false) { //設定された文字スタイルが存在しない
        myerror("指定された文字スタイル「" + my_char_style_str + "」はドキュメントに存在しません");
    }
}


var hit_obj = my_regex(my_selection, my_regex_str);//数字列の検索
var target_obj = new Array();//ターゲットとなるオブジェクトの配列
for (var i = 0; i< hit_obj.length; i++) {
	var tmp_str = hit_obj[i].contents;
	if ((tmp_str.match(/[,.]/) === null) && (tmp_str.match(/^(100|\d\d?)$/) !== null)) {//数字列にカンマやピリオドが含まれておらず、かつ、0〜100の数字列ならば
		target_obj.push(hit_obj[i]);//ターゲットとする
	}
}
if (target_obj.length === 0) {myerror("変換可能な数字はありませんでした")}


////////////////検索前処理
//処理の選択ダイアログ
var myList = ["丸数字にする &#9312;", 
"白抜き丸数字にする &#10103;", 
"四角内数字にする", 
"四角(ラウンド)内数字にする", 
"黒四角内白抜き数字にする", 
"黒四角(ラウンド)内白抜き数字にする", 
"括弧内数字にする"];
app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;
var ans_int = radioDialog("round_num", "選択した数字テキストを丸数字などに変換します。処理を選択してください\n", myList);

//CIDテーブルのセット
var my_tbl = new Array();//my_tblはCID番号の配列。0番地から100番地までが「0」〜「100」の字形の対応し、101番地から111番地までが「00」〜「09」の字形の対応している。
if (ans_int === 0) {//丸数字にする &#9312;
	my_tbl = [8224, 7555, 7556, 7557, 7558, 7559, 7560, 7561, 7562, 7563, 7564, 7565, 7566, 7567, 7568, 7569, 7570, 7571, 7572, 7573, 7574, 8091, 8102, 8103, 8104, 8105, 8106, 8107, 8108, 8109, 8110, 8111, 10244, 10245, 10246, 10247, 10248, 10249, 10250, 10251, 10252, 10253, 10254, 10255, 10256, 10257, 10258, 10259, 10260, 10261, 10262, 10263, 10264, 10265, 10266, 10267, 10268, 10269, 10270, 10271, 10272, 10273, 10274, 10275, 10276, 10277, 10278, 10279, 10280, 10281, 10282, 10283, 10284, 10285, 10286, 10287, 10288, 10289, 10290, 10291, 10292, 10293, 10294, 10295, 10296, 10297, 10298, 10299, 10300, 10301, 10302, 10303, 10304, 10305, 10306, 10307, 10308, 10309, 10310, 10311, 10312, 10234, 10235, 10236, 10237, 10238, 10239, 10240, 10241, 10242, 10243];
} else if (ans_int === 1) {//白抜き丸数字にする &#10103;
	my_tbl = [10503, 8286, 8287, 8288, 8289, 8290, 8291, 8292, 8293, 8294, 10514, 10515, 10516, 10517, 10518, 10519, 10520, 10521, 10522, 10523, 10524, 10525, 10526, 10527, 10528, 10529, 10530, 10531, 10532, 10533, 10534, 10535, 10536, 10537, 10538, 10539, 10540, 10541, 10542, 10543, 10544, 10545, 10546, 10547, 10548, 10549, 10550, 10551, 10552, 10553, 10554, 10555, 10556, 10557, 10558, 10559, 10560, 10561, 10562, 10563, 10564, 10565, 10566, 10567, 10568, 10569, 10570, 10571, 10572, 10573, 10574, 10575, 10576, 10577, 10578, 10579, 10580, 10581, 10582, 10583, 10584, 10585, 10586, 10587, 10588, 10589, 10590, 10591, 10592, 10593, 10594, 10595, 10596, 10597, 10598, 10599, 10600, 10601, 10602, 10603, 10604, 10504, 10505, 10506, 10507, 10508, 10509, 10510, 10511, 10512, 10513];
} else if (ans_int === 2) {//四角内数字にする
	my_tbl = [10764, 10766, 10768, 10770, 10772, 10774, 10776, 10778, 10780, 10782, 10784, 10785, 10786, 10787, 10788, 10789, 10790, 10791, 10792, 10793, 10794, 10795, 10796, 10797, 10798, 10799, 10800, 10801, 10802, 10803, 10804, 10805, 10806, 10807, 10808, 10809, 10810, 10811, 10812, 10813, 10814, 10815, 10816, 10817, 10818, 10819, 10820, 10821, 10822, 10823, 10824, 10825, 10826, 10827, 10828, 10829, 10830, 10831, 10832, 10833, 10834, 10835, 10836, 10837, 10838, 10839, 10840, 10841, 10842, 10843, 10844, 10845, 10846, 10847, 10848, 10849, 10850, 10851, 10852, 10853, 10854, 10855, 10856, 10857, 10858, 10859, 10860, 10861, 10862, 10863, 10864, 10865, 10866, 10867, 10868, 10869, 10870, 10871, 10872, 10873, 10874, 10765, 10767, 10769, 10771, 10773, 10775, 10777, 10779, 10781, 10783];
} else if (ans_int === 3) {//四角(ラウンド)内数字にする
	my_tbl = [11307, 11309, 11311, 11313, 11315, 11317, 11319, 11321, 11323, 11325, 11327, 11328, 11329, 11330, 11331, 11332, 11333, 11334, 11335, 11336, 11337, 11338, 11339, 11340, 11341, 11342, 11343, 11344, 11345, 11346, 11347, 11348, 11349, 11350, 11351, 11352, 11353, 11354, 11355, 11356, 11357, 11358, 11359, 11360, 11361, 11362, 11363, 11364, 11365, 11366, 11367, 11368, 11369, 11370, 11371, 11372, 11373, 11374, 11375, 11376, 11377, 11378, 11379, 11380, 11381, 11382, 11383, 11384, 11385, 11386, 11387, 11388, 11389, 11390, 11391, 11392, 11393, 11394, 11395, 11396, 11397, 11398, 11399, 11400, 11401, 11402, 11403, 11404, 11405, 11406, 11407, 11408, 11409, 11410, 11411, 11412, 11413, 11414, 11415, 11416, 11417, 11308, 11310, 11312, 11314, 11316, 11318, 11320, 11322, 11324, 11326];
} else if (ans_int === 4) {//黒四角内白抜き数字にする
	my_tbl =[11037, 11039, 11041, 11043, 11045, 11047, 11049, 11051, 11053, 11055, 11057, 11058, 11059, 11060, 11061, 11062, 11063, 11064, 11065, 11066, 11067, 11068, 11069, 11070, 11071, 11072, 11073, 11074, 11075, 11076, 11077, 11078, 11079, 11080, 11081, 11082, 11083, 11084, 11085, 11086, 11087, 11088, 11089, 11090, 11091, 11092, 11093, 11094, 11095, 11096, 11097, 11098, 11099, 11100, 11101, 11102, 11103, 11104, 11105, 11106, 11107, 11108, 11109, 11110, 11111, 11112, 11113, 11114, 11115, 11116, 11117, 11118, 11119, 11120, 11121, 11122, 11123, 11124, 11125, 11126, 11127, 11128, 11129, 11130, 11131, 11132, 11133, 11134, 11135, 11136, 11137, 11138, 11139, 11140, 11141, 11142, 11143, 11144, 11145, 11146, 11147, 11038, 11040, 11042, 11044, 11046, 11048, 11050, 11052, 11054, 11056];
} else if (ans_int === 5) {//黒四角(ラウンド)内白抜き数字にする
	my_tbl =[11576, 11578, 11580, 11582, 11584, 11586, 11588, 11590, 11592, 11594, 11596, 11597, 11598, 11599, 11600, 11601, 11602, 11603, 11604, 11605, 11606, 11607, 11608, 11609, 11610, 11611, 11612, 11613, 11614, 11615, 11616, 11617, 11618, 11619, 11620, 11621, 11622, 11623, 11624, 11625, 11626, 11627, 11628, 11629, 11630, 11631, 11632, 11633, 11634, 11635, 11636, 11637, 11638, 11639, 11640, 11641, 11642, 11643, 11644, 11645, 11646, 11647, 11648, 11649, 11650, 11651, 11652, 11653, 11654, 11655, 11656, 11657, 11658, 11659, 11660, 11661, 11662, 11663, 11664, 11665, 11666, 11667, 11668, 11669, 11670, 11671, 11672, 11673, 11674, 11675, 11676, 11677, 11678, 11679, 11680, 11681, 11682, 11683, 11684, 11685, 11686, 11577, 11579, 11581, 11583, 11585, 11587, 11589, 11591, 11593, 11595];
} else if (ans_int === 6) {//括弧内数字にする
	my_tbl =[8227, 8071, 8072, 8073, 8074, 8075, 8076, 8077, 8078, 8079, 8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089, 8090, 9894, 9895, 9896, 9897, 9898, 9899, 9900, 9901, 9902, 9903, 9904, 9905, 9906, 9907, 9908, 9909, 9910, 9911, 9912, 9913, 9914, 9915, 9916, 9917, 9918, 9919, 9920, 9921, 9922, 9923, 9924, 9925, 9926, 9927, 9928, 9929, 9930, 9931, 9932, 9933, 9934, 9935, 9936, 9937, 9938, 9939, 9940, 9941, 9942, 9943, 9944, 9945, 9946, 9947, 9948, 9949, 9950, 9951, 9952, 9953, 9954, 9955, 9956, 9957, 9958, 9959, 9960, 9961, 9962, 9963, 9964, 9965, 9966, 9967, 9968, 9969, 9970, 9971, 9972, 9973, 9884, 9885, 9886, 9887, 9888, 9889, 9890, 9891, 9892, 9893];
} else {
	myerror("処理をキャンセルしました");
}

//ターゲットをループ
var my_count = 0;
var tmp_result = "";
for (var i = 0; i < target_obj.length; i++) {
	//target_obj[i].select();
	var my_contents = target_obj[i].contents;//ターゲットの数字(テキスト)
	var my_font = target_obj[i].appliedFont;//ターゲットテキストのフォント
    
    //合成フォントへの対応
    var f_atc = false; //合成フォントかどうかのフラグ
    var my_font_org = my_font;//復帰する時のフォント
    if (my_font.fontType === FontTypes.ATC) {
        f_atc = true;
        var target_font_name = my_font.fontFamily;//合成フォント名
        var comp_ent = my_doc.compositeFonts.item(target_font_name).compositeFontEntries;//合成フォントリスト
        var fname = comp_ent[0].appliedFont + "\t" + comp_ent[0].fontStyle;//合成フォント中の「漢字」フォントを使用
        my_font = app.fonts.item(fname);
        target_obj[i].appliedFont = my_font;
    }
	
	//添字のスライド
	var my_suffix = 0;
	if (my_contents.match(/^0\d$/)) {//00〜09の時
		my_suffix = 101 + parseInt (my_contents);
	} else {
		my_suffix = parseInt (my_contents);
	}

	//選択文字列の変換
	target_obj[i].contents = "1";//強制的に選択文字を「1」にする

	//字形検索置換
	try {
		tmp_result = change_glyph(target_obj[i], my_font, 18, my_font, my_tbl[my_suffix]);
		if (tmp_result === "") {
			target_obj[i].contents = my_contents;
		} else {
			my_count++;
		}
        //if ( f_atc ) {target_obj[i].appliedFont = my_font_org; }//合成フォントのフラグが立っていたら、フォントを元の合成フォントに戻す。★overwriteしないとエラーになる(保留)
        if (my_char_style_str !== "") { //文字スタイルが指定されていれば適用する(存在の有無はチェック済み)
            target_obj[i].appliedCharacterStyle = my_char_style_obj;
        }
	} catch(e) {
        $.writeln(e);
		target_obj[i].contents = my_contents;
	}
}

//結果レポート
if (my_report) {
	if (my_count === 0) {
		alert ("変更箇所はありませんでした。字形をもたないフォントである可能性があります");
	} else if (target_obj.length > my_count) {
		var failure_count = target_obj.length - my_count;
		alert (target_obj.length + "箇所中、" + my_count + "箇所の数字を変換しました\n失敗した" + failure_count + "箇所については字形をもたないフォントである可能性があります");
	} else {
		alert ( my_count + "箇所の数字を変換しました");
	}
}

2011-04-02

[][][][][]InDesign正規表現では、後読みに繰り返しや選択が使えない

InDesign正規表現で、後読み(戻り読み)には繰り返しや選択が使えません。これはInDesign正規表現の仕様です。たとえば下図のようなキャプションの「●」だけに文字スタイルを適用したいときを考えてみましょう。

f:id:seuzo:20110402154921p:image

最初に思いつくのはこんな正規表現です。

(?<=^図\d?\d)●

しかし、マッチしません。「(?<=^図\d+)●」なども同様です。後読みの中では繰り返しは使えません。

正規表現クックブックのP95「後読みのさまざまなレベル」に少し解説がありました。ちょっとだけ引用します。

正規表現ソフトウエアは、テキストを左から右に探すように設計されてきました。多くの場合、後読みはちょっとしたハックとして実装されています。後読みに書き込まれた文字数を計算し、その字数分だけ後戻りし、後読み内のテキストと対象テキストを左から右に比較するのです。

要するに、基本的には後読み内のテキストは固定長でなければならないってことです。*1


後読み中の選択

今度は、選択を使ってこんな正規表現を試してみます。

(?<=^図\d\d|^図\d)●

これだと、2桁の数字の後ろの●だけにマッチします。1桁の後ろの●にはマッチしません。選択の順序を入れ替えて(?<=^図\d|^図\d\d)とすると、1桁の後ろの●にだけマッチすます。これもやはり選択が正しくサポートされていないのでしょう。*2

後読み中の選択をサポートしている言語、たとえばRuby1.9系*3で下記のように書いた時は正しく動作します。

p /(?<=^図\d\d|^図\d)●/ =~"図2●ほげほげ" #=>2
p /(?<=^図\d|^図\d\d)●/ =~"図2●ほげほげ" #=>2
p /(?<=^図\d\d|^図\d)●/ =~"図18●ほげほげ" #=>3
p /(?<=^図\d|^図\d\d)●/ =~"図18●ほげほげ" #=>3

解決策

とりあえず、こんな感じで解決してみました。

(?<=^図\d\d)●|(?<=^図\d)●

f:id:seuzo:20110402165021p:image

この正規表現スタイルをもった段落スタイルを適用すれば、●文字だけが文字スタイル適用になったのがわかります。

f:id:seuzo:20110402165022p:image

この場合だけに限れば、先頭文字スタイルを使っても解決するかもしれません。しかし、正規表現スタイルは先頭文字スタイルよりも格段に扱いやすいので、いろいろなバリエーションを習得すると吉でしょう。

*1:ただし、これは実装によるので、Javaや.NET Frameworkでは量指定子が使えるようです。

*2:選択が未サポートだとしたらエラーになるはずなので、もしかしたらバグかもしれない

*3:1.8系は後読みをサポートしません