ちょっとのスクリプト書いたから誰得だけどねちねち解説するよ

GW進行中いかがおすごしでしょうか。
客商売向けの印刷物を多く扱ってると死にますね、はい。
休みの分だけ余計に働く、じゃ済まない量っすよこれ。。
というか低予算での引っ越し作業がぶつかって個人的にやばかったです。
帰ってから23時過ぎまでバイクで荷物積んで往復往復。

というわけで、久々にInDesignさわりました。ほんっと久々。
で、原稿がエクセルだったのでデータ結合で流し込みなど。
そしたら原稿にない「商品名、いいとこで改行しといてー」が発生しまして。
改行のきっかけ箇所の動向はある程度つかめたので、
ある程度ではあるものの自動化自動化。
でもこのスクリプト、ニッチすぎて配布しても誰の役にも立たない。

160427a

ので、趣向を変えて(ネタ切れとも言う)ソースをひもといた解説などしてみましょうか、ということで。あまり自慢できそうな書き方もしてないですけども。
ちょっと書いてみたい人とか、ちょっと直してみたい人とか、
なんかそういう人のタシになれば。ならんかもなー。
独習を始めたばっかりの頃の自分へ向け、噛み砕いて書いています。用語は最小限にとどめています(わからないから)
横文字箇所についてはJavascript入門サイトやお手元の入門書など参照してください。
ツッコミどころは多々あると思います。適宜訂正お願いしますー。
自分のわかってなさが露見するようでやや汗かいてますが
ほぼほぼほぼ我流なので拾えるとこだけ拾ってくださいまし。

スクリプトはこちら



id_niceReturn.jsx

名前なんかわかればてきとうでいいのだ。
MacOSX 10.9.5、InDesignCS6で使用しました。
うっかりしてましたがヨコ組み専用です。タテ組みではまともに動きゃしません。
それは次回やりましょう。
また、X座標をダシにしているので、アホみたいに回転のかかったテキストフレームでもきちんと誤動作します。

2016.04.28 15:38 あるふぁさんよりご指摘があり7行目に処理を追記
2026.05.02 縦組みでも動きましたorz 勘違い大将。



 

ダウンロードしてもらったスクリプトには、各行これでもかってぐらいコメントを散りばめてありますが。

以下、自分なりの解説をだっらだらとしております。ウチと心が通じていればきっと理解できる(横暴)

 



3行目:
var tar = app.activeDocument.selection;

ここが実質の書き始め行ですが、この行はtarという変数を宣言しつつ、InDesign前面ドキュメント選択オブジェクト群を入れます、という式です。ここで変数に入れる理由は、以降でいちいちapp.activeDocument.selectionと書くのがめんどくさいからです。
appはアプリケーション(今回はInDesign)、activeDocumentは前面にいる書類、selectionは選択ハニー。

var は変数を宣言する時の物です。ヨソのスクリプトで同じ名前の変数があったとしてもそれはそれ、これはこれ、というやつです。
ここでvarを省略すると、InDesign上の他のスクリプトで別の意味で使用されている変数tarを書き換えてしまう恐れがあります。んなもん知るかよって言いたいとこですが、まあ、まあ。
プログラム(ここではInDesign)全体で参照できる物をグローバル変数、関数内(ここではひとつのスクリプトファイルがほぼ同義)だけで参照できる変数をローカル変数といいます。
変数名の競合を回避するためのクロージャ関数化というものがありますが、それは後述(と言っておいてそのまま忘れるやつ)。
tarは任意の変数名ですが、命名はてきとうです。自分でわかりやすければいいです。targetの略のつもりですが、tgtと書く人もいます。まあ自由です。予約語(スクリプトの命令や既存オブジェクト名)に名前がかぶらなければなんでもいいです。
えらい人の例文では、この任意の変数名も英語っぽい、いかにももっともらしい字面だったりするので、これはこういう決まりがあって、この通りにしないとちゃんと動かなかったり、笑われたり馬鹿にされたりするんだ、と思っていた時期がありました。

