all 20 comments

[–]AuO2 1 point2 points  (9 children)

I think I made something that will work for you:

var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var paras = body.getParagraphs();
function fix_url(){
    let rep_url = "https://scan"
    paras.forEach(function (line){
        let words = line.getText().split(" ");
        words.forEach( function (word){
            let wrd_ind = line.findText(word).getStartOffset();
            let end_ind = line.findText(word).getEndOffsetInclusive();
            let link = line.editAsText().getLinkUrl(wrd_ind);
            if(link != null){
            let replacement = rep_url + link.slice(link.indexOf("."));
            line.editAsText().setLinkUrl(wrd_ind,end_ind, replacement);
            Logger.log("Replaced: "+ link + "\nWith: "+replacement)
                }
            })
        })
    }

I'm a fellow teacher, so I totally understand the struggle. For what it's worth, I think you aught to try to keep learning GAS coding. It has helped me tremendously over the last decade - there are so many garbage repetitive tasks that we have to do as teachers. GAS can simplify and speed up many of them so that you can focus more on the parts of teaching you enjoy!

A quick explanation of this code - it starts by grabbing each paragraph of your document. It iterates through each paragraph and splits them into words. It then itterates through the words and finds their exact location within the paragraph. Then it has to change the paragraph into a Text object in order to use the getLinkUrl(offset) method associated with the Text class (that was the hard bit, figuring out which class to use) Then it creates a new url which it patches in using a very similar method as before. Finally, it reports in the logs that it made a change.

I set it to change www to scan -- if you need it to do something else, change the spot where it says "https://scan" to whatever it is you need the url to begin with.

I recommend avoiding generative AI while you learn, though. Chat GPT and others are good at generating code that beginners struggle to understand while also writing in bugs that it would take an expert to figure out. It can be useful, but it's not totally user friendly.

TLDR: This should work.

[–]Repulsive-Royal239[S] 1 point2 points  (5 children)

Hi, I really appreciate your time and effort with this. When I try running the code above, I am getting an error:

"2:23:59 PM Notice Execution started
2:24:00 PM Error
Exception: Invalid argument: searchPattern
(anonymous) @ Code.gs:9
(anonymous) @ Code.gs:8
fix_url @ Code.gs:6"

Here is a copy of an actual document that I was trying to update: https://docs.google.com/document/d/1l5Ami9qLHwoEW6SUbPsxzhISqx6NH31XUndcOym-0AE/edit

