mikeo_410
  1. 1.GAS(google Apps Script)
    1. 1.HowTo - google Apps Script
    2. 2.google Apps Script(ウエブアプリ)
    3. 3.ドキュメント(DocumentApp)
    4. 4.ドキュメント用ツールの作り方

ドキュメント用ツールの作り方

普通なら文書編集ソフトを使ってドキュメントを書くと考えます。そのツール類は文書作成ソフトの一部として機能するものです。ドキュメントは文書ファイルとして作成され、その編集は文書編集ソフトを使います。

googleドキュメントの場合、文書はサーバにあります。当然文書を編集する仕組みもサーバにあります。ツール類もサーバで動作する必要があります。

パソコンでワードを使うように、googleドキュメントを使っている訳ですが、開いているのは普通のブラウザです。Chromeに限らずEdgeでも構いません。ブラウザに特別な仕掛けがある訳ではありません。

googleドキュメントに作用するツールはサーバサイドスクリプトで作ります。UIが必要ならHTMLを作成してブラウザに送ることになります。
ツールを含めて全てサーバ上にあって、スクリプトの作成や保管もサーバ上のアプリケーションをブラウザから操作することで行なうことになります。

操作できるのはサーバサイドのアプリケーションが見せているものです。

1. どう見えるか

googleドライブのサーバを基幹としたアプリケーション群の1つがgoogleドキュメントのように見えます。googleドライブのサーバはURLによるリクエストを解析してアプリケーションを起動します。ユーザごとにドライブがあり、階層化したフォルダがあるように見せているのもアプリケーションの1つです。

この管理単位は「コンテナ」のようです。

 ブラウザで「drive.google.com」を開けばgoogleドライブが起動し、ドライブを一覧できます。

 この状態で、「+新規」をクリックすると、左図のようなメニューが表示されます。これが、総合的なメニューのようです。

 ここで、ドキュメントやスプレッドシートを選べば「無題のドキュメント」や「無題のスプレッドシート」の編集画面になります。

 画面の左上隅のアイコンをクリックすると、

  docs.google.com/document/
 docs.google.com/spreadsheets/

が開かれ、それぞれ、ドキュメントの一覧、スプレッドシートの一覧になります。これらが、それぞれのアプリケーションのトップメニューのようです。

 「無題のドキュメント」や「無題のスプレッドシート」は「googleドライブ」の「ファイル」です。

また、「google Apps Script」のコンテナです。

このコンテナは、ワードの作る .docx のような複合ファイルと考えられます。.docx は1つのファイルですが、圧縮フォルダとして開くことができます。コンテナは個別にファイルシステムを組み込んだフォルダです。

googleドキュメントの編集画面で、「ツール」「スクリプトエディタ」とすると「コード.gs」の編集画面になりますが、コード.gs はコンテナの中に作られるファイルです。googleドライブからは参照されません。

googleドライブのフォルダやファイルは、パソコンのストレージの階層化ディレクトリとは異なっています。一意IDによる識別方式になっていて、フォルダを移動しても、参照している側には影響を与えません。フォルダやファイルの名前は、単に名前属性の値であって、同じ「無題の・・・」が沢山あっても問題になりません。

これは、コンテナ内のファイルには当てはまりません。

2. プロジェクト

script.google.com を開くと、プロジェクトの一覧が表示されます。googleドキュメントでスクリプトを使用したり、「Apps Script」を作成すると、自動的に「無題のプロジェクト」ができています。

名前を付け直さないと区別ができないので、多くの場合、使用しないで済むものなのだろうと思います。

 左図は、プロジェクト名をコンテナと同じものに付け直した後のものです。

 プロジェクトの一覧では、アイコンがコンテナの種類を示すのに役立っています。「Apps Script」、「googleスプレッドシート」、「googleドキュメント」の別が分かります。

 また、マウスポインタを持っていくとコンテナ名が表示されます。

 プロジェクトを開くと「スクリプトエディタ」の編集画面になります。左のサイドバーから「概要」を開くと「コンテナ」が分かります。

 「Apps Script」を作成した場合は「コンテナ」はありません。
googleドライブでは、コンテナを持つプロジェクトは、コンテナをファイルとして表し、プロジェクトは見えないようです。コンテナがないプロジェクトはプロジェクトがファイルのようです。

3. Google Apps Script

Google Apps Script 、GASはWEBアプリケーション開発のプラットホームの名前ですが、そのスクリプト言語の名前としても使われています。このスクリプトは、サーバで動作するスクリプトであり、ブラウザに実装されたJavaScriptインタプリタが実行するものではありません。このスクリプトは、googleドライブのサービスを提供しているサーバ群に実装されているインタプリタによって実行されるものです。このインタプリタは JavaScript と思ってプログラミングが行なえるようになっています。しかし、ブラウザのDOMは存在しないのでこれらを操作する関数はありません。また、googleドキュメントやgoogleスプレッドシートなどを扱うので、DocumentAppやSpreadsheetAppと言ったクラスが組み込まれています。

