2009年10月20日火曜日

JavaScript と、Perl と、Groovy の split

ある文字でデータを分けて配列にするsplit。例えば JavaScirpt で
var text = "AAA<tag>BBB</tag>CCC";
var ary = text.split(/<.+?>/);
$.writeln(ary.toSource());
//ESTK の場合。 fireBug ならば console.log(ary);

["AAA", "BBB", "CCC"]
と表示される。
それが、splitの中で使う正規表現をキャプチャして
var ary = text.split(/(<.+?>)/);
とすると結果が
["AAA", "<tag>", "BBB", "</tag>", "CCC"]
と返る!このことを自分は今日まで知らなかった。

perlでも同じように返る。
my $text = "AAA<tag>BBB</tag>CCC";
my @ary = split(/(<.+?>)/,$text);
foreach $token(@ary){
print $token . "\n";
}
AAA
<tag>
BBB
</tag>
CCC

Rubyでも同じらしい。じゃあGroovyだと思い、
def text = "AAA<tag>BBB</tag>CCC"
def ary = text.split(/(<.+?>)/)
println ary
とやってみたが、
[AAA, BBB, CCC]
としか返らない。あれ?

Groovy の split は Java の split で、そういう風には使わないっぽい。
じゃあ tokenize では?
def text = "AAA<tag>BBB</tag>CCC"
def ary = text.tokenize(/(<.+?>)/)
println ary
[AAA, tag, BBB, /tag, CCC]
……。
タグの<>がなぜか外れてしまう。むー、よくわからん……。


10月21日追記。
Groovy の ソースの tokenize を読んでみた。DefaultGroovyMethods.java の 6627 行目あたりに
public static List tokenize(String self, String token) {
return InvokerHelper.asList(new StringTokenizer(self, token));
}
つまり、渡された引数が String でも GString でも、そのまま StringTokenizer に渡して List で返しているわけか。(ただの区切り文字の集まりとして読んでいるから、< と > がひっかかって分割されて、< > 自体は消えている)
GString でキャプチャされてた時にデリミタを返すのは難しいにしても、だったら tokenize(String, Boolean) でトークンを返すのは欲しいなぁ。

2009年10月17日土曜日

Illustratorのリンク画像を相対パスで使うスクリプト

Illstratorのリンク画像へのパスは絶対パスで保存されています(使っている人でも知らない人が多い!)。
Illustratorで、貼り込み画像をリンクとして使いたい時、よく下の階層にフォルダを作ってまとめてあるのを見ますが、それだと上のフォルダ名が変わったり、ドライブを移動したりするとリンク切れするんですよね。
それならまだいいんですが、フォルダごとコピーして、コピー側を開くと元のフォルダへのリンクが残っていて、画像を修正したと思ってても元に戻っちゃってたりとか……。

それらを解消できないかと思ってちょっと書いてみました。相対パスを画像に保存しておくスクリプト。
作業したあと、必ずこのスクリプトをかけておけば有効かもしれない。

【相対パスを保存_再リンク.jsx】
#target "Illustrator"
if(app.documents.length>0){
var actDoc=app.activeDocument;
if(actDoc.path==""){
//未保存ならばpathがない
alert("ファイルを保存して下さい");
}else{
var reLinkLog="";
var saveLinkLog="";
//ドキュメントの親フォルダのパス
var docPath=File.decode(actDoc.path);
//アイテムをループ
for(var i=0;i<actDoc.pageItems.length;i++){
//もしリンク画像なら
if(actDoc.pageItems[i].constructor.name=="PlacedItem"){
var item=actDoc.pageItems[i];
//メイン処理を実行
var log=main(item,docPath);
if(log[0]){reLinkLog+=("\n"+log[0])};
if(log[1]){saveLinkLog+=("\n"+log[1])};
};
};
//リンク修正アラート
if(reLinkLog!=""){
app.redraw();
alert("以下のリンクを修正しました。"+reLinkLog);
};
//リンク保存アラート
if(saveLinkLog!=""){
alert("以下の相対パスを保存しました。"+saveLinkLog);
};
};
};

