Google Apps ScriptとCheerioでSVGの中身を書き換えたら・・・【GAS】
座席表アプリで使うSVGファイルの特定のID達を手動で書き換えていくという作業があったりするのですが、こんなのを手作業でVSCodeでやってるとちょっと発狂したくなるような作業なので、どうにかしたいなと思い考えたのが、Cheerioと呼ばれるスクレイピングなどで大活躍のライブラリがあったので、これを使ってランダムなIDの置き換え作業を出来ないか?挑戦してみました。
今回利用するスプレッドシートやライブラリ
- SVGファイルのIDを置き換える - Google Spreadsheet
- Cheerio.gs - Library
- サンプルSVGファイル
今回は座席表アプリの1作業を自動化する為のコードなので、以下のエントリーも参考にしてみると良いと思います。
事前準備
今回はライブラリを使って操作等をしますのでライブラリの導入や、編集するSVGファイルの中身を確認しておく必要があります。
ライブラリの導入
以前は、HTML Parserライブラリ(1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw)を使うのが主流でしたが、正直言って使いにくい。ので、最近はNode.jsなどでもおなじみのjQuery的にHTMLを処理できるCheerio.gsを使うのが非常に楽に処理できるので、おすすめです。
以下の手順で追加します。
- スクリプトエディタを開く
- 左サイドバーの「ライブラリ」の+ボタンをクリックする
- スクリプトIDに「1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0」を追加する
- 検索をクリック
- 最新バージョンが出てくるので、追加ボタンをクリック。Cheerioで呼び出すことになります。
図:jQueryの使い手ならすぐ使いこなせるよ
編集するSVGファイルの該当箇所
座席表アプリで使ってるSVGファイルの座席ブロックの塊を編集します。
<a href="javascript:void(0);" onclick="seatinfo($(this).children().attr('id'));" id="a95">
<g id="seatA014-1-1" transform="matrix(0.84466019,0,0,0.88518024,217.45717,413.39983)">
<rect style="stroke-width:3" id="A014-1-1" width="94.380722" height="69.397591" x="24.057831" y="45.33976" class="bar"
fill="#afdde9" stroke="#0000ff">
</rect>
<text xml:space="preserve"
style="font-size:16.31940603px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';fill:#1a1a1a;stroke-width:0.0866573"
x="26.237806" y="88.865623" id="textI2" transform="scale(0.99255985,1.0074959)">
<tspan sodipodi:role="line" id="spanA014-1-1" style="fill:#1a1a1a;stroke-width:0.0866573;font-size:16.31940603px"
x="26.237806" y="88.865623"> </tspan>
</text>
</g>
</a>
- aタグで括られたgタグおよびその子要素、孫要素のid部分を書き換えます。
- 今回のコードは訳合って直接replaceさせるコードになっているので、idは他の文字列と被らないようなユニークな文字列にしておく必要があるので単純すぎるIDだと関係ない場所がreplaceされてしまうので注意(-1-1とつけてるのは被らないようにランダムにつけておくと良いでしょう)
- gタグのIDはseatA014といったようなスタイルのIDに変更します。
- rectタグのIDは単純なA014というスタイルのIDに変更します。
- tspanタグのIDはspanA014というスタイルのIDに変更します。
- inkscapeはブロックをコピーするとオリジナルのIDに加えてランダムな文字列を加えたIDが自動で付与されるので、ユーザが作成時には意識する必要はありません(最初のブロックだけユニークなIDを付けておけばオッケー)
注意点
replace作業をreplaceメソッドで行うという方法ではなく、Cheerioを使った以下のコードでDOM操作でリプレースしてみたのですが、前述の塊の場合、gタグとrectタグは上手く置き換わるのですが、textタグの子要素であるtspanタグの内容が消えてしまったり、オカシナ事になったりしてファイルの中身が壊れてしまう(おそらくCheerioのバグと思われる)
よって、このコードを使ったIDの書き換えが上手くいかなかったので、replaceメソッドで直接取得した文字列データ内の文字列を置き換える仕様にしています。
//aタグのIDを取得する
let atag = content.id
//atagの子要素のIDを取得する
let gtag = $("#" + atag).children('g');
//gtagのIDを取得する
let gtagid = gtag[0].attribs.id
//IDを書き換える
$(gtagid).replaceWith((_i, elm) => {
let tempid = seatbase + paddingZero(cnt);
return $(tempid)
}
1回目置き換え実行は上手くいくのですが、2回目連続して実行するとrectタグのIDは書き換わるものの、その次にあるtextタグの中身がごっそり消えてしまう謎現象で、断念しました。最終的には、以下のコードで変更したBody部分を持ってファイルを上書きする事が出来ます。(Cheerio.gsにはCheerio.htmlというメソッドがなく、loadしたその下にあるのでこのような書き方になります)
let header = "<?xml version='1.0' encoding='UTF-8' standalone='no'?>"
let body = header + $.html("body > *")
文字列をreplaceしてるだけなので、誤動作を防ぐことを考えたら、id=xxxxまで含めてreplaceするように構築すると、無関係の場所を置き換えてしまうのを防げます。其の場合ダブルコーテーションをきちんとエスケープする処理が必要です。
//id=まるごと置き換える
svgdata = svgdata.replace("id=\"" + rectid + "\"","id=\"" + tempid + "\"")
ソースコード
SVGファイルの中身をsvgfileとして取得しておきつつ(これを書き換える)、Cheerio.loadで同じファイルを別途読み込む。CheerioでDOM操作をしてIDを取得したら、同じ文字列に該当する内容をsvgfileの中でreplaceしていくという仕組みです。Cheerio自身では置き換えはさせていません。
inkscapeで最初の1個目のブロックに長いIDをそれぞれ付けておくとコピーするごとにユニークな長いIDが付与され、これまでこれを手作業で置き換えていましたが、この仕組の結果頭から通し番号で指定のスタイルのIDを付与する事が出来ます。
最終的にはDriveAppのsetContentでファイルを上書きさせています。
var svgman = "ここにSVGファイルのIDを入れる";
//SVGファイルのIDを自動でリプレース
function svgreplace(){
//置き換えよう変数
var cnt = 0;
var seatbase = "seatmanC"
var rectbase = "C"
var spanbase = "tspanC"
//SVGファイルを取得
var file = DriveApp.getFileById(svgman);
var svgdata = file.getBlob().getDataAsString("UTF-8");
var svgfile = DriveApp.getFileById(svgman).getBlob().getDataAsString("utf-8");
var $ = Cheerio.load(svgfile);
//aタグの塊を取得する
var $tables = $('a');
//ループで処理
$tables.each(function(index, element) {
//要素の塊を取得
let content = $(element)[0].attribs
//aタグのIDを取得する
let atag = content.id
//atagの子要素のIDを取得する
let gtag = $("#" + atag).children('g');
//gtagのIDを取得する
let gtagid = gtag[0].attribs.id
//gtagのIDを書き換える
let parent = "";
//gtagid用のIDを生成
parent = seatbase + paddingZero(cnt);
//replace
svgdata = svgdata.replace(gtagid,parent)
//gtagの子要素のIDを取得する
let recttag = $("#" + gtagid).children('rect');
//recttagのIDを取得する
let rectid = recttag[0].attribs.id
//recttagのIDを書き換える
let tempid = rectbase + paddingZero(cnt);
//replace
svgdata = svgdata.replace(rectid,tempid)
//gtagの孫要素のtspanを取得する
let texttag = $("#" + gtagid).children('text');
let textid = texttag[0].attribs.id
let spantag = $("#" + textid).children('tspan');
let tspanid = spantag[0].attribs.id
//tspanidのIDを書き換える
let tempid2 = spanbase + paddingZero(cnt);
//replace
svgdata = svgdata.replace(tspanid,tempid2)
//カウンタを回す
cnt = cnt + 1;
});
//ファイルを上書きする
file.setContent(svgdata);
}
//頭に0をつける
var paddingZero = function(n) {
return (n < 10) ? '0' + n : n;
};
