Search code examples
google-apps-scriptgoogle-docsgoogle-docs-api

How to find the index of a specific string in relation to the document's body, to use with the api?


I am trying to replace text, matching a specific regex, by a footnote with this text.

  1. I know how to find each occurrence of the regex (thanks to some other post here)

  2. I know how to create footnotes using API (thanks to the question here)

  3. I guess, I even know how to put the text into the footnote section corresponding to the created footnote

But I need an index of the position in the text and my search (done by script, not by request) returns some abstract starting position in the "Element", which is not the index from the start of the document's body( which is what the api wants).

Where can I get the index?

function makeRef() {
  var body = DocumentApp.getActiveDocument().getBody()
  const documentId = DocumentApp.getActiveDocument().getId();

  var myRegEx = '\\([a-z,A-Z]+?.+?,.+?[12][0-9][0-9][0-9]\\)'

  var foundElement = body.findText(myRegEx);

  while (foundElement != null) {
    // Get the text object from the element
    var foundText = foundElement.getElement().asText();
    // Where in the Element is the found text?
    var start = foundElement.getStartOffset();
    var end = foundElement.getEndOffsetInclusive();

    var requests = [
    {
      createFootnote: {
        location: {
          index: start 
        }
      }
    }]
    Docs.Documents.batchUpdate({requests:requests},documentId)
    // Find the next match
    foundElement = body.findText(myRegEx, foundElement);
  }
}

The problem is: how do I get the index of the place where I need the footnote?


Solution

  • I believe your goal is as follows.

    • You want to modify a text from Harry Potter is a magical child (Rowling, 1996) to Harry Potter is a magical child^1 and you want to put ^1 Rowling, 1996 as a footnote.
    • You want to achieve this using Google Apps Script.

    In this case, how about the following sample script?

    I thought that it might be difficult to directly obtain the index for using Docs API from your showing script. In this case, in order to obtain the index, how about using Docs API? In your situation, how about the following sample script?

    Sample script:

    Please copy and paste the following script to the script editor of Google Document and enable Docs API at Advanced Google services. Ref

    function myFunction() {
      const myRegEx = '\\([a-z,A-Z]+?.+?,.+?[12][0-9][0-9][0-9]\\)'; // This is from your script.
    
      // Retrieve the initial object from Google Document.
      const documentId = DocumentApp.getActiveDocument().getId();
      const obj1 = Docs.Documents.get(documentId).body.content;
    
      // Retrieving texts and creating footnotes.
      const r = new RegExp(myRegEx, "g");
      const { requests1, texts } = obj1.reduce((o, e) => {
        if (e.paragraph) {
          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 - 1 } } });
                  o.requests1.push({ deleteContentRange: { range: { startIndex: f.startIndex + c.index, endIndex: f.startIndex + c.index + c[0].length } } });
                });
              }
            }
          });
        }
        return o;
      }, { requests1: [], texts: [] });
      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: texts[i].replace(/\(|\)/g, "") } });
        }
        return ar;
      }, []);
      Docs.Documents.batchUpdate({ requests: requests2 }, documentId);
    }
    

    Testing:

    When this script is run to your provided sample Google Document, the following result is obtained.

    From:

    enter image description here

    To:

    enter image description here

    Note:

    • This sample script can be used in your provided Google Document. If you change Google Documents, this script might not be able to be used. Please be careful about this.

    References: