Get All Links in a Document

后端 未结 7 1335
余生分开走
余生分开走 2020-12-01 08:33

Given a \"normal document\" in Google Docs/Drive (e.g. paragraphs, lists, tables) which contains external links scattered throughout the content, how do you compile a list o

7条回答
  •  刺人心
    刺人心 (楼主)
    2020-12-01 08:56

    I offer another, shorter answer for your first question, concerning iterating through all links in a document's body. This instructive code returns a flat array of links in the current document's body, where each link is represented by an object with entries pointing to the text element (text), the paragraph element or list item element in which it's contained (paragraph), the offset index in the text where the link appears (startOffset) and the URL itself (url). Hopefully, you'll find it easy to suit it for your own needs.

    It uses the getTextAttributeIndices() method rather than iterating over every character of the text, and is thus expected to perform much more quickly than previously written answers.

    EDIT: Since originally posting this answer, I modified the function a couple of times. It now also (1) includes the endOffsetInclusive property for each link (note that it can be null for links that extend to the end of the text element - in this case one can use link.text.length-1 instead); (2) finds links in all sections of the document, not only the body, and (3) includes the section and isFirstPageSection properties to indicate where the link is located; (4) accepts the argument mergeAdjacent, which when set to true, will return only a single link entry for a continuous stretch of text linked to the same URL (which would be considered separate if, for instance, part of the text is styled differently than another part).

    For the purpose of including links under all sections, a new utility function, iterateSections(), was introduced.

    /**
     * Returns a flat array of links which appear in the active document's body. 
     * Each link is represented by a simple Javascript object with the following 
     * keys:
     *   - "section": {ContainerElement} the document section in which the link is
     *     found. 
     *   - "isFirstPageSection": {Boolean} whether the given section is a first-page
     *     header/footer section.
     *   - "paragraph": {ContainerElement} contains a reference to the Paragraph 
     *     or ListItem element in which the link is found.
     *   - "text": the Text element in which the link is found.
     *   - "startOffset": {Number} the position (offset) in the link text begins.
     *   - "endOffsetInclusive": the position of the last character of the link
     *      text, or null if the link extends to the end of the text element.
     *   - "url": the URL of the link.
     *
     * @param {boolean} mergeAdjacent Whether consecutive links which carry 
     *     different attributes (for any reason) should be returned as a single 
     *     entry.
     * 
     * @returns {Array} the aforementioned flat array of links.
     */
    function getAllLinks(mergeAdjacent) {
      var links = [];
    
      var doc = DocumentApp.getActiveDocument();
    
    
      iterateSections(doc, function(section, sectionIndex, isFirstPageSection) {
        if (!("getParagraphs" in section)) {
          // as we're using some undocumented API, adding this to avoid cryptic
          // messages upon possible API changes.
          throw new Error("An API change has caused this script to stop " + 
                          "working.\n" +
                          "Section #" + sectionIndex + " of type " + 
                          section.getType() + " has no .getParagraphs() method. " +
            "Stopping script.");
        }
    
        section.getParagraphs().forEach(function(par) { 
          // skip empty paragraphs
          if (par.getNumChildren() == 0) {
            return;
          }
    
          // go over all text elements in paragraph / list-item
          for (var el=par.getChild(0); el!=null; el=el.getNextSibling()) {
            if (el.getType() != DocumentApp.ElementType.TEXT) {
              continue;
            }
    
            // go over all styling segments in text element
            var attributeIndices = el.getTextAttributeIndices();
            var lastLink = null;
            attributeIndices.forEach(function(startOffset, i, attributeIndices) { 
              var url = el.getLinkUrl(startOffset);
    
              if (url != null) {
                // we hit a link
                var endOffsetInclusive = (i+1 < attributeIndices.length? 
                                          attributeIndices[i+1]-1 : null);
    
                // check if this and the last found link are continuous
                if (mergeAdjacent && lastLink != null && lastLink.url == url && 
                      lastLink.endOffsetInclusive == startOffset - 1) {
                  // this and the previous style segment are continuous
                  lastLink.endOffsetInclusive = endOffsetInclusive;
                  return;
                }
    
                lastLink = {
                  "section": section,
                  "isFirstPageSection": isFirstPageSection,
                  "paragraph": par,
                  "textEl": el,
                  "startOffset": startOffset,
                  "endOffsetInclusive": endOffsetInclusive,
                  "url": url
                };
    
                links.push(lastLink);
              }        
            });
          }
        });
      });
    
    
      return links;
    }
    
    /**
     * Calls the given function for each section of the document (body, header, 
     * etc.). Sections are children of the DocumentElement object.
     *
     * @param {Document} doc The Document object (such as the one obtained via
     *     a call to DocumentApp.getActiveDocument()) with the sections to iterate
     *     over.
     * @param {Function} func A callback function which will be called, for each
     *     section, with the following arguments (in order):
     *       - {ContainerElement} section - the section element
     *       - {Number} sectionIndex - the child index of the section, such that
     *         doc.getBody().getParent().getChild(sectionIndex) == section.
     *       - {Boolean} isFirstPageSection - whether the section is a first-page
     *         header/footer section.
     */
    function iterateSections(doc, func) {
      // get the DocumentElement interface to iterate over all sections
      // this bit is undocumented API
      var docEl = doc.getBody().getParent();
    
      var regularHeaderSectionIndex = (doc.getHeader() == null? -1 : 
                                       docEl.getChildIndex(doc.getHeader()));
      var regularFooterSectionIndex = (doc.getFooter() == null? -1 : 
                                       docEl.getChildIndex(doc.getFooter()));
    
      for (var i=0; i

提交回复
热议问题