Managing Footnotes on Google Documents using Google Apps Script

Gists

Description

Google Documents can be managed by the Document service of Google Apps Script. Ref One day, you might have a situation in which you are required to manage the footnotes on Google Documents using Google Apps Script. There are several official documents related to the footnotes for Google Apps Script. Ref and Ref However, unfortunately, I’m worried that it might be difficult a little to understand the management of the footnotes from these documents. Actually, I saw some questions related to the footnotes on Google Documents at Stackoverflow. In this report, I would like to introduce a method for managing the footnotes on Google Documents using Google Apps Script.

In this report, the method for managing the footnotes is introduced by the following 4 sections.

  1. Get footnotes
  2. Remove footnotes
  3. Update footnotes
  4. Create footnotes

1. Get footnotes

The sample Google Document is as follows.

The sample script for retrieving those footnotes is as follows. This sample script is required to be in the container-bound script of Google Document.

function sample1() {
  const doc = DocumentApp.getActiveDocument();
  const footnotes = doc.getFootnotes();
  const footnoteContents = footnotes.map((f, i) => [
    i + 1,
    f.getFootnoteContents().getText().trim(),
  ]);
  console.log(footnoteContents);
}

When this script is run to the above sample Document, the following result is obtained.

[
  [1,"sample footnote 1"],
  [2,"sample footnote 2"],
  [3,"sample footnote 3"],
  [4,"sample footnote 4"],
  [5,"sample footnote 5"],
  [6,"sample footnote 6"]
]

For example, when you want to move the cursor to the specific footnote in the document body, you can use the following script.

function sample2() {
  const n = 3; // Move cursor to the 3rd footnote.

  const doc = DocumentApp.getActiveDocument();
  const footnotes = doc.getFootnotes();
  const pos = doc.newPosition(footnotes[n - 1].getParent(), 1);
  doc.setCursor(pos);
}

When this script is run to the above sample Document, the cursor is moved to the footnote of 3.

2. Remove footnotes

The sample script for removing footnotes is as follows.

function sample3() {
  const n = 3; // Remove the 3rd footnote.

  const doc = DocumentApp.getActiveDocument();
  const footnotes = doc.getFootnotes();
  footnotes[n - 1].removeFromParent();
}

When this script is run to the above sample Document, the 3rd footnote is removed.

3. Update footnotes

The sample script for updating footnotes is as follows.

function sample4() {
  const n = 3; // Update the 3rd footnote.

  const doc = DocumentApp.getActiveDocument();
  const footnotes = doc.getFootnotes();
  const footnoteContent = footnotes[n - 1].getFootnoteContents();
  footnoteContent.replaceText("^.*$", " " + "Updated");
}

When this script is run to the above sample Document, the 3rd footnote is updated from sample footnote 3 to Updated.

Here, I used replaceText instead of setText for updating the text to keep the text style. When this script is run, the text “Updated” is set while keeping the font color. If the text is set with setText, the text style is reset. This technique might be also important.

4. Create footnotes

In the current stage, unfortunately, the Classes and Methods for creating the footnotes are not prepared in the Document service of Google Apps Script. However, fortunately, when Google Docs API is used, this can be achieved. Here, I would like to introduce a sample script for creating the footnotes to Google Documents using Google Docs API with Google Apps Script.

When you test this script, please enable Google Docs API at Advanced Google services. Ref

function sample5() {
  const documentId = "###"; // Please set your Google Document ID. Or, if you are using the container-bound script of Document, you can also use const documentId = DocumentApp.getActiveDocument().getId();

  // This is a sample placeholer and replacement data.
  const data = {
    "{{footnote1}}": { text: "sample footnote 1", fontColor: "#ff0000" },
    "{{footnote2}}": { text: "sample footnote 2", fontColor: "#ff9900" },
    "{{footnote3}}": { text: "sample footnote 3", fontColor: "#38761d" },
    "{{footnote4}}": { text: "sample footnote 4", fontColor: "#3c78d8" },
    "{{footnote5}}": { text: "sample footnote 5", fontColor: "#674ea7" },
    "{{footnote6}}": { text: "sample footnote 6", fontColor: "#741b47" },
  };

  // Create request body.
  const para = (o, e) => {
    e.paragraph.elements.forEach((f) => {
      if (f.textRun) {
        const m = [...f.textRun.content.matchAll(r)];
        if (m.length > 0) {
          m.forEach((c) => {
            o.texts.push(c[0]);
            o.requests1.push(
              {
                createFootnote: { location: { index: f.startIndex + c.index } },
              },
              {
                deleteContentRange: {
                  range: {
                    startIndex: f.startIndex + c.index,
                    endIndex: f.startIndex + c.index + c[0].length,
                  },
                },
              }
            );
          });
        }
      }
    });
  };

  // Retrieve the initial object from Google Document.
  const obj1 = Docs.Documents.get(documentId).body.content;

  // Retrieving texts and creating footnotes.
  const dataObj = Object.fromEntries(
    Object.entries(data).map(([k, v]) => {
      const c = SpreadsheetApp.newColor().setRgbColor(v.fontColor).asRgbColor();
      return [
        k,
        {
          text: v.text,
          fontColor: {
            red: c.getRed() / 255,
            green: c.getGreen() / 255,
            blue: c.getBlue() / 255,
          },
        },
      ];
    })
  );
  const keys = Object.keys(dataObj);
  const r = new RegExp(keys.join("|"), "g");
  let { requests1, texts } = obj1.reduce(
    (o, e) => {
      if (e.paragraph) {
        para(o, e);
      } else if (e.table) {
        e.table.tableRows.forEach((row) => {
          if (row.tableCells) {
            row.tableCells.forEach((col) => {
              if (col.content) {
                col.content.forEach((co) => {
                  if (co.paragraph) {
                    para(o, co);
                  }
                });
              }
            });
          }
        });
      }
      return o;
    },
    { requests1: [], texts: [] }
  );
  const putTexts = texts.map((t) => dataObj[t].text);

  const { replies } = Docs.Documents.batchUpdate(
    { requests: requests1.reverse() },
    documentId
  );
  const footnoteIds = replies
    .reduce((ar, e) => {
      if (e && e.createFootnote && e.createFootnote.footnoteId) {
        ar.push(e.createFootnote.footnoteId);
      }
      return ar;
    }, [])
    .reverse();

  // Retrieve the object after footnotes were created.
  const { footnotes } = Docs.Documents.get(documentId);

  // Inserting texts to footnotes.
  const requests2 = footnoteIds.reduce((ar, id, i) => {
    const o = footnotes[id];
    if (o) {
      ar.push(
        {
          insertText: {
            location: { segmentId: id, index: o.content[0].endIndex - 1 },
            text: putTexts[i],
          },
        },
        {
          updateTextStyle: {
            range: {
              segmentId: id,
              startIndex: o.content[0].endIndex - 1,
              endIndex: putTexts[i].length + 1,
            },
            textStyle: {
              foregroundColor: {
                color: { rgbColor: dataObj[texts[i]].fontColor },
              },
            },
            fields: "foregroundColor",
          },
        }
      );
    }
    return ar;
  }, []);
  Docs.Documents.batchUpdate({ requests: requests2 }, documentId);
}

When this script is run, the following result is obtained.

In this sample script, using data, {{footnote1}} of a placeholder is set as a footnote, and a value of sample footnote 1 is used as a footnote content, and also, the font color of the footnote content is changed to #ff0000. Other keys are also the same way.

By the way, in this sample, in order to convert the hex value of the color code to each value of RGB, the Spreadsheet service is used.

 Share!