function main(item,docPath){
//メイン処理
var ary=[undefined,undefined];
//「name」の値があれば
if(item.name!=""){
var beforeFilePath=docPath+"/"+File.decode(item.name);
var beforeFile=new File(beforeFilePath);
if(beforeFile.exists){
var flag=false;
try{
//ファイルが相対パスの場所と違っていたら
var nowFilePath=File.decode(item.file);
if(nowFilePath!=beforeFilePath){
flag=true;
}else{};
}catch(e){
//ファイルがなければ
flag=true;
};
//flagがtrueならばリンク貼り直し
if(flag){
item.file=beforeFile;
ary[0]=beforeFilePath;
};
};
};
//nameにファイルの相対パスを入れる
try{
var relativePath=item.file.getRelativeURI(docPath);
item.name=relativePath;
ary[1]=File.decode(relativePath);
}catch(e){};
return ary;
};
IllustratorのPlacedItemオブジェクトの、name属性に相対パスをそのまま保存しています。
なので他のnameを使うスクリプトと併用はできません。
MacOSX 10.5.8、 Illustrator CS3にて動作確認。

【使い方】
  1. Illustratorを編集して、最後に保存する前に、あらかじめこのスクリプトをかけておきます。
  2. ファイルを開いた時に『「〜」というリンクファイルが見つかりません』というダイアログが出た場合は、全て「無視」を選択します。
  3. 開いた後再びスクリプトを実行します。

2009年10月15日木曜日

XmlSlurperでどの階層にあるかわからない要素を取り出す。

ようやくわかったのでメモ。
def myXml = '''<?xml version="1.0" encoding="UTF-8" ?>
<root>
<a>
<target>ここだよ!</target>
</a>
<a>
<b>
<target>ここよ!</target>
</b>
</a>
<a>
<b>
<c>
<target>ここです!</target>
</c>
</b>
</a>
</root>'''

def node = new XmlSlurper().parseText(myXml)
node.'**'.grep{it.name() == 'target'}.each{
println it
}

2009年10月12日月曜日

JavaScriptの{}の使い方のこと。

この間スクリプトでひさしぶりにswitch文を書いていたとき、そういえばswitch文てあんまり使わないなと思った。
それは、自分がいまの会社でスクリプトを書き始めたすごく最初の頃に、「ある変数が持つ数値に対して、ある文字を返す」処理、たとえばv=1ならばж、2ならばз、3ならばи…といったふうな処理を書こうとしていて、たしかこんな感じだったと思う。
function test(v){
var cr;
if(v){
switch(v){
case 1:cr="ж";break;
case 2:cr="з";break;
case 3:cr="и";break;
case 4:cr="й";break;
case 5:cr="к";break;
case 6:cr="л";break;
case 7:cr="м";break;
case 8:cr="н";break;
case 9:cr="п";break;
default:cr="";
};
};
return cr;
};

var no=4;
alert(test(no));
そのコードを見た先輩が、「それだとコードが長くなって見にくくなるから」と言って、こんな風に書き直した。
function test(v){
 var cr="";
 var pat={1:"ж",2:"з",3:"и",4:"й",5:"к",6:"л",7:"м",8:"н",9:"п"};
if(v){cr=pat[v]};
return cr;
};

var no=4;
alert(test(no));
???
JavaScriptのオブジェクトは、
var obj=new Object();
と作るけれど、単純にパラメータを保持するだけなら
var obj={};
でよい。そして、JavaScriptのオブジェクトはほとんどハッシュマップなので、obj[key]で欲しい値が返るわけ。

この件で、{}の使い方と「短いほうが良いコード」という考え方を教わってから、対応する値が1対1ならばほとんどswitch文は書かないようになった。
そのせいで時々switch文を書かなきゃいけない時に、使い方を忘れてたりする。

2009年10月11日日曜日

ローカル上で、XmlSlurper で xhtml を読み込む時はフィーチャーを指定すること

Groovy で、xhtml を XmlSlurper で処理しようと思い、
def src = args[0]
def htmlFile=new File(src)
String str = htmlFile.getText("UTF8")

def node = new XmlSlurper().parseText(str)
//以下略
のようにやってみたら

Caught: java.io.IOException: Server returned HTTP response code: 503 for URL:
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
at xmlSlurperTest.run(xmlSlurperTest_2.groovy:4)
というエラーが出て止まる。
ローカル上でやっているので、xhtml の DTD を読みに行って503ってことらしい。
隣に居たO部長に
「〜となるんですけど、DTD を読みに行かずにする設定ってどうやるのか知りませんか?」
「ヘッダ読み込まなきゃいいんじゃないの?」
そうですけど…
「もういいです自分で調べます」
って言って Groovy の JavaDoc 見に行こうとしてたら方法をメッセンジャーで送ってもらえた。

def src = args[0]
def htmlFile=new File(src)
String str = htmlFile.getText("UTF8")

