Techniques for Creating Nested Lists on Google Documents using Google Docs API

Gists

Abstract

Current Google Docs API documentation offers methods for working with existing lists but lacks instructions for creating nested lists in new documents. This report fills this gap, empowering developers and streamlining nested list creation using Google Apps Script (concepts applicable to other languages).

Introduction

The official Google Docs API documentation provides instructions for working with existing lists, including converting paragraphs to lists and removing bullets. However, it lacks guidance on creating nested lists within an empty document body. Ref

This omission can be a hurdle for users who need to programmatically generate nested lists in new documents. In fact, encountering this challenge is a common theme on Stack Overflow, as evidenced by these discussions: Ref and Ref

By addressing this gap in the official documentation, this report aims to empower developers and streamline their use of the Google Docs API for creating nested lists. It leverages Google Apps Script for demonstration purposes, but the concepts and approaches can be adapted to other programming languages as well.

Sample

This report introduces the method for creating the following nested list by Google Docs API.

Usage

1. Create a New Google Document

Start by creating a new Google Document. Then, open the script editor within the document.

This report uses Google Docs API with Google Apps Script as a simple example. Authorization for the Google Docs API is simplified when using Apps Script, which is why it was chosen for this demonstration.

2. Enable Google Docs API

Enable the Google Docs API in Advanced Google services. You can find instructions for enabling advanced services in the Apps Script documentation. Ref

3. Sample script

Copy and paste the following script into the script editor of your Google Document. Save the script after pasting.

/**
 * ### Description
 * Create a request body for creating a nested list to Google Document using Google Docs API with Google Apps Script.
 *
 * @param {Object} object Object for using this method.
 * @param {Number} object.startIndex Position for inserting the list.
 * @param {String} object.bulletPreset Please select this from https://developers.google.com/docs/api/reference/rest/v1/documents/request#BulletGlyphPreset
 * @param {String} object.temporalBullet Temporal bullet using the value of "listArray".
 * @param {Array} object.listArray Array includes the list using the temporal bullet. The nested list is set using the space and the temporal bullet.
 * @returns {Array} Request body for creating a nested list to Google Document using Google Docs API.
 */
function createRequestBody_(object) {
  const { startIndex, bulletPreset, temporalBullet, listArray } = object;
  const { totalText, deleteParagraphBullets } = listArray.reduce((o, e) => {
    const reg = new RegExp(`^([ ${temporalBullet}]*) `);
    const r = e.match(reg);
    if (r) {
      const nest = r[1].replace(temporalBullet, "").length;
      const temp = `${Array(nest).fill("\t").join("")}${e.replace(r[0], "")}\n`;
      o.totalText += temp;
    } else {
      const temp = `${e}\n`;
      const start = startIndex + o.totalText.length;
      o.totalText += temp;
      o.deleteParagraphBullets.push({ deleteParagraphBullets: { range: { startIndex: start, endIndex: start + 1 } } });
    }
    return o;
  }, { totalText: "", deleteParagraphBullets: [] });
  return [
    { insertText: { text: totalText, location: { index: startIndex } } },
    { createParagraphBullets: { range: { startIndex: startIndex, endIndex: startIndex + totalText.length }, bulletPreset } },
    ...deleteParagraphBullets.reverse()
  ];
}

// Please run this function.
function myFunction() {
  // Please set your variables.
  const startIndex = 1; // When 1 is set, the list is inserted from the top of Google Document.
  const bulletPreset = "NUMBERED_DECIMAL_NESTED"; // Please select this from https://developers.google.com/docs/api/reference/rest/v1/documents/request#BulletGlyphPreset
  const temporalBullet = "*"; // Temporal bullet using the value of "listArray".

  // Array including the list using the temporal bullet.
  // Nested list is set using the space and the temporal bullet.
  const listArray = [
    "Sample text 1",
    "* Sample text 2",
    " * Sample text 3",
    "  * Sample text 4",
    "   * Sample text 5",
    "  * Sample text 6",
    " * Sample text 7",
    "* Sample text 8",
    "Sample text 9",
    "  * Sample text 10",
    "  * Sample text 11",
    "  * Sample text 12",
    "* Sample text 13",
    " * Sample text 14",
    " * Sample text 15",
  ];

  const requests = createRequestBody_({ startIndex, bulletPreset, temporalBullet, listArray });

  const docmentId = DocumentApp.getActiveDocument().getId();
  Docs.Documents.batchUpdate({ requests }, docmentId);
}

4. Explanation of script

When the function myFunction is run, it performs the following steps:

  1. Set variables.
  2. Create a request body for creating a nested list in a Google Document using the Google Docs API.
  3. Request the batchUpdate method of the Google Docs API with the created request body.

Creating the Request Body for Nested Lists

A crucial step is creating the request body for the Google Docs API. Before explaining the createRequestBody_ function, let’s explore how to build a nested list using the Docs API.

  1. Create Text for Insertion:
    • To create nested lists using \t and \n characters, the text for each paragraph becomes:
    • Sample text 2\n
    • \tSample text 3\n
    • \t\tSample text 4\n
    • \t\t\tSample text 5\n (and so on)
    • As you can see, each paragraph is constructed with \t (tab), text, and \n (newline).
    • The totalText variable in createRequestBody_ demonstrates this structure: Sample text 1\nSample text 2\n\tSample text 3\n\t\tSample text 4\n\t\t\tSample text 5\n\t\tSample text 6\n\tSample text 7\nSample text 8\nSample text 9\n\t\tSample text 10\n\t\tSample text 11\n\t\tSample text 12\nSample text 13\n\tSample text 14\n\tSample text 15\n
  2. Insert Text: Insert the created text into the document using InsertTextRequest.
  3. Set Paragraph Bullets: Apply bullets to the inserted text using CreateParagraphBulletsRequest. startIndex and endIndex define the starting index for the list and the length of the text, respectively.
  4. Delete Unwanted Bullets: For paragraphs that shouldn’t have bullets (like Sample text 1 and Sample text 9), remove them using DeleteParagraphBulletsRequest. Set startIndex and endIndex to target only the first character of the paragraph.

Following this flow, the createRequestBody_ function constructs the request body for the nested list. Here’s the created request body:

[
  { "insertText": { "text": "Sample text 1\nSample text 2\n\tSample text 3\n\t\tSample text 4\n\t\t\tSample text 5\n\t\tSample text 6\n\tSample text 7\nSample text 8\nSample text 9\n\t\tSample text 10\n\t\tSample text 11\n\t\tSample text 12\nSample text 13\n\tSample text 14\n\tSample text 15\n", "location": { "index": 1 } } },
  { "createParagraphBullets": { "range": { "startIndex": 1, "endIndex": 234 }, "bulletPreset": "NUMBERED_DECIMAL_NESTED" } },
  { "deleteParagraphBullets": { "range": { "startIndex": 122, "endIndex": 123 } } },
  { "deleteParagraphBullets": { "range": { "startIndex": 1, "endIndex": 2 } } }
]

This request body can also be used with various languages besides Google Apps Script. For instance, you can directly use it with the “API explorer” of “Try it!” at Method: documents.batchUpdate.

Steps in the createRequestBody_ function:

  1. The text for insertion is created within a reduce loop. The loop also builds an array containing objects for using DeleteParagraphBulletsRequest. This array needs to be reversed because deleting unnecessary bullets alters the indices.
  2. The request body is constructed in the order of InsertTextRequest, CreateParagraphBulletsRequest, and DeleteParagraphBulletsRequest.

 Share!