JavaScriptはHTMLのscriptタグの内容としてインタプリタに読み込まれます。分割してスクリプトを作成することが可能ですが、特に構造はありません。関数は保持され、関数の外のステートメントは読み込まれ次第実行されるだけです。

GASには「ライブラリ」の機構があります。また、スクリプトに記述した関数はトリガによって呼び出されます。googleドキュメントであれば、ドキュメントが開かれるとonOpen()関数が呼び出されます。

4. ライブラリ

googleドライブのメニューから「Apps Script」を選ぶとスクリプトエディタで「コード.gs」ファイルの編集中になります。また、ドキュメントを作成して、「ツール」「スクリプトエディタ」としても「コード.gs」ファイルの編集中になります。

いずれの場合も「無題のプロジェクト」が作成されます。スクリプトエディタの左のサイドバーの歯車アイコン「プロジェクトの設定」には「スクリプトID」が発行されています。スクリプトIDは、「Apps Script プロジェクトの一意の識別子」です。

.gs ファイルはプロジェクトにいくつでも作れます。スクリプトを分割して記述することができますが1つのファイルに記述したのと扱いに差はないものと思います。このファイル名が使われることはないようです。

他のプロジェクトで使用する関数群を記述したプロジェクトは「ライブラリ」です。スクリプトエディタの左のサイドバーに「ライブラリ+」ボタンがあって、他のプロジェクトをライブラリに加えることができます。

ライブラリを指すのは全てのプロジェクトが持っているスクリプトIDで、ライブラリと使用するプロジェクトに違いはありません。

ただし、ライブラリとなるプロジェクトの名前は、そのメンバ関数を示すデフォルトのプレフィクスとなるのでプログラミングに使用することになります。

5. デプロイ

Google Apps Script は、サーバ上のグローバルな空間を想定したシステムのようです。スクリプトの管理もグローバルな世界で考えられているようですが、ここでは個人のローカルな開発を扱います。

この範疇ではデプロイや共有は不要です。

 プロジェクト間のスクリプトの参照はスクリプトIDで関連付けられます。これはコード.gsファイルなどの .gs をコピーすることができないので必然ですが、フォルダの移動に影響されないと言う効果があります。

 また、デプロイをしていないので、バージョン欄は「HEAD(開発モード)」以外の選択肢はありません。

6. ライブラリ中の関数の参照

ライブラリはスクリプトを単純に連結している訳ではなく、ライブラリ中の関数は異なる名前空間に置かれます。このライブラリの概念は JavaScript には無いものです。実現方法が JavaScript にある機構なのかどうかは分かりません。

ライブラリ中の関数の名前空間は、ライブラリを追加する際のダイアログの最下段のID欄の文字列です。デフォルトはライブラリとなるプロジェクトの名前です。

この図の例では、ライブラリの関数を呼び出したり、展開することは出来ますが、ライブラリのエントリーを一覧することができません。

また、ライブラリをネストした場合、下位のライブラリの名前空間は含まれていません。したがって、lib1をプレフィクスとして呼び出せるのは、そのプロジェクトのgsに記されたメンバだけです。

7. 2つのモデル

googleドキュメントで使用するスクリプトのモデルは2種類あるようです。メニューバーに追加するものと、アドオンメニューに追加するものです。

両者の違いはUIが必要かどうかのようで、後者はサイドバーにテキストエリアやボタンを配して会話的な操作が想定されています。

公開されているアドオンはコードを記述することなく使用できるものと思いますが、このアドオンは、メニューを追加する部分をドキュメントごとのスクリプトに記述する必要があります。

また、1つのドキュメントの編集には複数のツールが必要です。

これらを試行錯誤して分かったことは以下のことです。

  1. アドオンメニューへの追加は、ドキュメントを含むプロジェクトの名前になる。複数追加すると同名となるので最後に追加したものだけが使用可能になる。
  2. メニューを追加する記述では、ドキュメントを含むプロジェクトのスクリプトだけでなく、ライブラリに記述されたスクリプトを指定できる。
  3. アドオンメニューに追加したサイドバーから、サーバサイドのスクリプトを呼び出す場合、そのスクリプトはドキュメントを含むプロジェクトのスクリプトに限られる。
8. コンテナ・バインド・スクリプト

googleドキュメントを作成して、「ツール」「スクリプトエディタ」として、コード.gsを作った場合、このコード.gsはドキュメントに「バインド」あるいは「バンドル」されていると呼ぶことにします。

googleドキュメントはプロジェクトのコンテナの一種です。googleドキュメントはバインド・スクリプトを持つ場合と、持っていない場合があります。