def parser = new XmlSlurper()
//↓フィーチャーの指定。これを入れないと DTD を読みにいってエラーになる。
parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
def node = parser.parseText(str)
//以下略
フィーチャー?
Java 入門 | DOM (Document Object Model)
groovy.util.XmlSlurper は org.xml.sax.helpers.DefaultHandler のサブクラスで、基本的にはSAXみたい。
デフォルトで true なのか。ふむ。

2009年10月5日月曜日

スクリプトでInDesignの文字組みアキ量設定を作る。

まず、自分の文字組みアキ量設定を作ります。
※サンプルに、n-yujiさんの
僕が勝手に考える理想の(というか妥当な)InDesign文字組版。 - 遠近法ノート
の設定をお借りしました。

文字組みを設定した .indd を開いた状態で、ExtendScript Toolkit から、以下のスクリプトを実行。
var myMojikumi=app.activeDocument.mojikumiTables.itemByName("Y汎用括弧半角v2.0/0808");
$.writeln(myMojikumi.basedOnMojikumiSet);
$.writeln(myMojikumi.overrideMojikumiAkiList
.toSource().replace(/\], \[/g,"],\n ["));

すると、こんな感じでコンソールに表示されます。

わー。
これをスクリプトに組み込みます。
var actDoc = app.activeDocument;
var myMojikumi=actDoc.mojikumiTables.add("Y汎用括弧半角v2.0/0808");
//MojikumiTableDefaults.LINE_END_ALL_ONE_HALF_EM_ENUM
myMojikumi.basedOnMojikumiSet = 1246572593;
//コンソールに表示された配列をそのままコピペ
myMojikumi.overrideMojikumiAkiList=[
[18, 22, false, 0.05, 0.05, 0.05, 0, false],
[18, 23, false, 0.05, 0.05, 0.05, 0, false],
[18, 22, true, 0.05, 0.05, 0.05, 0, false],
[12, 12, false, -0.05, 0, 0.5, 5, false],
[33, 12, false, -0.05, 0, 0.5, 5, false],
[11, 12, false, -0.05, 0, 0.5, 5, false],
[3, 12, false, -0.05, 0, 0.5, 5, false],
[9, 12, false, -0.05, 0, 0.5, 5, false],
[8, 12, false, -0.05, 0, 0.5, 5, false],
[4, 12, false, -0.05, 0, 0, 5, false],
[7, 12, false, -0.05, 0, 0.5, 5, false],
[12, 33, false, -0.05, 0, 0.5, 5, false],

//...中略...

[24, 4, false, 0, 0, 0.5, 0, false],
[25, 18, false, 0, 0, 0.25, 6, false],
[18, 18, false, 0, 0, 0.25, 6, false],
[18, 25, false, 0, 0, 0.25, 6, false],
[25, 25, false, 0, 0, 0.25, 6, false]];
空のドキュメントを開いた状態で実行すれば、新しく文字組みアキ量設定ができます。

2010-02-22追記:
めんどくさいので、読み出し・組み込み部分をまとめて、スクリプトを生成するようにしました。
文字組みを読み取りたい部分のテキストを選択して、スクリプトを実行して下さい。
ファイル保存ダイアログが開き、スクリプトを保存します。
(function(){
if(app.activeDocument.selection.length==0) return;
 var obj=app.activeDocument.selection[0];
var check={
"Text":true,
"TextColumn":true,
"Paragraph":true,
"InsertionPoint":true,
"Character":true,
"TextStyleRange":true
}
if(!check[obj.constructor.name]) return;

var defFile = new File("~/Desktop/mojikumi_"+obj.mojikumi.name+".jsx");
defFile = defFile.saveDlg('スクリプトの保存先を選択して下さい。');
if(!defFile) return;
var mojikumiAryTxt = obj.mojikumi.overrideMojikumiAkiList.toSource();
mojikumiAryTxt = mojikumiAryTxt.replace(/\], \[/g,"],\n [");

var script='var actDoc = app.activeDocument;\n'
+'var myMojikumi=actDoc.mojikumiTables.add("'+ obj.mojikumi.name +'");\n'
+'myMojikumi.basedOnMojikumiSet = '+ obj.mojikumi.basedOnMojikumiSet +';\n'
+'myMojikumi.overrideMojikumiAkiList =\n'+ mojikumiAryTxt +';';

if(defFile.open('w')){
defFile.writeln(script);
defFile.close();
alert("スクリプトを書き出しました。");
}
})();