1

I need to replace the word without affecting hyperlink (hyperlink must be preserved for the replaced word) and those with non hyperlinks the replace must happen in a regular way.

Here is the link of the coded Docs

I have tried with

function run() {
    var findtext = "Search";
    var replacetext = "Replacer";

    var body = DocumentApp.getActiveDocument().getBody();
    var foundElement = body.findText(findtext);

    while (foundElement != null) {
        var foundText = foundElement.getElement().asText();
        var startOffset = foundElement.getStartOffset();
        var endOffsetInclusive = foundElement.getEndOffsetInclusive();
        var hyperlink = foundText.getLinkUrl(0);
        foundText.insertText(0, findtext);
        foundText.setLinkUrl(startOffset + findtext.length, endOffsetInclusive + findtext.length, hyperlink);
        foundText.deleteText(startOffset + findtext.length, endOffsetInclusive + findtext.length)

        foundElement = body.findText(findtext, foundElement);
    }
}
  

1 Answer 1

3

The main issue is treating the result from findText as a word.

It is tricky because you can't get a "word" element. You have to:

  • Take the whole paragraph element that findText returns. This contains the search result.
  • Get the index values of the start and end of the found word.
  • Get the hyperlink at that index
  • Delete the text between those indices
  • Insert the new text and then assign the hyperlink with the new indices.

For example:

foundText.insertText(0, findtext)

Inserts the text you are looking for, i.e. "Search", at the start of the element which the result is in.

This:

var hyperlink = foundText.getLinkUrl(0)

This will only get the hyperlink found at the start of the paragraph, for example, which means that if the first word of the paragraph has a hyperlink, this is what it will return. In getLinkUrl() you should use the start index of the search result.

Solution

This code will replace text and will keep the hyperlink, if it has one.

function replaceTextKeepHyperlink(textToReplace, ReplacementText) {
  var body = DocumentApp.getActiveDocument().getBody();
  var searchResult = body.findText(textToReplace);
  
  while (searchResult != null) {
    
    // Getting info about result
    var foundText = searchResult.getElement().asText();
    var start = searchResult.getStartOffset();
    var end = searchResult.getEndOffsetInclusive();
    var hyperlink = searchResult.getElement().getLinkUrl(start);
    
    // Modifying text
    foundText.deleteText(start, end)
    foundText.insertText(start, ReplacementText)
    foundText.setLinkUrl(start, start + ReplacementText.length - 1, hyperlink)
    
    // Moving to next search result
    searchResult = body.findText(textToReplace, searchResult);
  }
}

It will not keep any other formatting though, so for that you would have add in some lines to the "Getting info" and "Modifying" parts of the code.

Reference


Update

mshcruz found that if you called the function with parameters like this:

replaceTextKeepHyperlink("Search", "PrefixedSearch")

The function gets caught in an infinite loop, because it finds the text its looking for in the text its just replaced, replaces that part, and on and on.

He provided the fix which is incorporated below with a try block to avoid the error that it produces if a textToReplace is found at the end of the document:

function replaceTextKeepHyperlink(textToReplace, ReplacementText) {
  var body = DocumentApp.getActiveDocument().getBody();
  var searchResult = body.findText(textToReplace);
  
  while (searchResult != null) {
    var foundText = searchResult.getElement().asText();
    var start = searchResult.getStartOffset();
    var end = searchResult.getEndOffsetInclusive();
    var hyperlink = searchResult.getElement().getLinkUrl(start);
    
    foundText.deleteText(start, end)
    foundText.insertText(start, ReplacementText)
    foundText.setLinkUrl(start, start + ReplacementText.length - 1, hyperlink)
    
    try {
      let rangeBuilder = DocumentApp.getActiveDocument().newRange();
      rangeBuilder.addElement(searchResult.getElement(), start, end+ReplacementText.length - 1);
      searchResult = rangeBuilder.getRangeElements()[0];
    } catch (e){
      Logger.log("End of Document")
      return null
    }
    
    searchResult = body.findText(textToReplace, searchResult);
  }
}
Sign up to request clarification or add additional context in comments.

5 Comments

It seems this function runs into an endless loop if ReplacementTextcontains textToReplace, but with a prefix that is longer than textToReplace.length, like "MyText" and "MyLongText". Apparently the reason is that textToReplace gets shifted forward and body.findText(textToReplace, searchResult) returns the same textToReplace, instead of the next one.
@mshcruz thanks for bringing it up! Do you mean if ReplacementText is "MyLongText" and textToReplace is "MyText" or the other way around? I just tried with replaceTextKeepHyperlink("Search", "SearchFix") and replaceTextKeepHyperlink("Search", "Sear") and it worked for me. Can you provide a test case in which it fails and I'll take a look, thanks!
Something like replaceTextKeepHyperlink('Text', 'MyLongText') or, using your example, replaceTextKeepHyperlink('Search', 'PrefixedSearch').
It seems the problem gets solved if we update the searchResult RangeElement after inserting the text, like: let rangeBuilder = DocumentApp.getActiveDocument().newRange(); rangeBuilder.addElement(searchResult.getElement(), start, end+ReplacementText.length - 1); searchResult = rangeBuilder.getRangeElements()[0];.
Nice one @mshcruz ! I've added an edit to the answer incorporating it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.