Google 文件:取得目前作用中的Google 文件 getActiveDocument()

 


如果你常在 Google 文件裡重複貼章節、改標題、加表格,其實不用手動來回切換;用 Apps Script 就能一鍵完成。

這篇文章要帶你認識 DocumentApp.getActiveDocument()——它就像伸手就能抓到「正在編輯的這份文件」,接著你能插入段落、建立目錄、調整樣式,甚至在開檔時自動彈出自訂選單。

內容不只講觀念,還會用清楚的步驟示範:怎麼建立容器綁定專案、首次授權要注意什麼、onOpen() 要怎麼配合,以及什麼情境該改用 openById()。最後再整理常見錯誤與踩雷清單(像是沒有 Active 文件、配額與效能問題),讓你少繞路、腳本一次就跑順。希望本篇文章可以幫助到需要的您。


目錄

{tocify} $title={目錄} 


getActiveDocument() 是什麼?什麼時候該用?

DocumentApp.getActiveDocument() 會回傳目前這支腳本所「綁定(container-bound)」的 Google 文件,也就是你正在畫面上開啟、並且將 Apps Script 綁進去的那份 Doc。取得這個 Document 物件之後,你就能進一步改標題、插入段落/表格、套 Style、加頁首頁尾、建立自訂選單等。

官方文件明確說明:如果你要操作不是綁定的那份文件,請改用 openById() 或 openByUrl()。此外,getActiveDocument() 需要文件相關的授權範圍(Scopes)。

什麼情境該用?

        你已經在 Google Docs 頁面中,透過「擴充功能 → Apps Script」打開編輯器,且腳本是綁定在這份 Doc 上。

        你需要在這份「當前文件」加選單、加入按鈕、插入範例章節、格式化整份文件等。

什麼情境不該用?

        定時器(Time-driven)或 Web App 這種沒有使用者互動、沒有打開任何 Doc 的背景環境;此時沒有「Active」的概念,請改用 openById() / openByUrl()。

        你要批次處理多份文件(例如全公司周報),那就用 DriveApp 找到檔案 ID,再 openById() 逐一處理比較穩。


最安全的上手步驟

步驟 1:建立一份測試用 Google 文件

        開 Google Docs,新建空白文件,隨便打幾行文字。

步驟 2:把 Apps Script 綁到這份文件

        在該文件中按「擴充功能 → Apps Script」,會自動建立一個容器綁定(container-bound)的專案。

        這種專案天然就能用 getActiveDocument() 取得你當前開著的這份 Doc。

步驟 3:貼入最小可運作範例

function helloDoc() {
  const doc = DocumentApp.getActiveDocument();      // 取得「當前」這份 Doc
  const body = doc.getBody();
  body.appendParagraph('Hello, Google Docs!');
  doc.saveAndClose();                                // 儲存+關閉(可選)
}


步驟 4:首次執行與授權

在 Apps Script 編輯器按「執行」,系統會跳授權流程,允許存取文件。

getActiveDocument() 會用到文件讀寫相關 Scopes(documents.currentonly 或 documents)。

步驟 5:在文件中加自訂選單

function onOpen(e) {
  DocumentApp.getUi()
    .createMenu('自動化')
    .addItem('插入範例章節', 'insertSampleSection')
    .addToUi();
}

function insertSampleSection() {
  const doc = DocumentApp.getActiveDocument();
  const body = doc.getBody();
  body.appendParagraph('專案總覽').setHeading(DocumentApp.ParagraphHeading.HEADING1);
  body.appendParagraph('背景說明').setHeading(DocumentApp.ParagraphHeading.HEADING2);
  body.appendParagraph('這裡寫你的內容...');
}


getUi() 僅能在綁定到文件的腳本內與目前開啟的文件互動,這點官方也有寫。


核心觀念拆解:Active、Bound 與非綁定

Active(目前):

        指的是使用者螢幕上正在操作的那份 Doc。

Container-Bound(容器綁定):

        腳本存在該 Doc 內部。「擴充功能 → Apps Script」打開的就是這種。

Standalone(獨立專案):

        在 script.google.com 新建的腳本,不綁任何文件。這種腳本沒有「目前文件」可取用,執行 getActiveDocument() 不符合語境,請改 openById() / openByUrl()。


常用操作範例

標題、作者資訊、頁首頁尾

function setupDocMeta() {
  const doc = DocumentApp.getActiveDocument();
  doc.setName('專案企劃書 - v1'); // 檔名(實際是 Drive 上檔名)
  const body = doc.getBody();

  // 頁首頁尾(若沒有會先建立)
  const header = doc.getHeader() || doc.addHeader();
  header.clear();
  header.appendParagraph('公司內部文件 | 機密');

  const footer = doc.getFooter() || doc.addFooter();
  footer.clear();
  footer.appendParagraph('版權所有 © ' + (new Date()).getFullYear());
}


章節骨架生成(Heading + 內文)

