3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[GAS] Googleドキュメントで全消去の後appendParagraphしたら先頭に空行ができる

Last updated at Posted at 2021-04-18

はじめに

久しぶりにGoogleドキュメントへGAS構築する機会がありましたので、小ネタ扱いの備忘録です。Googleドキュメント関連ではあまりピンポイントな情報にたどり着けないことが多いので、できる限り具体的な内容のタイトルにしてみました。

目的

タイトルの通り、スクリプトを実行したところ、先頭に空行ができてしまいました1。これを削除したい、もしくは空行を出さずにスマートに先頭から追加できないか、についての検証です。

ロジック

以下、検証用に既存のドキュメントデータを全消去した後、順次、ダミー文字列を追加していくという単純なスクリプトを書いてみました。

sample01.gs
function main(){
  const Doc  = DocumentApp.getActiveDocument()
  const Body = Doc.getBody()

  Body.clear() // 全消去

  // ダミー文字列を用意し順次展開でパラグラフセット
  const arr = Array(10).fill('王様の耳はパンの耳')
  arr.forEach(para => Body.appendParagraph(para))
}
// 先頭に空行

結果は先頭行が空行になります。

元々新規作成時や全消去(初期化)した場合、細かいことは置いておいて、DocumentBodyには1つのParagraphがあり、空のTextがある状態です2。つまり初期状態では空段落が最初から存在していて、そこにappendParagraphしていくわけなので、当然の結果です。

同様のことはinsertParagraphでも起きます。

sample02.gs
function main(){
  /* 省略 */
  arr.forEach((para, idx) => Body.insertParagraph(idx, para))
}
// 末尾に空行

この場合は先頭行ではなく、末尾が空行になります。元々あった空行がinsertParagraphによって押し出されるような感じになるので、これも当然の結果です。

解決策について

お恥ずかしいことですが、久しぶりかつドキュメントの振る舞いを理解していなかったためこの問題に嵌ってしまいました。現象の原因に納得ができたため、その解決策として以下の2パターンについて検証してみました。

  1. 順次処理の後でBody.removeChildする
  2. 先頭(もしくは末尾)のみ元の空行と差し替える

1-順次処理の後でBody.removeChildする

Body直下の子要素を削除するremoveChildを使用します。引数にChildを取りますので、Body.getChild(インデックス値)Childとして渡します。

1-1 appendParagraphの後、先頭行を削除する

sample03.gs
function main(){
  /* 省略 */
  arr.forEach(para => Body.appendParagraph(para))
  Body.removeChild(Body.getChild(0))
}

1-2 insertParagraphの後、末尾行を削除する

sample04.gs
function main(){
  /* 省略 */
  arr.forEach((para, idx) => Body.insertParagraph(idx, para))
  Body.removeChild(Body.getChild(Body.getParagraphs().length-1))
}
//Exception: Can't remove the last paragraph in a document section.

最終段落は削除できない仕様のようで、実行するとエラーになります。Exception: Can't remove the last paragraph in a document section.

2-先頭(もしくは末尾)のみ元の空行と差し替える

sample05.gs
function main(){
  const Doc  = DocumentApp.getActiveDocument()
  const Body = Doc.getBody()
  const firstPara = Body.clear().getParagraphs()[0] // 先頭行を取得

  const sentences     = Array(10).fill('王様の耳はパンの耳')
  const firstSentence = sentences.shift()
  firstPara.setText(firstSentence) // 先頭行を差し替え

  /* 省略 sample01参照 */
}

結果は想定通りでした。しかし、removeChildしたくないことにこだわったことで、かえって面倒くさいことをしている印象です。この方法は個別案件における何らかの優位性がない限りは使うことはないでしょう。
※末尾を元の空行と差し替える処理につきましては、上記理由より割愛します。

最後に

執筆時点ではsample03がシンプルでスマートかと思います。ドキュメント操作では基本的にParagraph単位の順次処理になると思いますので、より効率的で高速なロジックを意識しないといけないですね。ただ、スプレッドシートのsetValuesのような一括挿入、差し替えができるようなメソッドがあればそれで済む話なのにとも思います。(知らないだけかもしれませんが)

余談として、appendParagraphinsertParagpaphではどちらが高速か、1000文3 の新規追加で検証しました。各々5回試行の平均値となります。ご参考まで。

method time
appendParagraph 約8秒
insertParagpaph 約3秒

[参考]

  1. 正確には元々存在する空段落以降にダミー文字列が順次追加された状態なので、空行ができるということではないです。

  2. ドキュメントの詳細な構造については公式ガイドにて確認できます。

  3. 10000文で検証しようとしたところ、右記のように怒られました。Too many changes applied before saving document. Please save changes in smaller batches using Document.saveAndClose(), then reopen the document with Document.openById(). 妥協して5000文にしたところ、Service Documents failed while accessing document with id xxxxxxxxxxxxxxxxxxxxとまた怒られましたのでさらに妥協しました。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?