google-apps-scriptgoogle-docs

paragraph.clear() caused bookmark.getPosition() to shifted


I have a google docs with 2 bookmarks in it. Bookmarks are in different paragraphs.

Docs structure is something like this:

             | ...(other content)...
[bookmark-0] | Text-0
             | ...(other content)...
[bookmark-1] | Text-1
             | ...(other content)...

I'm running this script

function removeTextContent() {
  const docsId = '...';
  const docs = DocumentApp.openById(docsId);
  const doc = docs.getTabs()[0];
  const bookmarks = doc.asDocumentTab().getBookmarks();

  for (const [index, bookmark] of bookmarks.entries()) {
    Logger.log(bookmark.getId()); // output correct values
    const position = bookmark.getPosition(); // get shifted in 2nd iteration
    const element = position.getElement();
    const paragraph = element.asParagraph();
    paragraph.clear();
    position.insertText(String(index));
  }
}

Expected result:

             | ...(other content)...
[bookmark-0] | 0
             | ...(other content)...
[bookmark-1] | 1
             | ...(other content)...

Actual result:

             | ...(other content)...
[bookmark-0] | 1
             | ...(other content)...
[bookmark-1] | Text-1
             | ...(other content)...

Further debugging shows that in 2nd iteration position get shifted hence position.insertText() in the 2nd iteration put value at the wrong position.

This code also gives the same behavior

paragraph.getChild(0).removeFromParent(); // instead of paragraph.clear()

My goal is to replace the content of bookmarks with "something else" while keeping the bookmarks .getId() intact.


Solution

  • Further examination shows the problem isn't related to the order of bookmarks but because the code previously works from top to bottom.

    Changing this

    for (const [index, bookmark] of bookmarks.entries()) {
      // code
    }
    

    to this

    for (const [index, bookmark] of bookmarks.reverse().entries()) {
      // code
    }
    

    solves the problem.

    It's worth to note that when working with Position it's a good idea to always work from bottom to top, for good reasons.

    ==============

    As for the order problem, I've created an example in github to workaround this issue.

    The idea is to define the order yourself.

    const config = [
      {
        imageName: 'Figure 1',
        bookmarkId: 'id.wglkb02zor96'
      },
      {
        imageName: 'Figure 2',
        bookmarkId: 'id.ifl1btgwoni'
      },
      {
        imageName: 'Figure 3',
        bookmarkId: 'kix.jithfpq0bbwm'
      }
    ];
    

    Then use that order to work the bookmarks.

    const docsId = '...';
    const docs = DocumentApp.openById(docsId);
    const doc = docs.getTabs()[0];
    const bookmarks = doc.asDocumentTab().getBookmarks();
    
    const bookmarkOrder = config.map(item => item.bookmarkId);
    bookmarks.sort((a, b) => { // order bookmarks using config
      const indexA = bookmarkOrder.indexOf(a.getId());
      const indexB = bookmarkOrder.indexOf(b.getId());
      return indexA - indexB;
    });
    

    Alas, it's a shame that an important feature like bookmark is not well-behaved.