Search code examples
google-apps-scriptgoogle-docs-api

Replace text with hyperlink using Google apps script


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);
    }
}
  

Solution

  • 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);
      }
    }