「Apps Script」を作成すると、コンテナの無いプロジェクトができます。コンテナに「バインド」されていないスクリプトです。しかし、スクリプトは複数のファイルを「バンドル」した形式で保持されています。1つしかない場合も同様です。

スクリプトは複数のファイルから成り、「コード」と「HTML」の2種類あります。コードはサーバサイドで実行されるスクリプトで、HTMLはブラウザで動作するHTMLやJavaScriptです。

このファイルはgoogleドライブのファイルではなく、googleドライブは「バンドル」されたものをファイルとしています。

他のプロジェクトにバンドルされているスクリプトをライブラリとして使用するには、スクリプトIDのみが使用されます。

9. ドキュメント用ツール

以上を考慮して、下図のようにツール類を作成します。

ドキュメントを新たに作成するときには何もしないでツールを使えれば良いのですが其の方法は分かりません。出来ることで最小の手間で出来る方法を考えました。

9.1. 新しいドキュメントの作成

新しいドキュメントを開いたら「ツール」「スクリプトエディタ」と選択すると、新しいタブが開いてコード.gsの編集画面になります。

左サイドバーの「ライブラリ+」ボタンで、DocumentToolsMenu を追加します。この操作はライブラリの名前で行うことは出来ず、DocumentToolsMenuプロジェクトを開いて歯車アイコン「プロジェクトの設定」からスクリプトIDをコピーしなければなりません。

  1. function myFunction() {
  2.   DocumentToolsMenu.createToolEntries();
  3. }

スクリプトエディタは空の myFunction() を雛形として表示しますが、そこに上記のような記述をして「実行」します。

この関数は、ドキュメントに、コードを生成します。ドキュメントのタブに戻って、これをカット&ペーストで、コード.gsに貼り付けます。myFunction() は不要です。

  1. function onOpen(){DocumentToolsMenu.addMenu();}
  2. function insertText(html){DocumentToolsMenu.NS.SourceCodePane_insertText(html);};

ドキュメントのタブで、ブラウザの再読み込みボタンをクリックしてドキュメントを開き直します。すると、onOpen()が実行され、メニューバーに「DIY」メニュー、アドオンメニューに「無題のプロジェクト」項目が付かされていて、ツールが利用可能です。

複数のツールを使うので、メニューにはサブメニューとして追加されます。

ここで、「無題のプロジェクト」がメニューになるのは、ドキュメントのプロジェクト名が使われるためです。

ドキュメントを作成すると「無題のドキュメント」となりますが、おそらく適切な名前に直ぐ変更してあると思います。「ツール」「スクリプトエディタ」と選択するとプロジェクトとなって、プロジェクトに「無題のプロジェクト」の名前が付きます。この名前はユーザには必要性が分からないので、そのままにするか、ドキュメントと同じ名前にするかしかないように思います。
プロジェクトの一覧に「無題のプロジェクト」が並ぶのは誤操作の原因になりそうなので、わたしはドキュメントと同じ名前にすることを選択します。

結果、わたしのアドオンメニューを開くとドキュメント名の項があって、そのサブメニューからツールを選択するようになっています。

insertText() は、DocumentToolsMenuプロジェクトが参照する SourceCodePaneプロジェクトのスクリプトにある insertText() を呼び出すために必要です。アドオンで開かれるサイドバーはHTMLでデザインされ、そのビハインドコードである JavaScript は、サーバサイドスクリプトを呼び出しますが、このスクリプトは編集対象のドキュメントにバンドルされている必要があるようです。

9.2. ライブラリの統合

プロジェクトにライブラリを加えるにはスクリプトIDが必要で面倒です。そこで、DocumentToolsMenu プロジェクトにライブラリを集め、新たなドキュメントの作成時には DocumentToolsMenu だけをライブラリに加えることにします。

また、個別にメニューバーやアドオンにツールを加えることは現実的ではないので、メニューバーに1つ、アドオンに1つ追加をして、ツールはそのサブメニューに設定します。DocumentToolsMenu は、このメニュー統合に寄与します。

また、サイドバー・モデルでは、ブラウザサイドの JavaScript から、サーバサイドのスクリプトが呼び出されますが、このドライバ関数を提供します。

前述の通り、新たなドキュメントを作る際には、DocumentToolsMenu をライブラリに加え、createToolEntries()を呼び出します。

DocumentToolsMenu 自体も、新しいツールを加えるごとに、_CreateEntries() を呼び出してコードを生成します。DocumentToolsMenu は、ドキュメントにバインドされていて、そのドキュメントに生成したコードが書き込まれるので、スクリプトの後半をカット&ペーストで置き換えます。

_CreateEntries() は、DocumentToolsMenu に追加されているライブラリを列挙して、メニューとドライバ関数を生成します。

