Saturday, November 13, 2010 / InDesign, DTP

InDesign CS5 テキスト選択範囲をXMLツリーに関連づけして結果をXMLファイルとして出力 jsx

InDesign上で選択しているテキスト範囲をXMLツリー(構造)に関連づけ&結果をXMLファイルとして出力するための jsx を書いたのでメモ。

Step1 軽く実験してみる

var doc = app.activeDocument;
var sel = doc.selection;
if( sel ){
	for(var i=0; i<sel.length; i++){
		alert( sel[i].contents );
	}
}
  • 複数箇所を選択されている場合に備えて、app.activeDocument.selection で得られる オブジェクトは配列になっているようだ。
  • app.activeDocument.selection は配列でその中身は Object になっている。いろいろなオブジェクトが選択される可能性がある・・・ということか。

Step2 コードを改良

Step1の コードはテキストを選択していることを暗黙の前提としているが、 もし別の何か…たとえばテキストフレームを選択した状態なら contents を呼び出しても意図した結果を得られないことになる。 もう少し安全なコードに変更してみる。

var doc = app.activeDocument;
var sel = doc.selection;
if( sel ){
	for(var i=0; i<sel.length; i++){
		if( sel[i] instanceof Text ){
			$.writeln( sel[i].contents );
		}
		if( sel[i] instanceof Character ){
			$.writeln( sel[i].contents );
		}
	}
}

事前に instanceof を使って選択しているオブジェクトが Text オブジェクトかどうか確認した上で、その内容を コンソールに出力する。

  • 一文字のみ選択した場合は Text オブジェクトではなく、Character オブジェクトが返る。

Step3 完成したコード

いままでのテストコードと以前調べた 自前の相互参照の作成その1 ... 指定した部分をXML要素としてXML構造に追加する のコードを流用すると以下のようになる。

function makeXmlEle( doc, tf , range ){
    // ルート要素を取得
    var eRoot = doc.xmlElements.itemByName("Root");
    
    var eTf = null
    if( tf.associatedXMLElement==null ){
        // ルート要素に tf 要素を作成して追加
        eTf = eRoot.xmlElements.add("tf",tf);
    }
    else{
        eTf = tf.associatedXMLElement;
    }
    
    // 指定範囲を tf要素 にxref要素として追加
    eTf.xmlElements.add("xref", range );
}

// main
var doc = app.activeDocument;
var sel = doc.selection;
if( sel ){
    for(var i=0; i<sel.length; i++){
        if( sel[i] instanceof Text ){
            if( sel[i].parentTextFrames.length == 1 ){
                var tf = sel[i].parentTextFrames[0];
                makeXmlEle( doc, tf, sel[i] );
            }

        }
    }
}

InDesign上でテキストを選択して、このスクリプトを動かせば XMLツリーに選択部分が関連づけされる。
→InDesign CS5 で XMLツリーを表示させるには、【表示 - 構造 - 構造を表示】

ちょっと疑問

Text オブジェクトには parentTextFrames メンバがあるのだがなぜ複数なのだろうか? Text オブジェクトが複数のテキストフレームに入ることがあるのか? テキストフレームが連結されているような場合に複数のテキストフレームが親として設定されるか? この辺りはもう少し調査が必要です。 (テキストフレームの連結をしている場合を想定しているのだろうか、と思う。)

Step4 XMLファイルとして出力

var doc = app.activeDocument;
var eRoot = doc.xmlElements.itemByName("Root");
eRoot.exportFile( ExportFormat.XML, new File( "test.xml" ),false )

new File('test.xml') では、デフォルトのディレクトリに出力される。 たとえば (/Applications/Adobe InDesign CS5/Scripts/XHTML For Digital Editions/ など) これが不都合な場合は、/User/foo/bar/test.xml のように絶対パスで指定するか、 以下のようにすれば、このスクリプトファイルの設置してディレクトリを取得できるので、それを使えばよい。

var currentDir = function(){ return File($.fileName).parent; };
$.writeln( currentDir().fullName );

補足) XMLを関連づけしたテキストを後から一括更新する場合の対策

案1) uuid を属性として埋め込んでおこう

UUID.js のようなUUIDを生成する javascript ライブラリ を使用して、 関連づけした xref 要素に属性として id を振ってしまいましょう。 uuid.js を追加した上で XMLに xref 要素を追加している部分のコードをちょっと修正。

var eXref = eTf.xmlElements.add("xref", range );
var attrs = eXref.xmlAttributes;
attrs.add("id",UUID.generate());

id を振っておけばこのXMLデータをInDesignの外側で書き替えてからもう一度InDesignに戻す場合に便利です。

案2) IDML書き出しして InDesignが割り当てたIDを利用する

この処理をしたInDesignドキュメントをIDML書き出して該当部分を調べてみると(Stories/以下を参照)

<XMLElement Self="di2i3i7" MarkupTag="XMLTag/xref">

となっている。Self属性としてIDとして使える値が割り振られているのでこれが使える可能性がある。 (わざわざUUID値を発行して id 属性として登録する必要はないかも)

ただし、Self属性値はそのファイル内ではおそらくユニークと思われるが、別InDesignファイルを含めてユニークになっていない可能性がある。その場合は、"InDesignファイル名+Self値" をユニークとして扱えばいいのだが、もしInDesignファイル名が変更されたら...ということを考えるとやはり自前で UUID を発行して id 属性として割り当てておいた方がより問題が少ないのかもしれない。