selectionは配列(Array)でできています。
オブジェクトがプチクリームパン1個だとすると、配列は、あのー…袋にプチクリームパンが5個ぐらい入ったやつ、みたいな…。
配列内の1つ1つのオブジェクトを、エレメントと呼ぶらしいです。今回のエレメントはドキュメント上で選択されたオブジェクトです。
配列のエレメントがさらに配列でできている物を二次元配列と呼ぶらしいです。プチクリームパンの袋が配列、それが入った段ボール箱が二次元配列、みたいな…。160427c
三次元、四次元などがあるかは知りません。そこはこだわらないほうがいいです。
仮に配列tarの2番目のエレメントをさわる時は、tar[1]と書きます。エレメントのインデックスは0から始まるためです。
選択オブジェクトの配列は、CS4以前は階層順、CS5以降は選択順となります。一括ドラッグ選択した場合は階層順となりますが、Shift+ドラッグ選択を複数回に分けて行った場合は並びが違ってくるのでご用心。


4行目:
if ( tar.length == ) exit ( );

分岐処理です。条件によって、やるやらないを振り分けます。
if (式) hoge; は、( )内の式の値がならhoge;を実行します。なら実行せず次へ行きます。
( )内には真偽値(Boolean、trueかfalseか)が出るような式を書きますが、
式の値が数値である場合は、0以外ならtrue、0ならfalseとして動作してくれます。例えマイナス1でもtrueです。
変数tarは現在、配列オブジェクトです。配列はlength(長さ)というプロパティを持っています。
プチクリームパンが何個入りかを調べるものです。ここでは選択オブジェクトの個数がtar.lengthというわけです。
選択されているオブジェクトがひとつもなければ(lengthの値が0なら)ここで終了したいので、配列tarのエレメント数が0と同じか、を比較して真偽値を得ます。
exit( )はスクリプトを終了する、メソッドという物です。18行目で後述する関数と同じような物です。後ろのパーレーン( )は必須です。忘れると動きません。
このexit( )は、PhotoshopにもIllustratorにもありません。べんりです。やったぜInDesign。
で、選択オブジェクトが1つでもある場合、次の処理にいけちゃいます。


6行目:
var obj, myFind;

なんか5行目に空行を入れていますが、自分で見やすくするためにてきとうに空けたやつです。そこは自由です。何行でもどうぞ。
この行では、のちのち使う2つの変数をあらかじめ宣言しています。
objはObjectの略のつもりですが、my〜は既存オブジェクトと名前がかぶっているかを調べるのがめんどくさい人がよくやるやつで、たいがい頭にmyをつけておけばかぶらない、という事らしいです。
varのあとに変数名を次々”,”で区切って宣言していけば、それぞれをvar付きで宣言したことになります。


7行目:
app.findGrepPreferences=NothingEnum.nothing;

※この行はあるふぁさんよりご提案いただき、まるもらいの上あとで追加しました。ありがとうございます。
GREP検索設定の一切を未定義化します。検索文字スタイルなど設定してあった場合はこれをしておかないと文字列だけマッチしても拾ってもらえなくなります。
とのことです。ごもっとも。。やられましたなあw

 


