Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
282 views
in Technique[技术] by (71.8m points)

Google Docs API - complete documentation (hyperlink issue)

I hope everyone is in good health. This post is my continue of my previous post

My main goal

So main goal was to get the hyperlink and change it the text linked with it. I initially used code from this post and modified it to change the text of first hyperlink. Here is my modified code to change the text of first hyperlink.

function onOpen() {
  const ui = DocumentApp.getUi();
  ui.createMenu('What to do?')
    .addItem('HyperLink Modifier', 'findAndReplacetext')
    .addToUi();
}


/**
 * 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 The document element to operate on. 
 * .
 * @returns {Array}         Array of objects, vis
 *                              {element,
 *                               startOffset,
 *                               endOffsetInclusive, 
 *                               url}
 */
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;
    for (var ch=0; ch < text.length; ch++) {
      var url = textObj.getLinkUrl(ch);
      if (url != null) {
        if (!inUrl) {
          // We are now!
          inUrl = true;
          var 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 = {};
        }
      }
    }
    if (inUrl) {
      // in case the link ends on the same char that the element does
      links.push(curUrl); 
    }
  }
  else {
    var numChildren = element.getNumChildren();
    for (var i=0; i<numChildren; i++) {
      links = links.concat(getAllLinks(element.getChild(i)));
    }
  }
  return links;
}

/**
 * Replace all or part of UrlLinks in the document.
 *
 * @param {String} searchPattern    the regex pattern to search for 
 * @param {String} replacement      the text to use as replacement
 *
 * @returns {Number}                number of Urls changed 
 */

function findAndReplacetext() {
    var links = getAllLinks();
    while(links.length > 0){
      var link = links[0];
      var paragraph = link.element.getText();
      var linkText = paragraph.substring(link.startOffset, link.endOffsetInclusive+1);
      var newlinkText = `(${linkText})[${link.url}]`
      link.element.deleteText(link.startOffset, link.endOffsetInclusive);
      link.element.insertText(link.startOffset, newlinkText);
      links = getAllLinks();
    }
}

String.prototype.betterReplace = function(search, replace, position) {
  if (this.length > position) {
    return this.slice(0, position) + this.slice(position).replace(search, replace);
  }
  return this;
}

Note: I used insertText and deleteText functions to update the text value of hyperlink.

My problem with above code

Now the problem was that this code was running too slow. I thought may be it was because I was running the script every-time I needed to search for next hyperlink, So maybe I can break the loop and only get the first hyperlink each time.
Then from my previous post the guy gave me a solution to break loop and only get the first hyperlink but when I tried the new code unfortunately it was still slow. In that post he also proposed me a new method by using Google Docs API, I tried using that it was was super fast. Here is the code using Google Docs API

function myFunction() {
    const doc = DocumentApp.getActiveDocument();
    const res = Docs.Documents.get(doc.getId()).body.content.reduce((ar, {paragraph}) => {
      if (paragraph && paragraph.elements) {
        paragraph.elements.forEach(({textRun}) => {
          if (textRun && textRun.textStyle && textRun.textStyle.link) {
            ar.push({text: textRun.content, url: textRun.textStyle.link.url});
          }
        });
      }
      return ar;
    }, []);
    console.log(res)  // You can retrieve 1st link and test by console.log(res[0]).
  }

My new problem

I liked the new code but I am stuck again at this point as I am unable to find how can I change the text associated with the hyperlink. I tried using the functions setContent and setUrl but they don't seem to work. Also I am unable to find the documentation for these functions on main documentation of this API. I did find I reference for previously mentioned functions here but they are not available for appscript.
Here is the sample document I am working on
https://docs.google.com/document/d/1eRvnR2NCdsO94C5nqly4nRXCttNziGhwgR99jElcJ_I/edit?usp=sharing

End note:

I hope I was able to completly convey my message and all the details assosiated with it. If not kindly don't be mad at me, I am still in learning process and my English skills are pretty weak. Anyway if you want any other data let me know in the comments and Thanks for giving your time I really appreciate that.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

In order to remove all the hyperlink from your document, you can do the following:

  • First, retrieve the start and end indexes of these hyperlinks. This can be done by calling documents.get, iterate through all elements in the body content, checking which ones are paragraphs, iterating through the corresponding TextRun, and checking which TextRuns contain a TextStyle with a link property. All this is already done in the code you provided in your question.
  • Next, for all TextRuns that include a link, retrieve their startIndex and endIndex.
  • Using these retrieved indexes, call batchUpdate to make an UpdateTextStyleRequest. You want to remove the link property between each pair of indexes, and for that you would just need to set fields to link (in order to specify which properties you want to update) and don't set a link property in the textStyle property you provide in the request since, as the docs for TextStyle say:

link: If unset, there is no link.

Code sample:

function removeHyperlinks() {
  const doc = DocumentApp.getActiveDocument();
  const hyperlinkIndexes = Docs.Documents.get(doc.getId()).body.content.reduce((ar, {paragraph}) => {
    if (paragraph && paragraph.elements) {
      paragraph.elements.forEach(element => {
        const textRun = element.textRun;
        if (textRun && textRun.textStyle && textRun.textStyle.link) {
          ar.push({startIndex: element.startIndex, endIndex: element.endIndex });
        }
      });
    }
    return ar;
  }, []);
  hyperlinkIndexes.forEach(hyperlinkIndex => {
    const resourceUpdateStyle = {
      requests: [
        {
          updateTextStyle: {
            textStyle: {},
            fields: "link",
            range: {
              startIndex: hyperlinkIndex.startIndex,
              endIndex: hyperlinkIndex.endIndex
            }
          }
        }
      ]    
    }
    Docs.Documents.batchUpdate(resourceUpdateStyle, doc.getId());
  });
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...