このライブラリメンバの列挙は実際には出来ませんでした。スクリプトの編集時にのコード補完は機能しているので、何か方法がありそうですが分かりません。そこで、各ツールのスクリプトを書く際に決まった名前空間を宣言することにしました。

9.3. メニューバー・モデルのツール

図表番号を付与するツールを作って ChartNumber プロジェクトとしました。プロジェクト名は、ライブラリの追加の際の名前のデフォルト値となり、スクリプト中で記述するので注意して命名します。

このツールは、「図表番号」の下に「図番」「表番」「振り直し」のメニュー項目を持っています。このメニュー項目が選択された際に呼び出される処理には、FigureTitle()、TableTitle()、Renumber() を登録します。これは名前空間NSのメンバとして記述します。メニューとして登録する内容は決めた名前addMenuBar()で返すようにしました。

  1. var NS = {
  2.   FigureTitle : function(){_Insert('図');},
  3.   TableTitle : function(){_Insert('表');},
  4.   Renumber : function(){
  5.     _Renumber('図');
  6.     _Renumber('表');
  7.   },
  8. }
  9. function addMenuBar(){
  10.   return [['図表番号',''],
  11.     ['図番','FigureTitle'],
  12.     ['表番','TableTitle'],
  13.     ['振り直し','Renumber']];
  14. }

DocumentToolsMenu は、以下のようなコードを生成し、DocumentToolsMenu 内に置きます。

  1. function addBarMenu(){
  2.   let ui = DocumentApp.getUi();
  3.   let m0 = ui.createMenu('DIY');
  4.   let m1 = ui.createMenu('図表番号');
  5.   m1.addItem('図番','DocumentToolsMenu.NS.ChartNumber_FigureTitle');
  6.   m1.addItem('表番','DocumentToolsMenu.NS.ChartNumber_TableTitle');
  7.   m1.addItem('振り直し','DocumentToolsMenu.NS.ChartNumber_Renumber');
  8.   m0.addSubMenu(m1);
  9.   m0.addToUi();
  10. }
9.4. アドオン・モデルのツール

このページでソースコード枠を描いているのは、SourceCodePane プロジェクトです。

  1. var NS = { 
  2.   showSidebar : function() {
  3.     let temp = HtmlService.createTemplateFromFile('sidebar');
  4.     DocumentApp.getUi().showSidebar(
  5.       temp.evaluate().setTitle('Code Pane'));
  6.   },
  7.   insertText : function(html){
  8.     :
  9.   }
  10. }

DocumentToolsMenu は、showSidebar()を見付けるとアドオン・モデルと判断して、以下のようなコードを生成し、DocumentToolsMenu 内に置きます。

  1. function addOnMenu(){
  2.   DocumentApp.getUi().createAddonMenu()
  3.     .addItem('SourceCodePane''DocumentToolsMenu.NS.SourceCodePane_showSidebar')
  4.     .addToUi();
  5. }

アドオンメニューから選択されると、showSidebar()が呼び出され、記述に従って sidebar.html からサイドバーとなるHTMLが作られ、ブラウザに送られて表示されます。

sidebar.html には、js.htmlの挿入が記されていて、これもブラウザに送くられます。

  1. <script>
  2. const status = document.getElementById("status");
  3. document.getElementById("clipboard_html").addEventListener("paste"paste1false);
  4. function paste1(e) {
  5.   e.preventDefault();
  6.   status.innerHTML='<span></span>';
  7.   var html = (e.originalEvent || e).clipboardData.getData('text/html');
  8.   if(html){
  9.     google.script.run
  10.         .withSuccessHandler(
  11.           function() {
  12.             status.innerHTML='<span style="color:#0000ff">Success</span>';
  13.           })
  14.         .withFailureHandler(
  15.           function(msg) {
  16.             status.innerHTML='<span style="color:#ff0000">'+msg+'</span>';
  17.           })
  18.         .withUserObject(this)
  19.         .insertText(html);
  20.   } else {
  21.     status.innerHTML='<span style="color:#ff0000">'
  22.         +'クリップボードにHTML形式がない</span>';
  23.   }
  24. }
  25. </script>

このJavaScriptは、ブラウザでサイドバーのテキストエリアにクリップボードの貼付けを行なうと呼び出されます。

スクリプトエディタや、Visual Source CodeはHTML形式でカラーリングしたソースコードをクリップボードに積みます。これを、サーバサイドの insertText() に渡してソースコード枠を描いています。サーバサイドのスクリプトの呼び出しには、google.script.run を使います。google.script.runによって呼び出せるのは、編集中のドキュメントにバインドされたスクリプトだけで、ネストしたライブラリ中の関数を呼び出すような記述が出来ないようです。


題目一覧へmikeo_410@hotmail.com(updated: 2022/09/08)