8行目:
app.findGrepPreferences.findWhat = “(?<=[! 産」)])[^ 産」\n\r]|[(「]”;

やや長いですが
この行はInDesignGREP検索設定検索文字列を定義しています。正規表現部分は、改行を入れる「いいところ」を探すやつです。
〜Preferense というのは、たいがい〜設定というやつです。今回は複数形のPreferencesですけど。


10〜16行目:
while ( tar.length ) { }

ループ文ってやつです。( )内の条件がtrueであるうちは延々{ }内(16行目まで)の処理をします。
{ }内でbreak;と書いておくと強引にwhile( )ループから抜ける(その先の処理にかかる)ことが可能です。
今回は{ }内でtar.lengthに変化を与える処理があり、配列tarのエレメント数が0になったら式の値はfalseとなりループを抜ける仕組みです(4行目の解説参照)。今回のスクリプトはこのループを抜けたら終了します。
ヒマな人は17行目に alert (“処理完了でーす”); とか書き足しておくと少しかわいいかもしれません。知らないけども。


11行目:
obj = tar.shift( );

shift( )は、配列から先頭エレメントをひとつ抜き出して代入するメソッドです。4行目にもメソッドexit( )が出てきましたが、あちらはオブジェクトにくっつけないで単体使用するもの、こちらは配列クラスのメソッドです。shift( )により配列tarからは先頭エレメントがなくなります。だんご3兄弟から長男がいなくなります。
このほか、三男がいなくなるpop( )、長男の前に1人追加するunshift( )、三男の後ろに四男を追加するpush( )などがあります。
160427d
(↑クリックでオリジナルサイズ見れます)
前行で参照していたtar.lengthは、ここで-1される事になり、16行目まで処理を続行したら10行目に折り返し、while( )内の式の値はfalseとなり、ループを抜ける、という流れ。
ここでは宣言済み(で中身まだナシ)の変数objに、配列tarの先頭エレメントを抜いて代入しています。これで変数objには選択オブジェクトの1つ目が入りました。この先objに対して何かを行うと、その選択オブジェクトに対して処理がされます。
それに対して、selection配列を参照した配列tarから1つ抜いてもselection配列は元のままです。選択が解除されたりはしません。
この違いについては、カラダで覚えるか、えらい人にききましょう。ウチは前者です。。


12行目:
if ( ! obj.hasOwnProperty ( “parentStory” ) ) continue;

if文2度目の登場。
( )内の先頭についた”!”は、式の演算結果の真偽値を逆転します。trueとfalseが逆になります。うそが本当に。
hasOwnProperty( )は、選択オブジェクトobjが”parentStory”というプロパティを持っているかを真偽値で返します。parentStoryはストーリーオブジェクトです。選択オブジェクトがテキストフレームの場合はobj.parentStoryと書くとそのストーリーを参照することができます。ここではそのストーリーオブジェクトが存在するかを見ています。存在しなければ選択しているのはテキストフレームではないと見なし、処理は行わずほっといて次に行きたいからです。
ただし先頭に”!”があるので、この場合は「選択オブジェクトにストーリーが存在しないか」となります。ちゃんとなかった場合、後ろのcontinue;が実行されます。
continueは、以降の処理(13〜15行目)をスルーしてループを続行します。ここでは10行目に飛びます。
もし”!”がなかったら、どう書くことになるか、というと
if ( obj.hasOwnProperty ( “parentStory” ) == false ) continue;
または
if ( obj.hasOwnProperty ( “parentStory” ) ) {
13〜15行目の処理
}
などと、少し冗長になったり、入り組んでしまったり。まあこの例文だと、ちょっとしか違わないじゃんって自分でも思いますが…13〜15行目がもっともーっと長い内容の場合とか、これが入れ子になって連続した場合、ちょっとかなり見たくない感じになってしまいます。まあ、たまにやるけど。


13〜15行目:
if ( obj.lines.length > obj.paragraphs.length ) {
pepsi( obj );
}

前行のif ( ! obj.hasOwnProperty ( “parentStory” ) ) continue; に引っ掛からなかったオブジェクト、つまりテキストフレームを相手にしています。
テキストフレーム内の行数 > テキストフレーム内の段落数であれば、objを関数pepsiで処理。
このスクリプトの目的は「いいとこで改行」なので、行の折り返しがある(行数と段落数が等しくない)物にしか用事がないわけです。
関数名pepsiは、これもてきとうです。ペプシ好きです。


18〜25行目:
function pepsi ( obj ) {

はい出ました関数。関数とメソッドの違いについてはけっこうめんどくさい論争に見えたのでウチは関わりません。同じだと思っていいんじゃないかなあ…
関数名 (引数)
という書き方をします。渡すべき引数がなくても( )は必須となります。
最初に覚えさせられるalert (“Hello World!”); も、alert( )が関数、文字列”Hello World!”が引数です。返り値(戻り値ともいう)を返す物もありますが今回は用事がないので触れません。
14行目で関数pepsiに引数objを渡しました。pepsiの中でそのobjを処理します。
function は関数の宣言です。function文はスクリプト内のどこに置いても構いませんが、今回は下のほうに集めてみました。


19〜24行目:
for ( var i = obj.lines.length – 2; i >= 0; i – – ) {

ループの花形、for文。
for (最初に実行される式; 条件式; 繰り返し時に実行される式) { 条件合致時の処理 } と書きます。
変数iを定義し、そのiの値が条件を満たす間{ }内を実行する、繰り返される時iの値が変動する、という仕組みです。

ここでは末尾から先頭に向けて処理しています。スクリプトの処理内容がテキストを改行していく物や字数が増減する物の場合、先頭順で処理するとあとの様子がどんどん変わっていき不具合が起きやすいので。テキストがあふれたりするとフォローがめんどくさいし。

以下、各式をちょっとこまかく

var i = obj.lines.length – 2;
テキストフレームobjのlinesコレクションのlength -2 を変数iに代入しています。配列およびコレクションのエレメントは0番目から始まるため、行数-2、つまり2行あれば0行目、3行あれば1行目から始めたい、ということになります。

ここで「じゃテキストが1行しかなかったらマイナス1行目ってなるじゃんすか!エラー出るよエラー!」などと思われた聡明な方は、13行目を見直しましょう。
行数が段落数より多い物しか、この行まで来れないことになっています。空っぽのテキストフレームでも行と段落は必ず1つある事になっているので、ここまで来たからには最低でも2行はあるわけです。やーいやーい。

i >= 0;
変数iが0以上、が条件式。0行目も処理したいので0も条件に含んでいます。

i – –
変数iの値を1つずつ減らします。2行目を処理したら次は1行目、0行目、と処理します。
i = i – 1; とわかりやすく書いてもかまいません。
– – i というのもありますが、ここでは適しません。それがなぜなのか気になったら調べてみましょう。

Linesクラスはコレクションクラスといい、アプリケーションに依存した物です。内包アイテム(エレメント)はLineオブジェクトだけとなりす。配列変数ではありません。ぱっと見は似てるし使い方も半分は同じなんですが別物です。


20行目:
myFind = obj.lines[i].findGrep ( );

冒頭で宣言したまま忘れかけていた変数myFindの登場。いろいろ書き直しているうちに関数の外で変数宣言してしまっていますが、関数の中でしか使わない変数は関数の中で宣言したほうがいいです。悪い見本ですが、なおしません。
変数myFindに、選択テキストフレームobj内のi行目のテキストを対象にGREP検索した検索結果を入れています。
findGrep( )はInDesignの「検索と置換」ウィンドウの「検索」ボタンと同様ですが、検索結果は選択ハニーにはならず、Textオブジェクトの配列が返り値として変数myFindに入れられます。


21〜23行目:
while ( myFind.length ) {

10行目と同じ、配列myFindのエレメント数が0になるまでのループ。


22行目:
drPepper ( obj.lines[i], myFind.shift ( ) );

2つ目の関数処理です。ドクターペッパー好きです。
今度は引数が2つ。変数objのi行目と、配列myFindの先頭エレメントを抜き取ったもの。
obj.lines[i]はLineオブジェクト、myFind.shift( )は配列となった検索結果の先頭にいるTextオブジェクト。
関数を使う理由については、まあ気分で。。
・処理が長くなって見づらくなったので、ここからここまでを関数にして外にどけてしまえ、
・ここからここまでの処理は他の所でも使うから関数にして使い回そう、
などなど。
経験上、一度できあがったスクリプトを一部関数化するときは変数の取り回しなどで人的エラーを起こしやすいのでご注意。※と書いたそばから、ほんとに自分で超ミスッてて大幅に修正しました…


27〜39行目:
function drPepper ( obj, chr ) {

引数が2つありますが、実は関数内の引数と関数呼び出し時の引数は変数名がぜんぜん違ってても関係ありません。何番目の引数か、で扱われます。処理内容によっては引数の個数が合わなくても平気だったりします。
第一引数のobjは、この関数の外のobjと名前が同じですが別物となります。この関数内だけのobjです。
第二引数chrもとくにvar宣言していませんが、引数で宣言した変数名は完全にこの関数内のローカル変数となります。varいりません。〈例文1〉
これに対し、関数外で宣言済みの変数名を関数内でvarなしで使用すると外のやつが書き変わってしまいます。varであらためてローカル変数を宣言した場合は別物として定義されます。〈例文2〉

———————
〈例文1〉関数drPepperの引数名objと関数外の変数objが別物である事の検証
function func ( a, b ) { //引数名にaとbをわざと使用
alert ( a + “, ” + b ); //関数内におけるaとbの正体をあばく–>”4, 5″と出る
}

var a = 2, b = 3; //変数aとbを定義
func ( 4, ); //関数funcに、a、bとは無関係な数値を渡す
alert ( a + “, ” + b ); //関数処理から抜けたら、同様にaとbの正体をあばく–>”2, 3″と出る
———————
〈例文2〉関数の構文内でも外にある変数は生きてるからさわれるんだぜ、の検証
function func1 ( ) { a = 1; } //変数aを1にしちゃうだけのぶっきらぼうな関数その1
function func2 ( ) { var a = 3; } //varをつけた関数2。aを3にしようとしている

var a = 2; //変数aにてきとうな値を定義
func1 ( ); //関数1を処理
alert ( a ); //aの値を出してみる –>”1″と出る
func2 ( ); //関数2を処理
alert ( a ); //aの値をまた出してみる –>やっぱり”1″と出る

これを利用してスクリプト全体をまるっと無名関数にしてしまうことでヨソのスクリプトとの変数名かぶりを回避するのがクロージャという物らしいのですが(←忘れてたので書き足した)、なんとなくイラッとするのでウチはやりません。なんとなく。


28〜38行目:
if ( chr ) {

chrは20行目で行ったGREP検索結果です。検索対象が検索文字列にひとつもマッチしなかった場合、配列myFindのエレメントはundefined(未定義)となります。undefinedはこれはこれでひとつのオブジェクトなのですが、条件式の場合はfalseと同義に扱われます。なので、ここではundefinedでなければ以下の処理を行う、ということになります。
12行目と同様にif (!chr) {return;}としてもよかったのですが。。にんげんだもの。


29行目:
var nextLine = obj.parentStory.lines.nextItem ( obj );

これより先、引数として持ってきたLineオブジェクトの次の行をひんぱんに参照するので、ちょっと長くてイヤなので変数に入れることにしました。
Lineオブジェクトobjを内包するストーリーの、行コレクションのうちobjの次の物、という内容です。


31行目:
var myEnd = Math.max ( nextLine.insertionPoints[-1].horizontalOffset, nextLine.insertionPoints[-2].horizontalOffset );

前行で宣言した変数nextLine内の挿入点のうち、末尾の物と後ろから二番目の物の水平座標を比較して、より大きい方を変数myEndに入れています。

InsertionPointsは挿入点コレクションです。変数nextLineはここでしか使っていないので、
var nextLine = obj.parentStory.lines.nextItem(obj).insertionPoints;
としてしまえばこの行の.insertionPointsがすっきりしたかも知れませんですな。いや逆に伝わりにくいかと思って。。
[-1]は、コレクションのアイテム(配列でいうエレメント)を末尾から参照します。[-1]で一番後ろ、後ろから2番目なら[-2]と書きます。[-0]はありません。配列オブジェクトにはこの書き方はできません。ちなみにこれもPhotoshopやIllustratorのコレクションクラスではできません。やったぜInDesign。

Mathは数学っぽい計算ができるクラスです。累乗とか平方根とか。max( ) は2つの引数のうち大きい物を返り値にするメソッドです。逆のmin( )とかもあります。まあ比べるやつです。

挿入点2つの座標を比較した理由というのが、行末に改行(改段)がいた場合、末尾の挿入点イコール次行先頭の挿入点となってしまい、座標が行頭になってしまっていたからです。比べてでっかい方を使います。


33〜36行目:
if ( chr.insertionPoints[0].horizontalOffset >= myEnd ) {

検索結果文字列の挿入点0番目と、Lineオブジェクトobjの次行の行末、それぞれの挿入点の水平座標を比較しています。これを調べておかないと、せっかく改行しても下の行でまた折り返しが発生してしまう。下行の行末よりも後ろの座標で改行したいのです。まあ禁則処理や均等割付如何で狙った通りにはいかないような気もしますが。。今回は商品名なので左揃えです。160427b


34行目:
chr.insertionPoints[0].contents = “\r”;

contentsは文字列。chr.parentStory.contentsと参照すれば対象テキストフレームの内包ストーリーの文字列がすべて取得できます。ここでは挿入点オブジェクトのcontentsに文字列”\r”を入れています。”\r”は改段記号ですね。えー、挿入点って文字と文字の間じゃないの、なんでContentsプロパティがあるの、などという疑問については、わかりません。。こういう用途で便利ではあるですが。


35行目:
myFind = [ ];

[ ]は中身カラッポの配列オブジェクトです。もうこの行はいちど改行したので、もうこれ以上の処理しません。なので21行目のループから抜けさせるためにわざとカラッポにしています。



とまあ、解説は以上。わかりにくいでしょうか。でしょうな。でしょうよ。。
次回、これを改良してタテ組にも対応させるとします。
体力があれば、その次は必要以上に回転のかかったテキストフレームにも対応したい。してみたい。してください。

 

でーす。

2 thoughts on “ちょっとのスクリプト書いたから誰得だけどねちねち解説するよ

  1. […] 前回の反響、ひとことで言うと「おー、やったー! なんかわかりそうな気がする(けどわからん)!」 手前勝手に書き殴ったスクリプトを一行ずつ解説しても、全体がなんでそんなカ […]

コメントを残す

メールアドレスが公開されることはありません。

*