I hope everyone is in good health. This post is my continue of my previous post
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.
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]).
}
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
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.
In order to remove all the hyperlink from your document, you can do the following:
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.TextRuns
that include a link
, retrieve their startIndex
and endIndex
.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.
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());
});
}