Monday, July 12, 2010 / InDesign, DTP

ExtendScript 自前の相互参照の作成その1 ... 指定した部分をXML要素としてXML構造に追加する

InDesign CS4 からは相互参照機能が追加された。 しかし、これは改行を含んだ情報を相互参照できないとか、いま個人的にカタログDTPにおいて直面している問題を解決してくれない。そこで、自前の相互参照機能を jsx を使ってつくろうという話。

主な目標

  • 相互参照ソースとしてテキストフレーム内のコンテンツをまるごと(改行も含めて)扱えること
  • 相互参照をタグ付きテキスト機能を使って実装するので、相互参照ソースで設定したスタイルやルビなどを生かせること

Step1 xref要素を作成してXML構造に追加

とりあえずテキストフレームが一個だけある ind 文書に対して次のスクリプトを動かすと、テキストフレームの先頭部分を XML構造にxref要素として追加できる。

//
// 狙った部分をxref要素としてXMLツリーに追加する.
// InDesign CS5
//

// テキストフレームが一つ存在する indd 文書を想定
var doc = app.activeDocument;
var tf  = doc.textFrames.firstItem()

// ルート要素を取得
var eRoot = doc.xmlElements.itemByName("Root");

// ルート要素に tf 要素を作成して追加
var eTf   = eRoot.xmlElements.add("tf",tf);

// テキストフレーム中の段落を取得
var p = tf.paragraphs.item(0);

// 段落の先頭の範囲オブジェクトを生成
var aInsertionPoint = p.insertionPoints.itemByRange(0,0);

// その部分を tf要素 に追加
var eXref = eTf.xmlElements.add("xref", aInsertionPoint );

Step2 相互参照する場所を指定する、そこの位置を特定する

Step1 のように決めうちでテキストフレームの先頭段落の先頭を相互参照に…では話にならないので、相互参照を入れたい場所を指定できるようにしたい。 そこで、相互参照を入れる位置を {xref:id文字列} ... というように波括弧+xref:で囲むというローカルルールを追加することにした。 相互参照はキーとなるID文字列で管理するつもりなのでテキストフレーム中に以下のような記述をすると、その部分がxref要素になるようにしたい。

我が輩は{xref:あなたの選んだ動物}である。

このように記述し、 あなたの選んだ動物 という部分がIDにするというローカルルールを導入します

まずはその位置を探し出す ExtendScript.

//
// 波括弧(Brace)で囲まれた文字列を取り出す.
// InDesign CS5
//

// 波括弧開始位置を探して返す
function searchOpenBrace( p, pointer ){
    for(var j=0; j<p.contents.length; j++){
        if( p.contents[j] == "{" ){
            return j;
        }
    }
    return -1;
}

// 波括弧終了位置を探して返す
function searchCloseBrace( p, pointer ){
    for(var j=pointer; j<p.contents.length; j++){
        if( p.contents[j] == "}" ){
            return j;
        }
    }
    return -1;
}

var doc = app.activeDocument;
var tf  = doc.textFrames.firstItem()

for(var i=0; i<tf.paragraphs.length; i++){
    var p = tf.paragraphs.item(i);

    var startPoint = searchOpenBrace( p, 0 );
    if(startPoint!=-1){
        var endPoint = searchCloseBrace( p, startPoint+1 );
        if( endPoint!=-1 ){
            // 波括弧で囲まれた範囲を取り出す(波括弧は除外)
            var range = p.insertionPoints.itemByRange(startPoint+1,endPoint);
            alert( range );

            // 波括弧で囲まれた文字列を取り出す(波括弧は除外)
            var str = "";
            for(var j=startPoint+1; j<endPoint; j++){
                str += p.contents[j]
            }
            alert( str );
        }
    }
}

Step3 成果をまとめた makeXrefEle.jsx

ステップ1とステップ2の成果をまとめて、{xref:ほげほげ} した部分をxref要素としてXML構造に追加します。

//
// 波括弧(Brace)で囲まれた文字列を取り出すし、その部分をxref要素にする.
// InDesign CS5
//

// 波括弧開始位置を探して返す
function searchOpenBrace( p, pointer ){
    for(var j=0; j<p.contents.length; j++){
        if( p.contents[j] == "{" ){
            return j;
        }
    }
    return -1;
}

// 波括弧終了位置を探して返す
function searchCloseBrace( p, pointer ){
    for(var j=pointer; j<p.contents.length; j++){
        if( p.contents[j] == "}" ){
            return j;
        }
    }
    return -1;
}

function makeXmlEle( doc, tf , range , xref_id ){
    // ルート要素を取得
    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要素として追加
    var eXref = eTf.xmlElements.add("xref", range );

    // 相互参照IDを属性として埋め込む
    var attrs=eXref.xmlAttributes;
    attrs.add("id",xref_id);
}

function processTf( tf ){
    for(var i=0; i<tf.paragraphs.length; i++){
        var p = tf.paragraphs.item(i);

        var startPoint = searchOpenBrace( p, 0 );
        if(startPoint!=-1){
            var endPoint = searchCloseBrace( p, startPoint+1 );
            if( endPoint!=-1 ){

                // 波括弧で囲まれた文字列を取り出す(波括弧は除外)
                var str = "";
                for(var j=startPoint+1; j<endPoint; j++){
                    str += p.contents[j]
                }

                if( str.indexOf("xref:") != -1 ){
                    var xref_id = str.substring(5);

                    // 波括弧で囲まれた範囲を取り出す(波括弧を含める)
                    var range = p.insertionPoints.itemByRange(startPoint,endPoint+1);
                    makeXmlEle( doc, tf, range , xref_id );
                }
            }
        }
    }
}

var doc = app.activeDocument;
for(var i=0; i<doc.textFrames.length; i++){
    var tf = doc.textFrames.item(i);
    processTf( tf );
}

ポイント

  • xref要素を直接 Root要素 に追加できない(ようだ)
  • したがって、XPATH的に表現すると /Root/tf/xref として追加する
  • 一つのテキストフレーム内に、複数の{xref:ほげ}がある場合に備えて tf.associatedXMLElement で既にXML要素に結びついているかを事前にチェックしている

注意点 不具合 など

makeXrefEle.jsx を繰り返し実行して適用するとおかしなことになります。 自前の相互参照の作成その3 で説明している revertXref.jsx を実行してから、makeXrefEle.jsx を実行すれば問題ないです。 (makeXrefEle.jsx を実行する前に常にrevertXref.jsx を実行するようにコーディングしておけばよい、という話です。)

このコードは、 ひとつの段落に複数の相互参照を設定した場合に対応していません。