将 link 更新为 google 文档中的标题

Update link to heading in google docs

在 google 文档中,可以轻松地从文档内部添加标题和 link。但是当标题文本更改时,link 文本不会更改。 有没有办法更改该行为或自动更新 link 文本?

我知道大约需要 1 1/2 年,但这也许会有所帮助。我遇到了完全相同的问题并编写了一个函数来将所有 link 更新为文档中的标题。由于我找不到任何 built-in 函数或 add-ons,唯一的方法是编写脚本。

需要考虑的一些事项:

  • 这需要当前 table 的内容才能工作。如果您没有(或不想要)TOC,您可以插入一个 运行 该函数,然后将其删除。另外,我只用包含页码的目录对其进行了测试。
  • 它将 link 的所有文本更新为文档中的标题。然而,link其他一切都保持不变。
  • 请自行承担使用风险(也许可以在您的文档副本中试用)。我已经对其进行了测试,但测试本可以更彻底。另外,这是我第一次编写脚本文档。

将其粘贴到文档的脚本编辑器中并运行替换标题链接。脚本无法更新的链接(因为它们 link 指向不再存在的标题)将在控制台中输出。

function replaceHeadingLinks() {
  var curDoc = DocumentApp.getActiveDocument();
  var links = getAllLinks_(curDoc.getBody());
  var headings = getAllHeadings_(curDoc.getBody());
  var deprecatedLinks = []; // holds all links to headings that do not exist anymore.
    
  links.forEach(function(link) {
  
    if(link.url.startsWith('#heading')) {
    
      // get the new heading text
      var newHeadingText = headings.get(link.url);
      
      // if the link does not exist anymore, we cannot update it.
      if(typeof newHeadingText !== "undefined") {
        
        var newOffset = link.startOffset + newHeadingText.length - 1;
        
        // delete the old text, insert new one and set link
        link.element.deleteText(link.startOffset, link.endOffsetInclusive);
        link.element.insertText(link.startOffset, newHeadingText);
        link.element.setLinkUrl(link.startOffset, newOffset, link.url);
      
      } else {
        deprecatedLinks.push(link);
      }
      
    }
  
  }
  ) 
  
  // error handling: show deprecated links:
  
  if(deprecatedLinks.length > 0) {
    Logger.log("Links we could not update:");
    
    for(var i = 0; i < deprecatedLinks.length; i++) {
      var link = deprecatedLinks[i];
      var oldText = link.element.getText().substring(link.startOffset, link.endOffsetInclusive);
      Logger.log("heading: " + link.url + " / description: " + oldText);
    }
  } else {
    Logger.log("all links updated");
  }
  
}


/**
 * Get an array of all LinkUrls in the document. The function is
 * recursive, and if no element is provided, it will default to
 * the active document's Body element.
 *
 * @param {Element} element The document element to operate on. 
 * .
 * @returns {Array}         Array of objects, vis
 *                              {element,
 *                               startOffset,
 *                               endOffsetInclusive, 
 *                               url}
 *
 * Credits: https://whosebug.com/questions/18727341/get-all-links-in-a-document/40730088
 */
function getAllLinks_(element) {
  var links = [];
  element = element || DocumentApp.getActiveDocument().getBody();

  if (element.getType() === DocumentApp.ElementType.TEXT) {
    var textObj = element.editAsText();
    var text = element.getText();
    var inUrl = false;
    var curUrl = {};
    for (var ch=0; ch < text.length; ch++) {
      var url = textObj.getLinkUrl(ch);
      if (url != null) {
        if (!inUrl) {
          // We are now!
          inUrl = true;
          curUrl = {};
          curUrl.element = element;
          curUrl.url = String( url ); // grab a copy
          curUrl.startOffset = ch;
        }
        else {
          curUrl.endOffsetInclusive = ch;
        }          
      }
      else {
        if (inUrl) {
          // Not any more, we're not.
          inUrl = false;
          links.push(curUrl);  // add to links
          curUrl = {};
        }
      }
    }
    // edge case: link is at the end of a paragraph
    // check if object is empty
    if(inUrl && (Object.keys(curUrl).length !== 0 || curUrl.constructor !== Object)) {
      links.push(curUrl);  // add to links
      curUrl = {};
    }
  }
  else {
    // only traverse if the element is traversable
    if(typeof element.getNumChildren !== "undefined") {
        var numChildren = element.getNumChildren();
      
        for (var i=0; i<numChildren; i++) {
  
        // exclude Table of Contents
       
        child = element.getChild(i);
        if(child.getType() !== DocumentApp.ElementType.TABLE_OF_CONTENTS) {
          links = links.concat(getAllLinks_(element.getChild(i)));
        }
      }
    }
  }

  return links;
}


/**
 * returns a map of all headings within an element. The map key
 * is the heading ID, such as h.q1xuchg2smrk
 *
 * THIS REQUIRES A CURRENT TABLE OF CONTENTS IN THE DOCUMENT TO WORK PROPERLY.
 *
 * @param {Element} element The document element to operate on. 
 * .
 * @returns {Map} Map with heading ID as key and the heading element as value.
 */
function getAllHeadings_(element) {
  
  var headingsMap = new Map();
  
  var p = element.findElement(DocumentApp.ElementType.TABLE_OF_CONTENTS).getElement();

  if(p !== null) {
      var toc = p.asTableOfContents();
      for (var ti = 0; ti < toc.getNumChildren(); ti++) {
        
        var itemToc = toc.getChild(ti).asParagraph().getChild(0).asText();
        var itemText = itemToc.getText();
        var itemUrl =  itemToc.getLinkUrl(0);
        var itemDesc = null;
    
        // strip the line numbers if TOC contains line numbers
        var itemText = itemText.match(/(.*)\t/)[1];
        headingsMap.set(itemUrl,itemText);
      }
    }
    return headingsMap;
}