function buildOutline() {
  const body = DocumentApp.getActiveDocument().getBody();
  const sections = [
    {h: '摘要', level: 'HEADING1'},
    {h: '市場分析', level: 'HEADING1'},
    {h: '產品定位', level: 'HEADING1'},
    {h: '時程與資源', level: 'HEADING1'},
    {h: '風險評估', level: 'HEADING1'}
  ];
  sections.forEach(s => body.appendParagraph(s.h).setHeading(DocumentApp.ParagraphHeading[s.level]));
  body.appendPageBreak();
}


插入表格+套用表頭樣式

function insertKPI() {
  const body = DocumentApp.getActiveDocument().getBody();
  const table = body.appendTable([
    ['KPI', '目標', '目前值', '負責人'],
    ['月活用戶', '50,000', '18,200', '行銷'],
    ['轉換率', '5%', '2.3%', '產品'],
  ]);
  // 表頭加粗
  table.getRow(0).editAsText().setBold(true);
  // 欄寬、對齊等可視需求再調
}


批次格式化(避免逐字 loop 的效能坑)

function emphasizeBody() {
  const text = DocumentApp.getActiveDocument().getBody().editAsText();
  // 一次性設定全文字體與大小,避免逐段/逐字慢速操作
  text.setFontFamily(0, text.getText().length - 1, 'Noto Sans TC');
  text.setFontSize(0, text.getText().length - 1, 12);
}


跨文件操作(不用 Active,用 openById)

function updateById(docId) {
  const doc = DocumentApp.openById(docId); // 適合批次腳本與觸發器
  const body = doc.getBody();
  body.insertParagraph(0, '【系統批次更新】' + new Date());
  doc.saveAndClose();
}


對於非當前文件或在背景環境(如定時器)執行時,請一律用 openById() / openByUrl()。


觸發器與 getActiveDocument():能不能一起用?

1.    簡單觸發器(Simple triggers,如 onOpen(e)):

        在文件綁定腳本中非常好用,常用來加自訂選單或在文件打開時做初始化。這些觸發器有嚴格限制與配額,若動作涉及需要授權的服務(第一次),得先由使用者授權。

2.    定時器(Time-driven)/ 外部執行(Web App、API):

        這類背景環境沒有 Active 文件,所以 不要 用 getActiveDocument(),直接 openById()。

3.    在試算表觸發器裡調 Document:

        例如 onEdit(Sheets)去改 Docs,簡單觸發器通常不能跨存取其他檔案(需要授權)。解法是改用可安裝觸發器(Installable trigger),或改從獨立專案中用 openById()。


授權(Scopes)與權限要點

getActiveDocument() 需要文件相關 Scopes:https://www.googleapis.com/auth/documents.currentonly 或 https://www.googleapis.com/auth/documents(依你後續操作而定)。首次執行會跳授權視窗。 

你對文件的實際權限(檢視/留言/編輯)也會影響操作成功與否:例如你只有檢視權,卻要改內容,一定報錯。


配額與效能(避免踩限制造成中斷)

Apps Script 各服務都有每日配額與執行時間限制;文件服務(DocumentApp)的呼叫也算在內。若超標會拋出例外,官方提供「配額總表」與「檢視/編輯配額」頁面可參考。一般常見還有每次執行的時限、觸發器總時數等。


降風險建議:

    減少呼叫次數:

        能一次性設定文字樣式就不要逐字 loop(見上方範例)。

    批次處理分段跑:

        大量文件請分批、多個執行;必要時在兩批之間留 Utilities.sleep()。

    例外處理與重試:

        以 try/catch 包住關鍵操作,必要時實作簡單的退避(backoff)策略。

    避免不必要的 saveAndClose():

        頻繁呼叫會放大效能消耗。


非官方社群文常會提到文件服務每日操作次數的「經驗值」或 Workspace 與個人帳的差異,但實際數字請以 Google 官方配額頁面為準,因為會隨方案/專案而變更。


錯誤處理與偵錯技巧

常見錯誤訊息與對策

1.  TypeError: Cannot read properties of null/undefined ...

        多半是 DocumentApp.getActiveDocument() 回來是 null(沒有 Active 文件的環境),或是你串到不存在的元素。確認是否在綁定文件內執行;若在定時器/Web App,請改 openById()。

2.  權限/授權相關錯誤

        第一次執行沒授權就被觸發器叫起來;或你只有檢視權卻要寫入。請先用手動執行跑一次授權流程,並檢查文件分享權限。

3.  配額超限(Service invoked too many times / Exceeded maximum execution time)

        表示你呼叫太密集或單次執行超時。請參考上一節的降風險建議,並檢查官方配額文件。


偵錯小技巧

        Logger.log 與 執行記錄:逐步印出流程與關鍵變數。

        最小可重現:從最短的操作開始排查(例如只做 getActiveDocument() + appendParagraph())。

        手動先跑一次:讓 Scopes 就緒,避免簡單觸發器第一次就撞牆。


