【IndesignScripting】特定の文字列を検索してルビを振る
文字列の中から予め設定しておいた文字列全部拾ってそこにルビを振りたいのです。
InDesign用のExtendScriptの開発環境導入はこちらの記事で。
本文テキストの単位である「story」にアクセスする
InDesignScriptingのサンプルコードを見ていると、本文テキストにアクセスする際、Document>Page>TextFrame……という手順でアクセスしている者が多いのですが、よく読むとどれも最終的にStoryというオブジェクトにたどり着いています。
Story(ストーリー)というのは、「ひとかたまりのテキスト」です。「小説一本」「記事一本」と思って貰えればそれです。
文庫本のように全体で一遍の小説なら、(意図的に分割してなければ)1000ページあろうがストーリーは1つになっているはずですし、雑誌のように複数の文章が細切れに載っていれば20ページ程度でも10個くらいのストーリーがあるかもしれません。
InDesign使い慣れてる人なら「ストーリーエディタ」を知っているでしょうから、ストーリーという単位もイメージしやすいかもしれません。
今回は「本文全部の特定文字列にまとめてルビ入れたい」が目標なので、遠回りせず直接、「ドキュメント内にある全部のStory」を取得しちゃいます。
var document = app.activeDocument
var stories = document.stories
はい、とーれたっ!!
で、終了なんですが、これだと「空っぽのテキストフレーム分の空っぽのストーリー」とか、あとよく分からないんですけど、謎の「invalidなストーリー」が取れちゃったりして不便なので、ちょっとキレイキレイして必要なものだけ取得する関数を作ります。
var document = app.activeDocument
var valid_stories = get_valid_stories(document)
function get_valid_stories(document){
var stories = document.stories
var valid_stories = []
for(var n = 1; n <= stories.length; n++){
if(stories.item(n).isValid){
var story = stories.item(n)
if(!story.contents){continue}
valid_stories.push(story)
}
}
return valid_stories
}
アクセスできるstoryだけのリストを作って、stories全体をforでぐりぐり回して、アクセスできるものだけ拾っていきます。
invalidでないかどうかはisValidプロパティで取得できます。isValidがfalseのStoryオブジェクトの中身にアクセスしようとすると例外吐いて落ちてしまうので、Storyオブジェクトの中身にアクセスする場合必ずisValidでアクセスできるか確認してからにするとよさそう。
ルビを振りたい文字列を検索する
function search(keyword, story){
app.findTextPreferences = NothingEnum.nothing;
app.changeTextPreferences = NothingEnum.nothing;
app.findTextPreferences.findWhat = keyword;
var result = story.findText(false)
return result
}
検索の手順については
こちらのサイトを参考にしました。もうこのサイトないと何もできない。
ただ、サンプルだと最後にstoryの「置換」メソッドを使っていたので、今回は検索だけでいいんだけど、まあ検索メソッドもあるやろ、とオブジェクトツリーを上からしらみつぶししたらありました。findTextで探せます。
で、引数とかなんもわからないんですけどとりあえずそのまま実行したら「reverseorderをbooleanで寄越せ」と怒られたので、falseを渡したらちゃんと検索してくれました。
で、中身を覗いて見ると「Word」オブジェクトのリストが取れています。このWordオブジェクトをさらに覗いてみると、オッ、ルビ関連のプロパティがあるじゃないですか~~~
ルビを振る
function set_ruby(target_word_list, ruby_text){
for(var index = 0; index < target_word_list.length; index++){
var target = target_word_list[index]
target.rubyString = ruby_text
target.rubyType = RubyTypes.GROUP_RUBY
target.rubyFlag = true
}
}
というわけで、先ほど取得したWordのリストとルビを振りたいテキストをお渡しして、Wordオブジェクトのルビ関連のプロパティをセットしてあげれば完成です。
今回は用途の都合上、モノルビは不要で、全部グループルビが振れれば良かったので、その辺りの処理はかなり簡略化しています。
あとは
var document = app.activeDocument
var valid_stories = get_valid_stories(document)
var target_word_list = search("難読文字", valid_stories[0])
set_ruby(target_word_list,"なんどくもじ")
ってな感じで読んであげれば全ての「難読文字」という文字列にルビがつきます。
ただ、注意しないと行けないのが、「既にルビが振られている文字列にルビを振り直すことはできない」ので、
var document = app.activeDocument
var valid_stories = get_valid_stories(document)
var target_word_list_1 = search("文字", valid_stories[0])
set_ruby(target_word_list_1,"もじ")
var target_word_list_2 = search("難読文字", valid_stories[0])
set_ruby(target_word_list_2,"なんどくもじ")
みたいに、先にルビ振った文字列のを含む文字列に後からルビ振ろうとすると上手く行きません。こういう場合は先に長い方振っておけばいいのかな、多分。
どの文字になんというルビを振るかを取得する
function input_ruby_text(){
var base_text = input_prompt("ルビを振りたい文字列(親文字)")
if(base_text == null){return null}
var ruby_text = input_prompt("「" + base_text + "」に振るルビ")
if(ruby_text == null){return null}
var conf = confirm("「" + base_text + "」に「" + ruby_text + "というルビを振ります。よろしいですか?")
if(!conf){return null}
return new Ruby(base_text, ruby_text)
function input_prompt(message){
while(true){
input_text = prompt(message + "を入力してください","")
if(input_text == null){alert("処理を終了します"); return null} // cancelが押された
if(input_text == ""){alert(message + "を正しく入力してください"); continue} // 空白
return input_text
}
}
}
function Ruby(base_text, ruby_text){
this.base_text = base_text
this.ruby_text = ruby_text
}
ここまで作り込んだのでついでに、入力を受け取るロジックも作っちゃいましょう。各項目、空白なら再入力を促し、cancelが押されたら処理を終了します。
ついでのついでに、Rubyオブジェクトも作っておいて親文字とルビの情報の管理を楽にしておきます。
で、最後に
function run(){
var document = app.activeDocument
var valid_stories = get_valid_stories(document)
while(true){
var ruby = input_ruby_text()
if(ruby == null){return}
var target_word_list = search(ruby.base_text, valid_stories[0])
set_ruby(target_word_list,ruby.ruby_text)
if(!confirm("「" + ruby.base_text + "」に「" + ruby.ruby_text + "」とルビを振りました。続けますか?")){return}
}
}
呼びだし部分も終了を押すまで無限ループになるようにしておきます。
毎回入力したい文字とルビを入力する手間は残りますが、これでも相当楽になるので今回はこれで完成とします。やったね~~~
余談:そりゃあ公式リファレンスが無いわけだ
昨日の記事で、公式リファレンスないの嘘でしょって話をしていましたが、改めてオブジェクトツリーとにらめっこしてみたところ、PSやAIのそれに比べて、プロパティが嘘でしょってくらい多いことが分かりました。
こりゃ確かに正確なリファレンス作ってたら千ページくらいの弁当箱ができあがりそうです。
でも諦めないで欲しかったな!!!!
この記事が気に入ったらサポートをしてみませんか?