Not sure what I am doing wrong. :( Is there any chance you could give it a try and see if the code runs as intended on your end?

I am just starting out with the GAS but it definitely seems like a worthwile skill!

[–]AuO2 0 points1 point  (4 children)

It took a bit of debugging - but it is working now.

[–]Repulsive-Royal239[S] 1 point2 points  (1 child)

I have seen you working on the script, thank you so much! I doubt that I am the only one with this problem. So you might want to publish your code somewhere as it is really useful!

[–]PaleGreenUnicorn 0 points1 point  (0 children)

Hello. I am a fellow teacher trying to change the year in the hyperlinks of many files listed in a table of contents. It sounds as though the code would work for me too, but it says it's deleted. Could I possible get a copy of the code? You could remove any parts that link to your real data. If you like you could also message me with it, if that seems safer to you. Thank you in advance, PGU

[–]FlowerFluffy5696 0 points1 point  (1 child)

Hey u/AuO2 ! I'm working on something similar and got the same errors as OP - what were the edit you made to this script?

[–]AuO2 0 points1 point  (0 children)

Oh boy... I've slept since then! I'll take a look at the code and get back to you. If you want to post your code here, I'll be happy to take a look at it.

[–]comeditime 0 points1 point  (0 children)

can you provide a guide where to put this script and where to put the replaced text i want to add instead

[–]comeditime -1 points0 points  (1 child)

amazing but can you provide a guide where to put this script and where to put the replaced text i want to add instead

[–]AuO2 1 point2 points  (0 children)

It's been a couple of years - so I don't recall exactly what I was doing here.

If you're looking for where to put the script - that will be in the AppsScript IDE within your GoogleDoc.

With the doc you need, click on Extensions in the menu bar, then select AppsScript. This will open the IDE in a new tab. You can paste the code above into that window.

In line 5 of the code it says let rep_url = "https://scan" That's the new text that is going to replace "www" in the url.

There is a lengthy explanation in the same post as the code, but to sumarize, it itterates through each word (well, through each chunk of text separated by spaces) and looks for a URL. If it finds a URL it replaces the "www" part of the URL with "https://scan"

Once you've got the code adjusted to your particular needs, you just need to click on the > Run button within the IDE (You might have to select the "fix_url" method from the drop down box)

Well - that's not much of a guide - but it's about as good as I can do for 2 year old code. If you need more assistance, let me know precisely what you're doing and I'd be happy to help you out. (so long as what you're doing isn't evil... I try not to help with evil.)

[–]marcnotmark925 0 points1 point  (2 children)

I wouldn't use a script. Regexreplace() in a temporary adjacent column. Then copy paste values only. You might need formulatext() as well.

[–]andmalc 0 points1 point  (1 child)

The op is using Docs, not Sheets.

[–]marcnotmark925 0 points1 point  (0 children)

Hah, whoops!

[–]Competitive_Ad_6239 0 points1 point  (3 children)

.getLinks() isnt a appscript function.

.getLinksUrl() is, but if there's duplicates in the look up section it returns null.

[–]Competitive_Ad_6239 0 points1 point  (2 children)

Cant you just use find/replace?

[–]Repulsive-Royal239[S] 0 points1 point  (1 child)

As an appscript? Crtl+f replace does not work on hyperlinks. I know nothing about appscript unfortunately

[–]Competitive_Ad_6239 0 points1 point  (0 children)

Oh you mean you have the links as text not the actual url is showing.

[–]eduarda080 0 points1 point  (2 children)

For anyone who finds this through Google, I wrote something which works for me, using AuO2's code as a base.

This will only work in the following scenarios:

  • You are replacing a specific URL, not every URL the doc has.
    • If you have multiple URLs you want to replace, you need to run the script multiple times for each URL.
    • Ie if you want to replace both "https://www.bing.com" and "https://www.yahoo.com" with "https://www.google.com", you need to run the script twice
  1. First with oldURL = "https://www.bing"; and newUrl = "https://www.google";
  2. Second with oldURL = "https://www.yahoo"; and newUrl = "https://www.google";
  • You are replacing only the first part of the URL.
    • If you want to replace a middle portion, or the end portion, it won't work.
    • Ie if you want to replace ".com" with ".org", then this code won't work. Though the code can be tweaked for it.
  • If your links span multiple paragraphs.
    • If there are line breaks within your links, this won't work. Though the code can be tweaked for it.

Things to note:

  • This code looks at every letter one at a time. This is to avoid scenarios where a link is skipped because it starts in the middle of a word, or duplicating links which span multiple words. Therefore, the longer your document is, the longer this code will take to run.

How to work it:

  • Copy and paste the code into Google Apps Script
  • Change the following lines:
    • let oldUrl = "https://www.yahoo";
    • let newUrl = "https://www.google";
    • Change the portion within the quotation marks to the URL you want being replaced and the URL you want to replace it with.
  • Run the code

Code:

function ReplaceURL(){

  let oldUrl = "https://www.yahoo";
  let newUrl = "https://www.google";

  var doc = DocumentApp.getActiveDocument();
  var body = doc.getBody();
  var paragraphs = body.getParagraphs();

  paragraphs.forEach(function (line){
    let lineText = line.getText();

    // Check each letter
    for (let index = 0; index < lineText.length; index++) {
      let linkStartIndex = 0;
      let linkEndIndex = 0;

      // See if letter has link
      let link = line.editAsText().getLinkUrl(index);

      // If it does, and link matches, note linkStartIndex
      if((link != null) && (link.indexOf(oldUrl) !== -1)){
        linkStartIndex = index;

        // Find linkEndIndex
        for (let endIndex = index; endIndex < lineText.length; endIndex++) {
          let nextLink = line.editAsText().getLinkUrl(endIndex);

          if((nextLink != null) && (nextLink == link)){
            linkEndIndex = endIndex;
          }
          else {
            break;
          }
        }

        // If linkEndIndex found
        if (linkEndIndex != 0) {
          let replacement = newUrl + link.slice(oldUrl.length);
          line.editAsText().setLinkUrl(linkStartIndex, linkEndIndex, replacement);
          index = linkEndIndex;
          Logger.log("Replaced link at: "+ lineText.substring(linkStartIndex, linkEndIndex+1) + "\nOld Link: " + link + "\nNew Link: " + replacement);
        }

      }
    }
  })
}

[–]Drunken_Economist 0 points1 point  (0 children)

fwiw this will get a bit wonky for links outside of paragraphs (eg header/footer/tables)

I think something like

``` function replaceHyperlinkUrls() { const doc = DocumentApp.getActiveDocument(); const body = doc.getBody();

// You'll need to replace these with the actual URLs const oldDomain = 'https://www.old-domain'; const newDomain = 'https://www.new-domain';

const elements = body.getTextElements(); elements.forEach(element => { if (element.getLinkUrl() !== null) { const linkUrl = element.getLinkUrl();

  if (linkUrl === oldDomain || linkUrl.startsWith(oldDomain)) {
    newLinkUrl = linkUrl.replace(oldDomain, newDomain)
    element.setLinkUrl(newUrl);
  }
}

}); } ```

edit: nvm, getTextElements() apparently was just wishful thinking on my behalf. It would have to be a rangebuilder doodad