實務範例

範例 A:一鍵產生企劃書模板

function createProposalTemplate() {
  const doc = DocumentApp.getActiveDocument();
  const body = doc.getBody();

  body.clear(); // 先清空(請小心使用)
  body.appendParagraph('產品企劃書').setHeading(DocumentApp.ParagraphHeading.TITLE);

  const sections = [
    '一、專案背景', '二、目標與指標', '三、使用者洞察',
    '四、功能規劃', '五、時程與資源', '六、風險控管'
  ];
  sections.forEach((h) => body.appendParagraph(h).setHeading(DocumentApp.ParagraphHeading.HEADING1));

  const kpiTable = body.appendTable([
    ['KPI', '目標', '目前值', '註記'],
    ['月活', '50,000', '-', ''],
    ['留存 D30', '20%', '-', '']
  ]);
  kpiTable.getRow(0).editAsText().setBold(true);
}


範例 B:把文件內容搬到另一份 Doc(使用 openById)

function cloneTo(docIdTarget) {
  const source = DocumentApp.getActiveDocument().getBody();
  const target = DocumentApp.openById(docIdTarget).getBody();

  // 簡化示範:把純文字搬過去,保留基本段落(複雜格式請個別處理)
  target.appendParagraph('【來自原文件】');
  target.appendParagraph(source.editAsText().getText());
}


跨文件處理時,直接對目標文件使用 openById() 最穩。遇到複雜元素(表格、清單、圖片)建議逐一複製或以模板法處理。


範例 C:打開文件就出現自訂選單(Simple Trigger)

function onOpen(e) {
  DocumentApp.getUi()
    .createMenu('報告工具')
    .addItem('插入封面', 'insertCover')
    .addItem('加目錄', 'addTOC')
    .addToUi();
}

function insertCover() {
  const body = DocumentApp.getActiveDocument().getBody();
  body.insertParagraph(0, '專案報告').setHeading(DocumentApp.ParagraphHeading.TITLE);
}

function addTOC() {
  const body = DocumentApp.getActiveDocument().getBody();
  body.insertParagraph(1, '').asParagraph(); // 空行
  body.insertTable(2, [[DocumentApp.ElementType.TABLE_OF_CONTENTS]]);
}


onOpen(e) 屬於簡單觸發器,規則與限制請參考官方「Triggers」指南。


常見問題與雷點

1.    在非綁定環境呼叫 getActiveDocument()

例如獨立專案、定時器、Web App。結果不是 null 就是報錯。解法:改 openById() / openByUrl(),不要依賴 Active。

2.    把 Sheets 的 onEdit(e) 當萬能用

簡單觸發器(如 onEdit)無法直接跨檔案做需要授權的動作。解法:用可安裝觸發器或改由獨立腳本執行,並使用檔案 ID 操作。

3.    第一次沒授權就讓觸發器跑

會因缺乏授權而失敗。解法:先手動跑一次,完成 Scopes 授權。

4.    過度逐字/逐元素操作,效能雪崩

文件 API 操作屬於高成本呼叫。解法:偏向「整段處理、一次設定」,減少呼叫次數;大量處理時分批執行,搭配 try/catch 與簡單 backoff。

5.    把非官方數字當鐵律

社群文章的「每日 N 次」僅供參考。解法:以官方配額頁與實際專案配額為準。

6.    誤以為 getActiveDocument() 等同「目前視窗的任何文件」

它回的是綁定那份文件,不是你瀏覽器任何開啟的文件。解法:需要指定其他文件時,用 openById()。


實務最佳做法

 明確選擇模式:當前文件就 getActiveDocument();批次與觸發器就 openById()。

 先手動授權:跑一次主流程,讓 Scopes 就緒。

 控制呼叫次數:能一次性設定就不要逐字 loop。

 錯誤處理:try/catch,必要時加退避重試。

 分批執行:大量文件拆批處理,監控執行記錄。

 自訂選單入口:讓非技術同事也能一鍵操作(onOpen + getUi())。

 文件權限檢查:沒有編輯權就不要嘗試寫入。

 配額監控:熟悉官方「服務配額」與「檢視/編輯配額」文件。


問題集

Q1:我在獨立專案呼叫 getActiveDocument(),為何是 null?

A:因為沒有「容器綁定」的 Active 文件。請改 openById()。

Q2:想用排程自動生成報告,可以用 getActiveDocument() 嗎?

A:不行。排程沒有 Active 文件。請以 ID 開檔並處理。

Q3:第一次裝腳本就讓 onOpen(e) 自動跑,結果報授權錯?

A:先手動執行一次任一需要授權的函式,完成授權,之後觸發器才會順利。

Q4:我要對好多文件跑相同流程,怎麼兼顧效能與配額?

A:用 DriveApp 搜 ID → openById() → 批次跑,控制每次處理量、減少 API 呼叫、加上錯誤重試並監控配額。


張貼留言 (0)
較新的 較舊